文章转载自:https://zhuanlan.zhihu.com/p/467573337
监控原理
v2-91b3af5685ff106f06e989ddbbf36ca4_1440w.jpg如图所示,按照页面Activity的生命周期将页面分为三个阶段,开始阶段,运行阶段,结束阶段,具体如下:
开始阶段:在Activity的onStart()回调中开始采集cpu/内存/线程数量信息。
运行阶段:在运行期间注册帧率监听回调,开启线程定时采集cpu/内存/线程数量信息保存在缓存中,等待结束时将数据上传服务器。
结束阶段:在onStop执行之前停止信息采集,收集信息上传。不在onPause中做耗时任务是因为会影响下一个Activity的启动,onStop是在主线程空闲的时候执行的。
采集信息
Activity生命周期观察者模式
在Application中注册Activity生命周期回调监测生命周期函数:
application.registerActivityLifeCallBacks();
服务端根据目标用户的城市/高中低机型/业务场景维度下发采集开关及采集时长策略。
内存/FPS/CPU/线程数量/电量/流量指标维度进行信息采集上报。
1.在onActivityStart回调中使用HandlerThread根据服务端下发的定时时长采集内存/CPU/线程数量/流量信息,仅采集app处于前台的信息,app切换回后台后不再采集。
2.在onActivityPause中暂停信息采集,在生命周期onActivityStart回调中恢复执行。
3.帧率的信息采集是在onActivityResume生命周期函数中定时执行信息采集,在onActivityStop中暂停。
采集信息Sampler.java中的实现逻辑:
/**
* 采用HandlerThread子线程,Handler定时发送消息采取cpu 内存 线程数数据
* 根据服务端下发定时时间,默认是3s
* 使用单利设计模式,责任链设计模式
*/
public class Sampler {
public static final String TAG = "ActivityPerformanceSampler";
private static Sampler mInstance;
/** 采集线程*/
private HandlerThread mHandlerThread;
private Handler mHandler;
/**是否开始采集*/
private boolean isStart;
private BaseCollector mCollector;
private Sampler() {
/**初始化HandlerThread工作子线程*/
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
/**数据采集责任链设计模式初始化*/
CpuCollector cpuCollector = new CpuCollector();/**cpu*/
MemoryCollector memoryCollector = new MemoryCollector();/**内存*/
ThreadNumCollector threadNumCollector = new ThreadNumCollector();/**线程数*/
/**封装责任链*/
cpuCollector.setSuccessor(memoryCollector);
memoryCollector.setSuccessor(threadNumCollector);
mCollector = cpuCollector;
}
public static synchronized Sampler getInstance() {
if (null == mInstance) {
mInstance = new Sampler();
}
return mInstance;
}
/** 定时采样线程*/
private final Runnable mWorkRunnable = new Runnable() {
public void run() {
if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
Log.d(TAG, "Sample task run....");
}
doSample();
if (服务端下发字段是否采集) {
if (isStart && mHandler != null) {
mHandler.postDelayed(this, 3000);
}
} else {
stop();
}
}
};
public boolean isStarted() {
return this.isStart;
}
/** 启动采样线程*/
public void start() {
if (!isStart) {
isStart = true;
mHandler.post(mWorkRunnable);
} else {
if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY){
Log.w(TAG, "Sample thread has already start.");
}
}
}
/**
* 停止采样线程
*/
public void stop() {
if (isStart) {
isStart = false;
mHandler.removeCallbacksAndMessages(null);
} else {
if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
Log.w(TAG, "Sample thread is not start.");
}
}
}
/**立即执行doSample操作*/
public void postDoSample() {
if (isStart) {
mHandler.post(new Runnable() {
@Override
public void run() {
if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY){
Log.w(TAG, "doSample directly.");
}
doSample();
}
});
}
}
/**
* 进行性能数据的采样并且上报
*/
private void doSample() {
if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
Log.d(TAG, "perform doSample");
}
try {
HashMap<String, String> data = new HashMap<>();
/** 这个方法是责任链开始工作 传入一个map集合 */
mCollector.process(data);
/**
* 上报数据
*/
} catch (Throwable ex) {
ex.printStackTrace();
}
}
}
CPU信息
Proc文件系统可利用reader以文件系统读取方式从内存中proc文件读取内核cpu信息,可获取用户态的cpu时间、负进程的cpu时间、核心时间、io等待和非io等待时间、硬中断时间、软中断时间。比如:/proc/stat,可获取cpu总使用时间情况,获取文件内容如下
cpu 432661 13295 86656 422145968 171474 233 5346
cpu0 123075 2462 23494 105543694 16586 0 4615
cpu1 111917 4124 23858 105503820 69697 123 371
cpu2 103164 3554 21530 105521167 64032 106 334
cpu3 94504 3153 17772 105577285 21158 4 24
intr 1065711094 1057275779 92 0 6 6 0 4 0 3527 0 0 0 70 0 20 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 7376958 0 0 0 0 0 0 0 1054602 0 0 0 0 0 0 0 30 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
ctxt 19067887
btime 1139187531
processes 270014
procs_running 1
procs_blocked 0
参数 解释
user (432661) 从系统启动开始累计到当前时刻,用户态的CPU时间(单位:jiffies) ,不包含 nice值为负进程。1jiffies=0.01秒
nice (13295) 从系统启动开始累计到当前时刻,nice值为负的进程所占用的CPU时间(单位:jiffies)
system (86656) 从系统启动开始累计到当前时刻,核心时间(单位:jiffies)
idle (422145968) 从系统启动开始累计到当前时刻,除硬盘IO等待时间以外其它等待时间(单位:jiffies)
iowait (171474) 从系统启动开始累计到当前时刻,硬盘IO等待时间(单位:jiffies) ,
irq (233) 从系统启动开始累计到当前时刻,硬中断时间(单位:jiffies)
softirq (5346) 从系统启动开始累计到当前时刻,软中断时间(单位:jiffies)
其中,cpu0-cpu3表示当前cpu为四核,计算时我们主要考虑第一个,也就是这行信息即可:
cpu 432661 13295 86656 422145968 171474 233 5346
实现方式
计算公式:
1)totalTime = user+system+nice+idle+iowait+irq+softtirq
2)cpuWorkTime = user+system+nice
3)cpu占用率 = (work - workBefore) / (totalCpu - totalCpuBefore)
解释:totalTime为cpu总使用时间,workTime为cpu工作时间,两个时间都为连续的片段,所以,我们在计算总占用率时,需要获取两次数据,两次数据的获取时间间隔可以自己设置,1s or 60s都可以,我们用第二次获取的工作时间-历史获取工作时间,就是这段时间内cpu的工作耗时,再除总耗时,即可获取cpu占用率。
//将读取的cpu信息存于字符串数组
String[] cpuInfos = null;
//计算总cpu占用率
try {
//读文件的形式从proc文件系统中读取stat的信息
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("/proc/stat")), 1000);
String load = reader.readLine();
reader.close();
//存储信息
cpuInfos = load.split(" ");
} catch (IOException ex) {
ex.printStackTrace();
}
//totalCpu = user+system+nice+idle+iowait+irq+softtirq
work = Long.parseLong(cpuInfos[2])
+ Long.parseLong(cpuInfos[3]) + Long.parseLong(cpuInfos[4]);
totalCpu = work
+ Long.parseLong(cpuInfos[6]) + Long.parseLong(cpuInfos[5])
+ Long.parseLong(cpuInfos[7]) + Long.parseLong(cpuInfos[8]);
......(省略获取历史工作时间的方式,可使用计时器再本次数据保存为历史数据)
//cpu占用率 = (work - workBefore) / (totalCpu - totalCpuBefore)
float CpuRate = 100 * ((float) (work - workBefore) / (float) (totalCpu - totalCpuBefore));
CPU监控
/**
* 责任链设计模式
* 精简到关注两项指标
* (1)cpu总体使用率。
* (2)应用程序cpu占用率。
*/
public class CpuCollector extends BaseCollector {
/**静态变量是防止高频率重复创建对象,减小内存变化*/
/**记录第一次cpu信息*/
private static CpuInfo cpuInfo1 = new CpuInfo();
/**记录第二次cpu信息*/
private static CpuInfo cpuInfo2 = new CpuInfo();
private static final int BUFFER_SIZE = 1024;//buffer 大小
/**隔开50ms后对/proc/stat或者/proc/+mPid+/stat中cpu数据采样*/
private static final int SLEEP_TIME = 50;
/**区分是第几次获取cpu信息*/
private boolean isFirst = true;
@Override
public void doCollect(HashMap<String, String> vector) {
CpuEntity entity;
/**Api 26以上 执行(top -n 1)命令获取cpu信息*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
entity = getCpuDataForO();
} else {
entity = getCpuEntity();
}
if (entity == null || (TextUtils.isEmpty(entity.app) || TextUtils.isEmpty(entity.total)))
return;
vector.put("cpuTotal", entity.total);
vector.put("cpuApp", entity.app);
}
/**
* Api 26以上 执行(top -n 1)命令获取cpu信息
*/
private CpuEntity getCpuDataForO() {
CpuEntity cpuEntity = new CpuEntity();
java.lang.Process process = null;
try {
try {
Thread.sleep(SLEEP_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
process = Runtime.getRuntime().exec("top -n 1");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
int cpuIndex = -1;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (TextUtils.isEmpty(line)) {
continue;
}
int tempIndex = getCPUIndex(line);
if (tempIndex != -1) {
cpuIndex = tempIndex;
continue;
}
if (line.startsWith(String.valueOf(Process.myPid()))) {
if (cpuIndex == -1) {
continue;
}
String[] param = line.split("\\s+");
/**说明获取的cpu信息不全*/
if (param.length <= cpuIndex) {
continue;
}
/**根据cpuIndex获取cpu信息*/
String cpu = param[cpuIndex];
if (cpu.endsWith("%")) {
cpu = cpu.substring(0, cpu.lastIndexOf("%"));
}
float rate = Float.parseFloat(cpu) / Runtime.getRuntime().availableProcessors();
NumberFormat format = NumberFormat.getNumberInstance();
format.setMaximumFractionDigits(0);
format.setRoundingMode(RoundingMode.UP);
cpuEntity.total = format.format(0);
cpuEntity.app = format.format(rate);
return cpuEntity;
}
}
} catch (Throwable e) {
} finally {
if (process != null) {
process.destroy();
}
}
return cpuEntity;
}
/**
* 获取CPU下标索引 用于获取cpu信息
* @param line
* @return
*/
private int getCPUIndex(String line) {
if (line.contains("CPU")) {
String[] titles = line.split("\\s+");
for (int i = 0; i < titles.length; i++) {
if (titles[i].contains("CPU")) {
return i;
}
}
}
return -1;
}
/**
* Api 26以下获取cpu使用率
* 通过proc文件系统的cpuinfo文件获取
* 不需要root,/proc文件系统是一个伪文件系统,存在于内存内,以文件系统的方式为内核与进程提供通信接口。
*/
private synchronized CpuEntity getCpuEntity() {
CpuEntity entity = new CpuEntity();
try {
if (cpuInfo1 != null) cpuInfo1.reset();
if (cpuInfo2 != null) cpuInfo2.reset();
cpuInfo1 = parse(getCpuInfo(), getMyPidCpuInfo());
if (cpuInfo1 == null) {
return entity;
}
try {
/**两次采集隔开50ms*/
Thread.sleep(SLEEP_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
cpuInfo2 = parse(getCpuInfo(), getMyPidCpuInfo());
if (cpuInfo2 == null) {
return entity;
}
/**两次采集cpu空闲时间差*/
long idleTime = cpuInfo2.idle - cpuInfo1.idle;
/**两次采集cpu总共时间差*/
long totalTime = cpuInfo2.total - cpuInfo1.total;
/**系统cpu使用率*/
double cpu = ((double) (totalTime - idleTime)) / totalTime;
/**app进程cpu使用率*/
double app = ((double) (cpuInfo2.appCpuTime - cpuInfo1.appCpuTime)) / totalTime;
NumberFormat format = NumberFormat.getNumberInstance();
format.setMaximumFractionDigits(0);
format.setRoundingMode(RoundingMode.UP);
/**该段时间内的cpu使用率,总时间减去空闲时间再除以总时间,(totaltime-idletime)/totaltime*/
entity.total = format.format(cpu * 100);
/**该段时间内app进程的cpu使用率, ,(pidCpuTime2-pidCpuTime1)/totaltime*/
entity.app = format.format(app * 100);
} catch (Exception e) {
if (Log.LOGSWITCH) {
e.printStackTrace();
}
}
return entity;
}
/**
* 获取{@link CpuInfo}类型数据
* @return 第一次返回 {@link #cpuInfo1} 第二次返回 {@link #cpuInfo2}
*/
private CpuInfo parse(String cpuInfo, String pidCpuInfo) {
if (TextUtils.isEmpty(cpuInfo)) {
if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
Log.e("CpuCollector method prase cpuInfo is a null object reference");
}
return null;
}
String[] cpuInfoArray = cpuInfo.split(" ");
/**获取的cpu使用率数据不正确返回空值*/
if (cpuInfoArray.length < 9) {
return null;
}
String[] pidCpuInfoArray = pidCpuInfo.split(" ");
/**获取应用进程cpu使用率数据不正确,返回空值*/
if (pidCpuInfoArray.length < 17) {
return null;
}
/**if条件来判断是否进行了第一次数据采集*/
if (isFirst) {
cpuInfo1.user = Long.parseLong(cpuInfoArray[2]);
cpuInfo1.nice = Long.parseLong(cpuInfoArray[3]);
cpuInfo1.system = Long.parseLong(cpuInfoArray[4]);
cpuInfo1.idle = Long.parseLong(cpuInfoArray[5]);
cpuInfo1.ioWait = Long.parseLong(cpuInfoArray[6]);
/**user+system+nice+idle+iowait+irq+softtirq*/
cpuInfo1.total = cpuInfo1.user + cpuInfo1.nice + cpuInfo1.system + cpuInfo1.idle + cpuInfo1.ioWait
+ Long.parseLong(cpuInfoArray[7])
+ Long.parseLong(cpuInfoArray[8]);
/**
* 13 majflt: 当前进程等待子进程的majflt
* 14 utime: 该进程处于用户态的时间,单位jiffies
* 15 stime: 该进程处于内核态的时间,单位jiffies
* 16 cutime: 当前进程等待子进程的utime
*/
cpuInfo1.appCpuTime = Long.parseLong(pidCpuInfoArray[13])
+ Long.parseLong(pidCpuInfoArray[14])
+ Long.parseLong(pidCpuInfoArray[15])
+ Long.parseLong(pidCpuInfoArray[16]);
isFirst = false;
return cpuInfo1;
} else {
cpuInfo2.user = Long.parseLong(cpuInfoArray[2]);
cpuInfo2.nice = Long.parseLong(cpuInfoArray[3]);
cpuInfo2.system = Long.parseLong(cpuInfoArray[4]);
cpuInfo2.idle = Long.parseLong(cpuInfoArray[5]);
cpuInfo2.ioWait = Long.parseLong(cpuInfoArray[6]);
cpuInfo2.total = cpuInfo2.user + cpuInfo2.nice + cpuInfo2.system + cpuInfo2.idle + cpuInfo2.ioWait
+ Long.parseLong(cpuInfoArray[7])
+ Long.parseLong(cpuInfoArray[8]);
cpuInfo2.appCpuTime = Long.parseLong(pidCpuInfoArray[13])
+ Long.parseLong(pidCpuInfoArray[14])
+ Long.parseLong(pidCpuInfoArray[15])
+ Long.parseLong(pidCpuInfoArray[16]);
isFirst = true;
return cpuInfo2;
}
}
/**
* 获取/proc/stat文件中的第一行cpu信息
* @return 当前总的cpu使用信息
*/
private String getCpuInfo() {
BufferedReader cpuReader = null;
String cpuRate = null;
try {
cpuReader = new BufferedReader(new InputStreamReader(
new FileInputStream("/proc/stat")), BUFFER_SIZE);
/**获取/proc/stat文件中的第一行cpu信息*/
cpuRate = cpuReader.readLine();
if (cpuRate == null) {
cpuRate = "";
return "";
}
} catch (FileNotFoundException e) {
if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
Log.d("/proc/stat not found");
}
} catch (IOException e) {
if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY){
Log.d("open /proc/stat fail");
}
} finally {
try {
if (cpuReader != null) {
cpuReader.close();
}
} catch (IOException e) {
if (Log.LOGSWITCH) {
e.printStackTrace();
}
}
}
return cpuRate;
}
/**
* 获取应用进程/proc/myPid/stat文件中的第一行cpu信息
* @return 当前总的cpu使用信息
*/
private String getMyPidCpuInfo() {
BufferedReader pidReader = null;
String pidCpuRate = null;
try {
int mPid = android.os.Process.myPid();
pidReader = new BufferedReader(new InputStreamReader(
new FileInputStream("/proc/" + mPid + "/stat")), BUFFER_SIZE);
pidCpuRate = pidReader.readLine();
if (pidCpuRate == null) {
pidCpuRate = "";
return pidCpuRate;
}
} catch (FileNotFoundException e) {
if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
Log.d("my application /proc/mPid/stat not found");
}
} catch (IOException e) {
if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
Log.d("my application /proc/mPid/stat open fail");
}
} finally {
try {
if (pidReader != null) {
pidReader.close();
}
} catch (IOException e) {
if (Log.LOGSWITCH) {
e.printStackTrace();
}
}
}
return pidCpuRate;
}
class CpuEntity {
/**该段时间内的cpu使用率,总时间减去空闲时间再除以总时间,(totaltime-idletime)/totaltime*/
public String total;
/**该段时间内该进程的cpu使用率,进程使用的时间片除以总时间,(pidCpuTime2-pidCpuTime1)/totaltime*/
public String app;
}
/**
* 内部类,记录cpu的信息,本类内部cpuInfo都是该类型
*/
private static class CpuInfo {
/**用户态时间*/
long user;
/**用户态时间(低优先级,nice>0)*/
long nice;
/**内核态时间*/
long system;
/**空闲时间*/
long idle;
/**I/O等待时间*/
long ioWait;
/**硬中断时间*/
long irq;
/**软中断时间*/
long softtirq;
/**系统cpu总使用时间*/
long total;
/**app进程cpu总使用时间*/
long appCpuTime;
public void reset() {
user = 0;
nice = 0;
system = 0;
idle = 0;
ioWait = 0;
total = 0;
appCpuTime = 0;
}
}
}
内存监控
获取的是分配给app的总内存量,app已使用的内存总量,当前页面使用内存的增加量
/**
* 责任链设计模式中获取分配APP内存总量、内存使用量、使用内存增加量
*/
public class MemoryCollector extends BaseCollector {
@Override
public void doCollect(HashMap<String, String> vector) {
MemoryEntity entity = getMemoryEntity();
/**系统可分配给App的最大内存*/
vector.put("memoryMax", String.valueOf(entity.maxMemory/1024/1024));
/**当前使用内存大小*/
vector.put("memoryUsed", String.valueOf(entity.usedMemory/1024/1024));
Session session = ActivityPerfMonitor.getInstance().getCurrentSession();
if (session != null){
/**计算并记录最大内存增量*/
session.recordMemoryUsage(entity.usedMemory);
}
}
/**
* get the memory of process with certain pid.
*
* @return memory usage of certain process
*/
private MemoryEntity getMemoryEntity() {
MemoryEntity entity = new MemoryEntity();
try {
long totalMemory = Runtime.getRuntime().totalMemory();
long freeMemory = Runtime.getRuntime().freeMemory();
entity.maxMemory = Runtime.getRuntime().maxMemory();
entity.usedMemory = (totalMemory - freeMemory);
} catch (Exception e) {
e.printStackTrace();
}
return entity;
}
/**
* 获取内存使用量
* @return
*/
public static long getUsedMemory() {
long totalMemory = Runtime.getRuntime().totalMemory();
long freeMemory = Runtime.getRuntime().freeMemory();
return (totalMemory - freeMemory);
}
/**
* 内存采样信息
* Runtime.getRuntime().maxMemory();
* Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
*/
class MemoryEntity {
public long maxMemory; //系统可分配给App的最大内存
public long usedMemory; //当前使用内存大小
}
}
流量监控
在onActivityStarted方法中开始统计进入页面的流量戳,在onActivityStoped方法中开始统计离开页面的流量戳。
/**
* 通过broadcastReceiver广播状态来判定当前网络是否为移动数据
* 然后根据状态的切换记录使用app消耗了多少流量
*/
public class MobileTrafficCalculator {
public static int uid = -1;
/**
* 获取app的瞬间流量值
* @return 流量值
*/
public static long getApplicationTrafficFlow() {
Context context = Sentry.getApplication().getApplicationContext();
if (context == null) return -1;
long totalBytes = 0;
long rxBytes = 0;
long txBytes = 0;
if(uid == -1){
try {
/**根据包名获取uid*/
PackageManager pm = context.getPackageManager();
ApplicationInfo ai = pm.getApplicationInfo(ProcessUtil.getPackName(), PackageManager.GET_ACTIVITIES);
uid = ai.uid;
if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
Log.d("self_flowData", "uid= " + uid);
}
} catch (PackageManager.NameNotFoundException e) {
if (Log.LOGSWITCH) {
e.printStackTrace();
}
}
}
try {
/**获取到目前为止此uid共接收的总字节数//2.2以上版本适用*/
/**根据UID获取某个网络接收和发送字节的总和*/
rxBytes = TrafficStats.getUidRxBytes(uid);
txBytes = TrafficStats.getUidTxBytes(uid);
totalBytes = rxBytes + txBytes;
if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
Log.d("self_flowData", "uid=" + uid + " ; totalBytes=" + totalBytes + " ; rxBytes=" + rxBytes + " ; txBytes=" + txBytes);
}
} catch (Throwable e) {
// TODO: handle exception
if (Log.LOGSWITCH) {
e.printStackTrace();
}
}
return totalBytes;
}
}
线程数量监控
通过Thread.getAllStackTraces()方法返回Map<Thread,StackTraceElement[]>集合,遍历集合可以获取线程数量,名称和调用栈信息。
/**
* 责任链设计模式中获取线程数和线程名
*/
public class ThreadNumCollector extends BaseCollector {
@Override
public void doCollect(HashMap<String, String> vector) {
int num = getThreadNum();
if (num <= 0) return;
vector.put("threadNum", String.valueOf(num));
vector.put("threadName", getAllThreadName());
}
/**
* 获取当前进程中的线程数目
*
* @return 返回int类型
*/
public int getThreadNum() {
Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
Log.d(Configuration.COMMON_TAG, "当前线程数:" + map.size());
}
return map.size();
}
/**
* 获取当前进程中的所有线程的名字
*
* @return 返回一个由线程名字拼接成的字符串,由空格分割
*/
public String getAllThreadName() {
Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
StringBuilder builder = new StringBuilder();
Iterator<Thread> iterator = map.keySet().iterator();
boolean isFirst = true;
while (iterator.hasNext()) {
if (!isFirst) {
builder.append("|||");
}
builder.append(iterator.next().getName());
isFirst = false;
}
return String.valueOf(builder);
}
}
帧率监控
在onActivityResume方法中注册帧绘制回调,开始帧率计算,在onActivityPause方法中注销掉监听,暂停帧率采集,在onChange方法中设置监听项
/**
* 帧率统计
* 目前统计三个指标: 帧率,掉帧数量,掉帧程度
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public class FrameTrace implements Choreographer.FrameCallback, ViewTreeObserver.OnDrawListener {
public static final String TAG = FrameTrace.class.getSimpleName();
private static FrameTrace mInstance;
private Choreographer mChoreographer;
/**记录上一帧绘制的时间*/
private long mLastFrameNanos;
private boolean isCreated;
private volatile boolean isPause = true;
/**标识页面是否将要去绘制*/
private boolean isDrawing = false;
/**标识是否在有效生命周期区间内*/
private boolean isInvalid = false;
/**当前停留页面*/
private String mCurrentScene;
/**相邻帧数据*/
private LinkedList<Float> mFrameData;
/**帧刷新累计时间*/
private float mFrameTotalTime;
private FrameTrace() {
}
public static synchronized FrameTrace getInstance() {
if (null == mInstance) {
mInstance = new FrameTrace();
}
return mInstance;
}
/**暂停帧率采集*/
public void pause() {
if (!isCreated) {
return;
}
isPause = true;
if (null != mChoreographer) {
mChoreographer.removeFrameCallback(this);
mLastFrameNanos = 0;
}
}
/**启动帧率采集*/
public void resume() {
if (!ProcessUtil.isInMainThread(Thread.currentThread().getId())) {
if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
Log.e("FrameTrace init must create on main thread.");
}
return;
}
if (!isCreated) {
if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
Log.d("FrameTrace init.");
}
isCreated = true;
mFrameData = new LinkedList<>();
mChoreographer = Choreographer.getInstance();
}
/**activity在前台*/
if (ApplicationLifecycleObservable.getInstance().isForeground()) {
isPause = false;
if (null != mChoreographer) {
mChoreographer.removeFrameCallback(this);
mChoreographer.postFrameCallback(this);
mLastFrameNanos = 0;
}
}
}
public void setCurrentScene(String scene) {
this.mCurrentScene = scene;
}
/**添加视图树观察者 来判断是否开始绘制*/
public void addDrawListener(final Activity activity) {
isInvalid = false;
activity.getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
activity.getWindow().getDecorView().getViewTreeObserver().removeOnDrawListener(FrameTrace.this);
activity.getWindow().getDecorView().getViewTreeObserver().addOnDrawListener(FrameTrace.this);
}
});
}
public void removeDrawListener(final Activity activity) {
isInvalid = true;
activity.getWindow().getDecorView().getViewTreeObserver().removeOnDrawListener(FrameTrace.this);
}
@Override
public void onDraw() {
isDrawing = true;
}
@Override
public void doFrame(long frameTimeNanos) {
if (isPause) {
return;
}
if (frameTimeNanos < mLastFrameNanos || mLastFrameNanos <= 0) {
mLastFrameNanos = frameTimeNanos;
if (null != mChoreographer) {
mChoreographer.postFrameCallback(this);
}
return;
}
/**开启监控*/
if (!isInvalid && isDrawing) {
handleDoFrame(frameTimeNanos);
}
if (null != mChoreographer) {
mChoreographer.postFrameCallback(this);
}
mLastFrameNanos = frameTimeNanos;
isDrawing = false;
}
/**数据缓存*/
private void handleDoFrame(long frameNanos) {
/**单帧绘制时间*/
long offset = frameNanos - mLastFrameNanos;
if (offset >= 5 * 1000000000L) {
if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
Log.w(TAG, "[handleDoFrame] WARNING drop frame! offset: " + offset + " ,scene: " + mCurrentScene);
}
}
synchronized (this.getClass()) {
if (!TextUtils.isEmpty(mCurrentScene)) {
float offsetMs = nanoTime2Ms(offset);
mFrameTotalTime += offsetMs;
mFrameData.add(offsetMs);
/** 帧率计算时间,即每隔多长时间计算一次帧率 默认2s计算一次*/
if (mFrameTotalTime >= DEFAULT_FPS_TIME_SLICE_ALIVE_S * 1000) {
asyncReport();
}
}
}
}
private float nanoTime2Ms(long nanos) {
return nanos / 1000000f;
}
private void asyncReport() {
ThreadUtil.getGlobalThreadPool().execute(workRunnable);
}
private Runnable workRunnable = new Runnable() {
@Override
public void run() {
reportData();
}
};
private void reportData() {
LinkedList<Float> raw = null;
synchronized (this.getClass()) {
if (mFrameData.isEmpty()) {
return;
}
raw = mFrameData;
mFrameData = new LinkedList<>();
mFrameTotalTime = 0f;
}
HashMap<String, String> result = new HashMap<>();
/**累计总时间*/
float sumTime = 0f;
/**标记*/
int markIndex = 0;
/**累计刷新次数*/
int count = 0;
/**记录本时间段内掉帧水平*/
int[] dropLevel = new int[DropStatus.values().length];
/**记录本时间段内掉帧数量*/
int[] dropSum = new int[DropStatus.values().length];
/**
* Android 16ms原理*:Android系统每隔16ms会发出VSYNC信号重绘我们的界面(Activity).为什么是16ms,
* 因为Android设定的刷新率是60FPS(Frame Per Second), 也就是每秒60帧的刷新率, 约合16ms刷新一次.
*
* 每隔16ms,系统都会发出VSYNC信号,如果这时候我们的画面(view)准备好了,我们的view绘制就会很流畅。
* 如果我们在这个16ms间隔内,没有准备好画面(view),那么这一次绘制,就不会展示在屏幕上,就相当于少绘制了一帧,画面就会出现卡顿,断断续续。
*/
/**两帧帧间时长,一般等于16.7ms*/
int refreshRate = (int) DEFAULT_DEVICE_REFRESH_RATE;
for (float offset : raw) {
sumTime += offset;
count++;
/**渲染掉帧参数计算 tmp: 渲染了多少次帧(也就是掉了多少帧) offset:统计的绘制一帧所需要的时间 refreshRate: 标准绘制一帧时间 = 16.666667f*/
int tmp = (int)(offset / refreshRate) - 1;
/**掉帧数>=42 冻结*/
if (tmp >= DEFAULT_DROPPED_FROZEN) {
dropLevel[DropStatus.DROPPED_FROZEN.index]++;
dropSum[DropStatus.DROPPED_FROZEN.index] += tmp;
} else if (tmp >= DEFAULT_DROPPED_HIGH) {
dropLevel[DropStatus.DROPPED_HIGH.index]++;
dropSum[DropStatus.DROPPED_HIGH.index] += tmp;
} else if (tmp >= DEFAULT_DROPPED_MIDDLE) {
dropLevel[DropStatus.DROPPED_MIDDLE.index]++;
dropSum[DropStatus.DROPPED_MIDDLE.index] += tmp;
} else if (tmp >= DEFAULT_DROPPED_NORMAL) {
dropLevel[DropStatus.DROPPED_NORMAL.index]++;
dropSum[DropStatus.DROPPED_NORMAL.index] += tmp;
} else {
dropLevel[DropStatus.DROPPED_BEST.index]++;
dropSum[DropStatus.DROPPED_BEST.index] += (tmp < 0 ? 0 : tmp);
}
}
/**帧率计算 帧率 = 每秒刷了多少帧*/
/**标准的屏幕的刷新率为每秒60Hz*/
float fps = Math.min(60.f, 1000.f * (count - markIndex) / sumTime);
if (Log.LOGSWITCH && Log.LOGSWICTH_ACTIVITY) {
Log.d(TAG, "scene : " + mCurrentScene + " , fps ====> " + fps);
}
result.put("value", String.valueOf((int)fps));
result.put("frozen", String.valueOf(dropLevel[DropStatus.DROPPED_FROZEN.index]));
result.put("high", String.valueOf(dropLevel[DropStatus.DROPPED_HIGH.index]));
result.put("middle", String.valueOf(dropLevel[DropStatus.DROPPED_MIDDLE.index]));
result.put("normal", String.valueOf(dropLevel[DropStatus.DROPPED_NORMAL.index]));
/**
* 上报数据 result
*/
}
public enum DropStatus {
DROPPED_FROZEN(4), DROPPED_HIGH(3), DROPPED_MIDDLE(2), DROPPED_NORMAL(1), DROPPED_BEST(0);
int index;
DropStatus(int index) {
this.index = index;
}
}
/**=========================帧率常量 ================================*/
public static final int DEFAULT_FPS_TIME_SLICE_ALIVE_S = 2;
public static final float DEFAULT_DEVICE_REFRESH_RATE = 16.666667f;
public static final int DEFAULT_DROPPED_NORMAL = 3; //正常
public static final int DEFAULT_DROPPED_MIDDLE = 9; //中等
public static final int DEFAULT_DROPPED_HIGH = 24; //严重
public static final int DEFAULT_DROPPED_FROZEN = 42; //冻结
优化流程:
1.UI层级嵌套绘制
2.静态代码扫描工具配合
3.Traceview定位主线程卡顿问题
4.Systrace分析
5.Strictmode严苛模式主线程耗时操作
6.Blockcanary非侵入式自动检测卡顿
网友评论