主流开源框架源码深刻了解第5篇——BlockCanary源码分析。(源码以1.5.0版为准)java
问:为何16ms没完成绘制就会卡顿?android
咱们先来了解几个概念:git
答:Android系统每隔16ms就会发出VSYNC信号,触发对UI进行渲染,VSYNC是Vertical Synchronization(垂直同步)的缩写,能够简单的把它认为是一种定时中断。在Android 4.1中开始引入VSYNC机制。为何是16ms?由于Android设定的刷新率是60FPS(Frame Per Second),也就是每秒60帧的刷新率,约16ms刷新一次。这就意味着,咱们须要在16ms内完成下一次要刷新的界面的相关运算,以便界面刷新更新。举个例子,当运算须要24ms完成时,16ms时就没法正常刷新了,而须要等到32ms时刷新,这就是丢帧了。丢帧越多,给用户的感受就越卡顿。github
正常流畅刷新图示: 算法
哎呀!丢帧啦。卡顿图示: 数组
在说原理以前,咱们先来了解几个概念:bash
主线程ActivityThread:严格来讲,UI主线程不是ActivityThread。ActivityThread类是Android APP进程的初始类,它的main函数是这个APP进程的入口。APP进程中UI事件的执行代码段都是由ActivityThread提供的。也就是说,Main Thread实例是存在的,只是建立它的代码咱们不可见。ActivityThread的main函数就是在这个Main Thread里被执行的。这个主线程会建立一个Looper(Looper.prepare),而Looper又会关联一个MessageQueue,主线程Looper会在应用的生命周期内不断轮询(Looper.loop),从MessageQueue取出Message 更新UI。网络
// ActivityThread类:
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
...
Looper.prepareMainLooper();
...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
// Looper开始轮询
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
复制代码
Vsync信号:屏幕的刷新过程是每一行从左到右(行刷新,水平刷新,Horizontal Scanning),从上到下(屏幕刷新,垂直刷新,Vertical Scanning)。当整个屏幕刷新完毕,即一个垂直刷新周期完成,会有短暂的空白期,此时发出 VSync 信号。因此,VSync 中的 V指的是垂直刷新中的垂直-Vertical。Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,VSync是Vertical Synchronization(垂直同步)的缩写,是一种在PC上很早就普遍使用的技术,能够简单的把它认为是一种定时中断。而在Android 4.1(JB)中已经开始引入VSync机制,用来同步渲染,让App的UI和SurfaceFlinger能够按硬件产生的VSync节奏进行工做。并发
界面刷新:界面上任何一个 View 的刷新请求最终都会走到 ViewRootImpl 中的 scheduleTraversals() 里来安排一次遍历绘制 View 树的任务;而且经过源码均可以知道全部的界面刷新(包括Vsync信号触发的),都会经过Choreographer 的 postCallback() 方法,将界面刷新这个 Runnable 任务以当前事件放进一个待执行的队列里,最后经过主线程的Looper的loop方法取出消息并执行。app
// Looper类:
public static void loop() {
final Looper me = myLooper();
...
// 获取当前Looper的消息队列
final MessageQueue queue = me.mQueue;
...
for (; ; ) {
// 取出一个消息
Message msg = queue.next(); // might block
...
// "此mLogging可经过Looper.getMainLooper().setMessageLogging方法设置自定义"
final Printer logging = me.mLogging;
if (logging != null) {// 消息处理前
// "若mLogging不为null,则此处可回调到该类的println方法"
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
...
try {
// 消息处理
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
if (logging != null) {// 消息处理后
// "消息处理后,也可调用logging的println方法"
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
...
}
}
复制代码
卡顿发生点:从第3条中,咱们能够看出,全部消息最终都通过dispatchMessage方法。所以界面的卡顿最终都应该是发生在Handler的dispatchMessage里。
// Handler类:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
复制代码
屏幕刷新机制,具体可参考:Android 屏幕刷新机制
Handler消息机制和View的绘制机制,具体可参考:Handler机制和View绘制流程源码分析
原理: 上面几个概念中,其实里面已包含卡顿监控的原理啦。咱们在界面刷新中Looper的loop方法注释中声明:"若mLogging不为null,则此处可回调到该类的println方法",所以咱们能够经过自定义的mLogging(实际为Printer接口的子类),实现Printer接口的println方法,而后在println方法中监控是否有卡顿发生。从loop方法中,能够看出logging.println调用是成对出现的,会在消息处理先后分别调用,所以能够在自定义的println方法中经过标识来分辨是消息处理前/后,经过计算时间差与咱们本身设置的阀值(咱们认为消息处理的最长时间,即卡顿的临界值)比对,来监控咱们的程序是否发生卡顿。
官方原理介绍示例图:
// Application中
// 卡顿优化
// 指定的卡顿阀值为500毫秒——provideBlockThreshold()方法;可在onBlock方法处收集堆栈信息
BlockCanary.install(this, new AppBlockCanaryContext()).start();
/**
* BlockCanary配置的各类信息(部分)
*/
public class AppBlockCanaryContext extends BlockCanaryContext {
// 实现各类上下文,包括应用标识符,用户uid,网络类型,卡顿判断阀值,Log保存位置等
/**
* 指定的卡顿阀值 500毫秒
*/
public int provideBlockThreshold() {
return 500;
}
/**
* 保存日志的路径
*/
public String providePath() {
return "/blockcanary/";
}
/**
* 是否须要在通知栏通知卡顿
*/
public boolean displayNotification() {
return true;
}
/**
* 此处可收集堆栈信息,以备上传分析
* Block interceptor, developer may provide their own actions.
*/
public void onBlock(Context context, BlockInfo blockInfo) {
Log.i("lz","blockInfo "+blockInfo.toString());
// 获取当前执行方法的调用栈信息
// String trace = Log.getStackTraceString(new Throwable());
}
复制代码
AppBlockCanaryContext具体配置可参考:AppBlockCanaryContext.java
public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) {
// 将上下文和咱们自定义的blockCanaryContext传入
BlockCanaryContext.init(context, blockCanaryContext);
// 根据displayNotification()设置是否启用或者禁用DisplayActivity组件
setEnabled(context, DisplayActivity.class, BlockCanaryContext.get().displayNotification());
// 返回单例BlockCanary
return get();
}
复制代码
咱们能够看到install方法中干了3件事情,咱们分别来分析一下。
// BlockCanaryContext类:
private static Context sApplicationContext;
private static BlockCanaryContext sInstance = null;
static void init(Context context, BlockCanaryContext blockCanaryContext) {
sApplicationContext = context;
// 将咱们自定义的blockCanaryContext类,保存在BlockCanaryContext类的成员变量sInstance中
sInstance = blockCanaryContext;
}
复制代码
第一步,实际上就是在咱们使用BlockCanary时,将咱们自定义的AppBlockCanaryContext保存在BlockCanaryContext类的成员变量sInstance中,以供BlockCanary能够经过sInstance,来使用咱们自已配置的各类信息(包括应用标识符,用户uid,网络类型,卡顿判断阀值,Log保存位置等)。
// BlockCanaryContext类:
public static BlockCanaryContext get() {
if (sInstance == null) {
throw new RuntimeException("BlockCanaryContext null");
} else {
return sInstance;
}
}
// BlockCanary类:
// 调用newSingleThreadExecutor初始化文件IO线程池
private static final Executor fileIoExecutor = newSingleThreadExecutor("File-IO");
private static void setEnabledBlocking(Context appContext,
Class<?> componentClass,
boolean enabled) {
// 初始化组件对象
ComponentName component = new ComponentName(appContext, componentClass);
// 获取包管理者
PackageManager packageManager = appContext.getPackageManager();
int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
// 动态不杀死应用启用或者禁用组件,若enabled为true则启用,不然禁用
packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
}
private static void executeOnFileIoThread(Runnable runnable) {
fileIoExecutor.execute(runnable);
}
private static Executor newSingleThreadExecutor(String threadName) {
return Executors.newSingleThreadExecutor(new SingleThreadFactory(threadName));
}
private static void setEnabled(Context context,
final Class<?> componentClass,
final boolean enabled) {
final Context appContext = context.getApplicationContext();
executeOnFileIoThread(new Runnable() {
@Override
public void run() {
setEnabledBlocking(appContext, componentClass, enabled);
}
});
}
复制代码
从上述代码中,能够看出来setEnabled方法,经过参数:BlockCanaryContext.get().displayNotification(),来设置DisplayActivity组件(用于显示记录的异常信息给开发者)是否启用。
public static BlockCanary get() {
if (sInstance == null) {
synchronized (BlockCanary.class) {
if (sInstance == null) {
sInstance = new BlockCanary();
}
}
}
return sInstance;
}
private BlockCanary() {
// 将BlockCanaryContext.get(),即sInstance(咱们自定义的AppBlockCanaryContext)
// 设置到BlockCanary核心类BlockCanaryInternals中,用来获取咱们自定义配置的信息
BlockCanaryInternals.setContext(BlockCanaryContext.get());
// 初始化BlockCanaryInternals
mBlockCanaryCore = BlockCanaryInternals.getInstance();
// 添加拦截器(将自定义的AppBlockCanaryContext添加到拦截器中,可回调其onBlock方法)
mBlockCanaryCore.addBlockInterceptor(BlockCanaryContext.get());
// 根据咱们自定义的AppBlockCanaryContext获取是否展现通知,默认为true
if (!BlockCanaryContext.get().displayNotification()) {
return;
}
// 若容许展现通知,则将DisplayService继续添加到拦截器中
mBlockCanaryCore.addBlockInterceptor(new DisplayService());
}
复制代码
咱们从这部分源码中看到,BlockCanary的构造方法中完成了其核心类:BlockCanaryInternals的初始化与设置(包括sInstance传入和添加拦截器),那么咱们再来看一看BlockCanaryInternals的初始化都有些什么操做:
// BlockCanaryInternals类:
static BlockCanaryInternals getInstance() {
if (sInstance == null) {
synchronized (BlockCanaryInternals.class) {
if (sInstance == null) {
sInstance = new BlockCanaryInternals();
}
}
}
return sInstance;
}
public BlockCanaryInternals() {
// 初始化堆栈采样器
stackSampler = new StackSampler(
Looper.getMainLooper().getThread(),
sContext.provideDumpInterval());
// 初始化cpu采样器
cpuSampler = new CpuSampler(sContext.provideDumpInterval());
// 设置监视器,传入LooperMonitor looper监控器
// LooperMonitor 实际上就是咱们上面【BlockCanary原理】中讲到的Printer接口的子类
setMonitor(new LooperMonitor(new LooperMonitor.BlockListener() {
@Override
public void onBlockEvent(long realTimeStart, long realTimeEnd,
long threadTimeStart, long threadTimeEnd) {
// Get recent thread-stack entries and cpu usage
ArrayList<String> threadStackEntries = stackSampler
.getThreadStackEntries(realTimeStart, realTimeEnd);
if (!threadStackEntries.isEmpty()) {
BlockInfo blockInfo = BlockInfo.newInstance()
.setMainThreadTimeCost(realTimeStart, realTimeEnd, threadTimeStart,
threadTimeEnd)
.setCpuBusyFlag(cpuSampler.isCpuBusy(realTimeStart, realTimeEnd))
.setRecentCpuRate(cpuSampler.getCpuRateInfo())
.setThreadStackEntries(threadStackEntries)
.flushString();
// 卡顿日志记录
LogWriter.save(blockInfo.toString());
if (mInterceptorChain.size() != 0) {
// 遍历全部拦截器成员,调用每一个成员的onBlock,并将卡顿信息传入
for (BlockInterceptor interceptor : mInterceptorChain) {
interceptor.onBlock(getContext().provideContext(), blockInfo);
}
}
}
}
}, getContext().provideBlockThreshold(), getContext().stopWhenDebugging()));
LogWriter.cleanObsolete();
}
private void setMonitor(LooperMonitor looperPrinter) {
// setMonitor把建立的LooperMonitor赋值给BlockCanaryInternals的成员变量monitor。
monitor = looperPrinter;
}
复制代码
BlockCanaryInternals的构造方法中,初始化了几个变量,包括:堆栈采样器、cpu采样器、looper监控器,以及looper监控器的回调方法onBlockEvent。
public void start() {
if (!mMonitorStarted) {
mMonitorStarted = true;
// 设置Looper中的mLogging,每次消息处理先后,
// 均可回调自定义的实现Printer接口LooperMonitor类的println方法
Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
}
}
复制代码
将在BlockCanaryInternals中建立的LooperMonitor给主线程Looper的mLogging变量赋值。这样主线程Looper就能够消息分发先后使用LooperMonitor#println输出日志。此时BlockCanary已经开始监控卡顿状况,因此咱们如今须要关注的就是LooperMonitor的println方法。
再回顾一下Looper的loop方法:
//Looper
for (;;) {
Message msg = queue.next();
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
...
}
复制代码
Lopper的loop方法中logging如今就是BlockCanary中实现了Printer接口的LooperMonitor类。
// LooperMonitor类:
private boolean mPrintingStarted = false;
@Override
public void println(String x) {
if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
return;
}
if (!mPrintingStarted) {
// 获取消息处理前系统当前时间
mStartTimestamp = System.currentTimeMillis();
// 获取当前线程运行时间
mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
// 将此标识置为true,下此进入就是消息处理以后
mPrintingStarted = true;
// 开始获取堆栈信息
startDump();
} else {
// 获取消息处理后系统当前时间
final long endTime = System.currentTimeMillis();
// 将此标识置为true,下此进入就是下一条消息处理以前
mPrintingStarted = false;
// 判断是否发生卡顿
if (isBlock(endTime)) {
// 发生卡顿,通知卡顿事件发生
notifyBlockEvent(endTime);
}
// 中止获取堆栈信息
stopDump();
}
}
复制代码
对于每个Message消息而言,println方法都是按顺序成对出现的,所以根据mPrintingStarted是不是消息开始前的标识,来判断此消息当前的处理先后两种状态。下面咱们来看一下卡顿发生的状况:
// LooperMonitor类:
private boolean isBlock(long endTime) {
// 判断消息执行时间是否超过阈值
return endTime - mStartTimestamp > mBlockThresholdMillis;
}
// 若超过阀值,则通知卡顿事件
private void notifyBlockEvent(final long endTime) {
final long startTime = mStartTimestamp;
final long startThreadTime = mStartThreadTimestamp;
// 获取消息处理结束后线程运行时间
final long endThreadTime = SystemClock.currentThreadTimeMillis();
// HandlerThreadFactory异步线程Looper的Handler
HandlerThreadFactory.getWriteLogThreadHandler().post(new Runnable() {
@Override
public void run() {
// 异步线程中执行onBlockEvent回调
mBlockListener.onBlockEvent(startTime, endTime, startThreadTime, endThreadTime);
}
});
}
复制代码
经过消息执行的先后时间差 - 咱们自定义AppBlockCanaryContext中设置的卡顿阀值,来肯定是否发生卡顿,卡顿后的回调消息是在设置为异步线程Looper的Handler中执行。
// BlockCanaryInternals类构造方法中:
setMonitor(new LooperMonitor(new LooperMonitor.BlockListener() {
@Override
public void onBlockEvent(long realTimeStart, long realTimeEnd,
long threadTimeStart, long threadTimeEnd) {
// 根据开始及结束时间,从堆栈采集器的map当中获取记录信息
ArrayList<String> threadStackEntries = stackSampler
.getThreadStackEntries(realTimeStart, realTimeEnd);
if (!threadStackEntries.isEmpty()) {
// 构建 BlockInfo对象,设置相关的信息
BlockInfo blockInfo = BlockInfo.newInstance()
.setMainThreadTimeCost(realTimeStart, realTimeEnd, threadTimeStart, threadTimeEnd)
.setCpuBusyFlag(cpuSampler.isCpuBusy(realTimeStart, realTimeEnd))
.setRecentCpuRate(cpuSampler.getCpuRateInfo())
.setThreadStackEntries(threadStackEntries)
.flushString();
// 记录信息
LogWriter.save(blockInfo.toString());
// 遍历拦截器,通知
if (mInterceptorChain.size() != 0) {
for (BlockInterceptor interceptor : mInterceptorChain) {
interceptor.onBlock(getContext().provideContext(), blockInfo);
}
}
}
}
}, getContext().provideBlockThreshold(), getContext().stopWhenDebugging()));
复制代码
最后若拦截器成员中存在DisplayService,则会发送前台的通知,代码以下:
// DisplayService类:
@Override
public void onBlock(Context context, BlockInfo blockInfo) {
Intent intent = new Intent(context, DisplayActivity.class);
intent.putExtra("show_latest", blockInfo.timeStart);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 1, intent, FLAG_UPDATE_CURRENT);
String contentTitle = context.getString(R.string.block_canary_class_has_blocked, blockInfo.timeStart);
String contentText = context.getString(R.string.block_canary_notification_message);
// 根据不一样的sdk兼容全部版本的通知栏显示
show(context, contentTitle, contentText, pendingIntent);
}
复制代码
// LooperMonitor类:
private void startDump() {
if (null != BlockCanaryInternals.getInstance().stackSampler) {
// 开始记录堆栈信息
BlockCanaryInternals.getInstance().stackSampler.start();
}
if (null != BlockCanaryInternals.getInstance().cpuSampler) {
// 开始记录cpu信息
BlockCanaryInternals.getInstance().cpuSampler.start();
}
}
private void stopDump() {
if (null != BlockCanaryInternals.getInstance().stackSampler) {
// 中止记录堆栈信息
BlockCanaryInternals.getInstance().stackSampler.stop();
}
if (null != BlockCanaryInternals.getInstance().cpuSampler) {
// 中止记录cpu信息
BlockCanaryInternals.getInstance().cpuSampler.stop();
}
}
public void start() {
// mShouldSample其实是AtomicBoolean原子布尔值。
if (mShouldSample.get()) {
return;
}
// 原子布尔值,可以保证在高并发的状况下只有一个线程可以访问这个属性值。
// 原子布尔值具体详情,参考:https://www.jianshu.com/p/8a44d4a819bc
mShouldSample.set(true);
// 移除上一次任务
HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
// 延迟 卡顿阀值*0.8 的时间执行相应信息的收集
HandlerThreadFactory.getTimerThreadHandler().postDelayed(mRunnable,
BlockCanaryInternals.getInstance().getSampleDelay());
}
public void stop() {
if (!mShouldSample.get()) {
return;
}
mShouldSample.set(false);
// 移除任务
HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
}
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
// 调用doSample方法,执行相应操做
doSample();
// 若此原子布尔值为true,即此时为开始记录堆栈信息
if (mShouldSample.get()) {
// 延迟 卡顿阀值 时间执行任务
HandlerThreadFactory.getTimerThreadHandler()
.postDelayed(mRunnable, mSampleInterval);
}
}
};
// BlockCanaryInternals类:
long getSampleDelay() {
// 卡顿阀值的0.8
return (long) (BlockCanaryInternals.getContext().provideBlockThreshold() * 0.8f);
}
复制代码
卡顿信息的记录,其实是经过CpuSampler和StackSampler二者相同父类AbstractSampler类,提供的方法start和stop记录,而start方法中经过HandlerThreadFactory获取异步的TimerThreadHandler发送延时消息,最后分别调用CpuSampler类和StackSampler类中,继承自AbstractSampler抽象方法doSample()完成的卡顿信息的记录。下面分别看一下CpuSampler类和StackSampler类的doSample()方法的实现。
StackSampler类的doSample()方法
private static final LinkedHashMap<Long, String> sStackMap = new LinkedHashMap<>();
@Override
protected void doSample() {
StringBuilder stringBuilder = new StringBuilder();
// mCurrentThread.getStackTrace():返回一个表示该线程堆栈转储的堆栈跟踪元素数组。
// 经过mCurrentThread.getStackTrace()获取StackTraceElement,加入到StringBuilder
for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) {
stringBuilder
.append(stackTraceElement.toString())
.append(BlockInfo.SEPARATOR);
}
synchronized (sStackMap) {
// Lru算法,控制LinkHashMap的长度,移除最先添加进来的数据
if (sStackMap.size() == mMaxEntryCount && mMaxEntryCount > 0) {
sStackMap.remove(sStackMap.keySet().iterator().next());
}
// 以当前系统时间为key,存储此处的堆栈信息
sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());
}
}
复制代码
CpuSampler类的doSample()方法
// 主要经过获取/proc/stat文件 去获取cpu的信息
@Override
protected void doSample() {
BufferedReader cpuReader = null;
BufferedReader pidReader = null;
try {
// 经过bufferReader读取 /proc 下的cpu文件
cpuReader = new BufferedReader(new InputStreamReader(
new FileInputStream("/proc/stat")), BUFFER_SIZE);
String cpuRate = cpuReader.readLine();
if (cpuRate == null) {
cpuRate = "";
}
if (mPid == 0) {
mPid = android.os.Process.myPid();
}
// 经过bufferReader读取 /proc 下的内存文件
pidReader = new BufferedReader(new InputStreamReader(
new FileInputStream("/proc/" + mPid + "/stat")), BUFFER_SIZE);
String pidCpuRate = pidReader.readLine();
if (pidCpuRate == null) {
pidCpuRate = "";
}
parse(cpuRate, pidCpuRate);
} catch (Throwable throwable) {
Log.e(TAG, "doSample: ", throwable);
} finally {
try {
if (cpuReader != null) {
cpuReader.close();
}
if (pidReader != null) {
pidReader.close();
}
} catch (IOException exception) {
Log.e(TAG, "doSample: ", exception);
}
}
}
private void parse(String cpuRate, String pidCpuRate) {
String[] cpuInfoArray = cpuRate.split(" ");
if (cpuInfoArray.length < 9) {
return;
}
long user = Long.parseLong(cpuInfoArray[2]);
long nice = Long.parseLong(cpuInfoArray[3]);
long system = Long.parseLong(cpuInfoArray[4]);
long idle = Long.parseLong(cpuInfoArray[5]);
long ioWait = Long.parseLong(cpuInfoArray[6]);
long total = user + nice + system + idle + ioWait
+ Long.parseLong(cpuInfoArray[7])
+ Long.parseLong(cpuInfoArray[8]);
String[] pidCpuInfoList = pidCpuRate.split(" ");
if (pidCpuInfoList.length < 17) {
return;
}
long appCpuTime = Long.parseLong(pidCpuInfoList[13])
+ Long.parseLong(pidCpuInfoList[14])
+ Long.parseLong(pidCpuInfoList[15])
+ Long.parseLong(pidCpuInfoList[16]);
if (mTotalLast != 0) {
StringBuilder stringBuilder = new StringBuilder();
long idleTime = idle - mIdleLast;
long totalTime = total - mTotalLast;
stringBuilder
.append("cpu:")
.append((totalTime - idleTime) * 100L / totalTime)
.append("% ")
.append("app:")
.append((appCpuTime - mAppCpuTimeLast) * 100L / totalTime)
.append("% ")
.append("[")
.append("user:").append((user - mUserLast) * 100L / totalTime)
.append("% ")
.append("system:").append((system - mSystemLast) * 100L / totalTime)
.append("% ")
.append("ioWait:").append((ioWait - mIoWaitLast) * 100L / totalTime)
.append("% ]");
synchronized (mCpuInfoEntries) {
mCpuInfoEntries.put(System.currentTimeMillis(), stringBuilder.toString());
if (mCpuInfoEntries.size() > MAX_ENTRY_COUNT) {
for (Map.Entry<Long, String> entry : mCpuInfoEntries.entrySet()) {
Long key = entry.getKey();
mCpuInfoEntries.remove(key);
break;
}
}
}
}
mUserLast = user;
mSystemLast = system;
mIdleLast = idle;
mIoWaitLast = ioWait;
mTotalLast = total;
mAppCpuTimeLast = appCpuTime;
}
复制代码
Android平台CPU的一些常识:
> cat /proc/stat
1. cpu 2255 34 2290 22625563 6290 127 456
2. cpu0 1132 34 1441 11311718 3675 127 438
3. cpu1 1123 0 849 11313845 2614 0 18
4. intr 114930548 113199788 3 0 5 263 0 4 [... lots more numbers ...]
5. ctxt 1990473
6. btime 1062191376
7. processes 2915
8. procs_running 1
9. procs_blocked 0
复制代码
这些数字指明了CPU执行不一样的任务所消耗的时间(从系统启动开始累计到当前时刻)。时间单位是USER_HZ或jiffies(一般是百分之一秒)。参数 解析 (如下数值都是从系统启动累计到当前时刻)
user (38082) 处于用户态的运行时间,不包含 nice值为负进程
nice (627) nice值为负的进程所占用的CPU时间
system (27594) 处于核心态的运行时间
idle (893908) 除IO等待时间之外的其它等待时间iowait (12256) 从系统启动开始累计到当前时刻,IO等待时间
irq (581) 硬中断时间
irq (581) 软中断时间
stealstolen(0) 一个其余的操做系统运行在虚拟环境下所花费的时间
guest(0) 这是在Linux内核控制下为客户操做系统运行虚拟CPU所花费的时间
复制代码
总结:总的CPU时间totalCpuTime = user + nice + system + idle + iowait + irq + softirq + stealstolen + guestcat /proc/6873/stat
6873 (a.out) R 6723 6873 6723 34819 6873 8388608 77 0 0 0 41958 31 0 0 25 0 3 0 5882654 1409024 56 4294967295 134512640 134513720 3215579040 0 2097798 0 0 0 0 0 0 0 17 0 0 0
复制代码
计算CPU使用率有用相关参数:参数 解析
pid=6873 进程号
utime=1587 该任务在用户态运行的时间,单位为jiffies
stime=41958 该任务在核心态运行的时间,单位为jiffies
cutime=0 全部已死线程在用户态运行的时间,单位为jiffies
cstime=0 全部已死在核心态运行的时间,单位为jiffies
复制代码
结论:进程的总CPU时间processCpuTime = utime + stime + cutime + cstime,该值包括其全部线程的CPU时间。卡顿发生时,会回调LooperMonitor的onBlockEvent方法,而此方法中,会将卡顿信息写入本地日志文件,日志的路径在自定义的AppBlockCanaryContext中定义。
// BlockCanaryInternals类构造方法中:
setMonitor(new LooperMonitor(new LooperMonitor.BlockListener() {
@Override
public void onBlockEvent(long realTimeStart, long realTimeEnd,
long threadTimeStart, long threadTimeEnd) {
ArrayList<String> threadStackEntries = stackSampler
.getThreadStackEntries(realTimeStart, realTimeEnd);
if (!threadStackEntries.isEmpty()) {
BlockInfo blockInfo = BlockInfo.newInstance()
.setMainThreadTimeCost(realTimeStart, realTimeEnd, threadTimeStart, threadTimeEnd)
.setCpuBusyFlag(cpuSampler.isCpuBusy(realTimeStart, realTimeEnd))
.setRecentCpuRate(cpuSampler.getCpuRateInfo())
.setThreadStackEntries(threadStackEntries)
.flushString();
// 日志保存
LogWriter.save(blockInfo.toString());
if (mInterceptorChain.size() != 0) {
for (BlockInterceptor interceptor : mInterceptorChain) {
interceptor.onBlock(getContext().provideContext(), blockInfo);
}
}
}
}
}, getContext().provideBlockThreshold(), getContext().stopWhenDebugging()));
// LogWriter类:
public static String save(String str) {
String path;
synchronized (SAVE_DELETE_LOCK) {
path = save("looper", str);
}
return path;
}
private static String save(String logFileName, String str) {
String path = "";
BufferedWriter writer = null;
try {
// 根据开发者本身配置的日志存储路径,生成文件
File file = BlockCanaryInternals.detectedBlockDirectory();
long time = System.currentTimeMillis();
path = file.getAbsolutePath() + "/"
+ logFileName + "-"
+ FILE_NAME_FORMATTER.format(time) + ".log";
// 写入卡顿信息
OutputStreamWriter out =
new OutputStreamWriter(new FileOutputStream(path, true), "UTF-8");
writer = new BufferedWriter(out);
writer.write(BlockInfo.SEPARATOR);
writer.write("**********************");
writer.write(BlockInfo.SEPARATOR);
writer.write(TIME_FORMATTER.format(time) + "(write log time)");
writer.write(BlockInfo.SEPARATOR);
writer.write(BlockInfo.SEPARATOR);
writer.write(str);
writer.write(BlockInfo.SEPARATOR);
writer.flush();
writer.close();
writer = null;
} catch (Throwable t) {
Log.e(TAG, "save: ", t);
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (Exception e) {
Log.e(TAG, "save: ", e);
}
}
return path;
}
复制代码
至此,BlockCanary的总体已分析完成,收工咯。
...
注:如有什么地方阐述有误,敬请指正。期待您的点赞哦!!!