BlockCanary原理解析(Android端UI卡顿监测工具)
Android在效率这一块是一个需要长期优化的点,那么就需要依赖很多的第三方库以及工具,这次就从BlockCanary这个卡顿监测工具开始
1、BlockCanary的使用
引用
dependencies { compile 'com.github.markzhai:blockcanary-android:1.5.0'}
在应用的application中完成初始化
public class DemoApplication extends Application { @Override public void onCreate() { super.onCreate(); BlockCanary.install(this, new AppContext()).start(); }} //参数设置public class AppContext extends BlockCanaryContext { private static final String TAG = "AppContext"; @Override public String provideQualifier() { String qualifier = ""; try { PackageInfo info = DemoApplication.getAppContext().getPackageManager() .getPackageInfo(DemoApplication.getAppContext().getPackageName(), 0); qualifier += info.versionCode + "_" + info.versionName + "_YYB"; } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "provideQualifier exception", e); } return qualifier; } @Override public int provideBlockThreshold() { return 500; } @Override public boolean displayNotification() { return BuildConfig.DEBUG; } @Override public boolean stopWhenDebugging() { return false; }}
2、核心原理
我们都知道Android应用程序只有一个主线程ActivityThread,这个主线程会创建一个Looper(Looper.prepare),而Looper又会关联一个MessageQueue,主线程Looper会在应用的生命周期内不断轮询(Looper.loop),从MessageQueue取出Message 更新UI。
那么我们需要监听所有UI的地方就在这个Lopper.loop()方法里面了,我们来看一下
/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */ public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger final Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs; final long traceTag = me.mTraceTag; if (traceTag != 0 && Trace.isTagEnabled(traceTag)) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); final long end; try { msg.target.dispatchMessage(msg); end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } if (slowDispatchThresholdMs > 0) { final long time = end - start; if (time > slowDispatchThresholdMs) { Slog.w(TAG, "Dispatch took " + time + "ms on " + Thread.currentThread().getName() + ", h=" + msg.target + " cb=" + msg.callback + " msg=" + msg.what); } } if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } msg.recycleUnchecked(); } }
我们可以看到,主要的执行主线程逻辑的代码都在logging.println两句的中间,那么我们是不是只需要重写了logging.println方法,是不是就可以监测到他这一次花了多少事件了?
那么如何来重写呢?重点就在这里了
Looper.getMainLooper().setMessageLogging(new Printer() { @Override public void println(String x) { }});
知道了这个之后,其实就只剩两件事了
1、写好计算使用了多少事件执行完毕的逻辑
2、写好获取一系列信息的放啊
1这个很好写,我们甚至不需要看BlockCanary的源码最粗暴的方式就是
Looper.getMainLooper().setMessageLogging(new Printer() { @Override public void println(String x) { if(x.contains(">>>>> Dispatching")){ //这是开始 } if(x.contains("<<<<< Finished to")){ //这是结束 } } });
我们来看看BlockCanary是怎么做的
从他的初始化方法开始
1、install
public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) { BlockCanaryContext.init(context, blockCanaryContext); setEnabled(context, DisplayActivity.class, BlockCanaryContext.get().displayNotification()); return get();} 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); } });} 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; // Blocks on IPC. packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);}
- BlockCanaryContext.init会将保存应用的applicationContext和用户设置的配置参数;
- setEnabled将根据用户的通知栏消息配置开启(displayNotification=true)或关闭(displayNotification=false)DisplayActivity (DisplayActivity是承载通知栏消息的activity)
public static BlockCanary get() { if (sInstance == null) { synchronized (BlockCanary.class) { if (sInstance == null) { sInstance = new BlockCanary(); } } } return sInstance;}//私有构造函数private BlockCanary() { BlockCanaryInternals.setContext(BlockCanaryContext.get()); mBlockCanaryCore = BlockCanaryInternals.getInstance(); mBlockCanaryCore.addBlockInterceptor(BlockCanaryContext.get()); if (!BlockCanaryContext.get().displayNotification()) { return; } mBlockCanaryCore.addBlockInterceptor(new DisplayService()); }
- 单例创建BlockCanary
- 核心处理类为BlockCanaryInternals
- 为BlockCanaryInternals添加拦截器(责任链)
- BlockCanaryContext对BlockInterceptor是空实现,可以忽略;
- DisplayService只在开启通知栏消息的时候添加,当卡顿发生时将通过DisplayService发起通知栏消息
看看核心类BlockCanaryInternals的初始化过程
public BlockCanaryInternals() { stackSampler = new StackSampler( Looper.getMainLooper().getThread(), sContext.provideDumpInterval()); cpuSampler = new CpuSampler(sContext.provideDumpInterval()); 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 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.cleanObsolete();}
这里找到了我们需要的继承了Printer接口的对象LooperMonitor,以及两个采样类StackSampler和CpuSampler,即线程堆栈采样和CPU采样,通过调用setMonitor把创建的LooperMonitor赋值给BlockCanaryInternals的成员变量monitor
先来看看LooperMonitor的print方法
public void println(String x) { if (mStopWhenDebugging && Debug.isDebuggerConnected()) { return; } if (!mPrintingStarted) { mStartTimestamp = System.currentTimeMillis(); mStartThreadTimestamp = SystemClock.currentThreadTimeMillis(); mPrintingStarted = true; startDump(); } else { final long endTime = System.currentTimeMillis(); mPrintingStarted = false; if (isBlock(endTime)) { notifyBlockEvent(endTime); } stopDump(); } }
嗯,的确是跟我们自己写的差不多的意思,
不过还没有设置到Looper对象中,那么继续看第二个方法
2、start
public void start() { if (!mMonitorStarted) { mMonitorStarted = true; Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor); }}
好吧,这个方法直接就看到了设置
然后剩下的就是收集当前卡顿的点的一些细节信息了,比如当前执行的是什么方法等等,不过这一块目前就先不看了,因为主要的逻辑已经理解了,其他的细节的之后再说
更多相关文章
- IOS多线程开发之GCD
- 数据更新后让ListView自动滚动到底部
- Android(安卓)使用Intent传递对象
- Android(安卓)模拟器之 Market 安装
- android开发教程之listview使用方法
- 生命周期组件 Lifecycle 源码解析(一)
- Android(安卓)setTag方法定义key的问题
- Android(安卓)8.0 dexopt记录
- Android(安卓)4.4 Kitkat Phone工作流程浅析(五)__MT(来电)流程