BlockCanary源码分析
来源:互联网 发布:javascript静态网页 编辑:程序博客网 时间:2024/06/05 07:03
BlockCanary是一个Android平台的非侵入式的性能监控组件,应用只需要实现一个抽象类,提供一些该组件需要的上下文环境,就可以在平时使用应用的时候检测主线程上的各种卡慢问题,并通过组件提供的各种信息分析出原因并进行修复。(作者原述)
很敬佩作者能够写出这么高质量的代码,BlockCanary的代码设计简洁明了,结构清晰,从第一行代码install走下去,很快就能了解到整个框架的思路。去网上搜了下作者,发现作者12年毕业,自己很汗颜。
本文主要介绍BlockCanary的实现原理和源码设计。
项目地址
https://github.com/markzhai/AndroidPerformanceMonitor
设计思路
首先我们确定一个事实,所有的UI操作都要经过Looper和Handler的处理。如图所示。
ActivityThread是个android私有类,在应用层无法直接访问(通过反射可以访问),Handler是一个应用层经常自定义的类。Looper是个公开类,而且被final修饰,所以无法被继承,这样就给了应用层分析的机会。下面是Looper.loop的代码:
从代码中可以看到,在dispatchMessage的前后,Looper都做了一次logging的判断,进行log的打印。非常幸运的是,android提供了一个公开的接口,允许各app自定义这个Printer。该接口是:
大家应该都知道主线程默认会生成一个Looper,这个Looper其实就是在ActivityThread中被创建的,在创建之后,也写了setMessageLogging的代码,只不过if(false)了。
BlockCanary的关键代码就在Looper. setMessageLogging上,通过注册自己的Printer,得到UI事件的开始处理时间和结束处理时间,若时间超过block阈值,则提取主线程的调用栈信息和cpu使用信息。
下图是作者绘制的流程图,非常明了的展示了BlockCanary的工作方式。一个额外注意的地方就是在dispatchMessage的同时,会执行一个计时器,如果超过了某个阈值,就会后台dump主线程的调用栈和cpu使用信息、内存使用信息。
接下来就开始代码分析吧。
源码分析
BlockCanary号称APP一行代码即可搞定。
就从BlockCanary.install这个方法开始吧。
BlockCanary.install
public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) { BlockCanaryContext.init(context, blockCanaryContext); setEnabled(context, DisplayActivity.class, BlockCanaryContext.get().displayNotification()); return get();}
该方法包括三个步骤
1. BlockCanaryContext.init(context, blockCanaryContext);。就是把app设置的参数传递给BlockCanaryContext。
2. setEnabled(context, DisplayActivity.class, BlockCanaryContext.get().displayNotification()); DisplayActivity是用来展示block信息的界面,该方法就是根据app设置是否显示通知来设置该activity是否展示。通常在debug的时候为true,release的时候为false。
3. get()。使用懒汉的单例模式,生成BlockCanary实例.
BlockCanary()
private BlockCanary() { BlockCanaryInternals.setContext(BlockCanaryContext.get()); mBlockCanaryCore = BlockCanaryInternals.getInstance(); mBlockCanaryCore.addBlockInterceptor(BlockCanaryContext.get()); if (!BlockCanaryContext.get().displayNotification()) { return; } mBlockCanaryCore.addBlockInterceptor(new DisplayService());}
BlockCanaryInternals是BlockCanary的内部实现,在这个类中创建了Looper的printer。mBlockCanaryCore是其单例对象。代码当中,还有两句addBlockInterceptor,该方法是注册两个block的拦截器,供UI或者APP额外处理block的事件。mBlockCanaryCore.addBlockInterceptor(BlockCanaryContext.get());是在APP中实现,
mBlockCanaryCore.addBlockInterceptor(new DisplayService())是BlockCanary的内部实现,用来发出block通知,调出DisplayActivity展示block信息。
BlockInterceptor的定义如下。
void onBlock(Context context, BlockInfo blockInfo);}
BlockCanaryInternals()
5. 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<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.cleanObsolete();}
这个方法做了四件事:
1. new StackSampler。该类是用来dump thread的stack信息。传递的参数是主线程和dump间隔。
2. new CpuSampler。该类是用来dump cpu的使用情况,传递的参数是dump间隔。
3. setMonitor。终于创建监听器了。到LooperMonitor类中,发现该类实现了Printer,并且实现了println方法。还记得文章开始提到的Looper.loop()中dispatchMessage前后的logging方法吗?没错,最终就会调用到LooperMonitor.println()中来。
4. onBlockEvent是在当block事件发生后,监听器要做的事情,包括:保存已经dump下来的主线程调用栈,cpu使用情况和内存使用情况以及监听的app的基本信息等;通知block拦截器分别处理各自的事情,也就是通知DisplayService去创建通知栏信息。
5. LogWriter.cleanObsolete(),删除过时的log。BlockCanary默认保存两天的log。
终于初始化完了。接下来就该启动监听了吧。还记得Application的那唯一一句代码吗?除了install,可千万别忘了后面还有个start呢。
Start monitor
public void start() { if (!mMonitorStarted) { mMonitorStarted = true; Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor); }}
这句代码可真是BlockCanary最关键的一句代码。mBlockCanaryCore.monitor就是刚才BlockCanaryInternals构造函数中setMonitor设置的。
监听器设置了,UI事件的响应可以监听了。接下来我们继续分析监听器的处理过程。需要详细分析LooperMonitor这个类。
LooperMonitor
先分析该类中最重要的一个方法:println。
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(); }}
可以看到这个方法的主要思想是:如果是dispatchMessage之前调用,则记录开始时间和startDump。mStartTimestamp表示的是当前系统时间,mStartThreadTimestamp表示的是当前线程纯运行时间。SystemClock.currentThreadTimeMillis()这个方法返回的时间并不是当前线程的已存活时间,而是当前线程处于运行状态的总时间。如果该线程执行过sleep或者wait,那么sleep和wait的时间是不记录到该方法里的。如果是dispatchMessage之后调用,则根据时间差判断是否block,如果block,则发送block事件,会调用到前面提到的onBlockEvent方法中,同时停止dump。isBlock和notifyBlockEvent的方法很简单,不在详述。
doDump
private void startDump() { if (null != BlockCanaryInternals.getInstance().stackSampler) { BlockCanaryInternals.getInstance().stackSampler.start(); } if (null != BlockCanaryInternals.getInstance().cpuSampler) { BlockCanaryInternals.getInstance().cpuSampler.start(); }}
还记得在BlockCanaryInternals的构造函数中创建的stackSampler和cpuSampler吗?该他们上场了。这两个类都继承AbstractSampler,区别在于doSample()上。本文主要讲这个方法。先看StackSampler的。
@Overrideprotected void doSample() { StringBuilder stringBuilder = new StringBuilder(); for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) { stringBuilder .append(stackTraceElement.toString()) .append(BlockInfo.SEPARATOR); } synchronized (sStackMap) { if (sStackMap.size() == mMaxEntryCount && mMaxEntryCount > 0) { sStackMap.remove(sStackMap.keySet().iterator().next()); } sStackMap.put(System.currentTimeMillis(), stringBuilder.toString()); }}
代码很简单,提取当前线程的调用栈。mCurrentThread其实就是主线程,因为在new StackSampler传递参数时,代码中用的是Looper.getMainLooper().getThread(),然后以当前时间戳为key放到sStackMap中。sStackMap是HashMap吗?是,但作者将他定义是一个LinkedHashMap。LinkedHashMap和HashMap的区别是:LinkedHashMap能够记录entry的插入顺序,而HashMap无法做到,所以当进行遍历时,LinkedHashMap是按照先插入后输出的顺序进行,而HashMap的顺序是未知的。在该场景下LinkedHashMap输出的内容基本上可以保证按时间戳从小到大排列。
接下来我们再看CpuSampler的doSample方法:
protected void doSample() { BufferedReader cpuReader = null; BufferedReader pidReader = null; try { 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(); } 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); } }}
这个方法就是读取/proc/下的两个stat内存文件。可以直接通过adb shell去查看这两个文件中的原始内容是什么。
/proc/stat的内容如下:
/proc/${pid}/stat的内容如下:
然后执行parse方法进行解析。
整个监听的过程中就分析完了,还有一些UI的代码本文就不做分析了。
- BlockCanary源码分析
- BlockCanary源码分析
- Android-BlockCanary框架源码分析
- 关于BlockCanary的源码分析
- [源码]BlockCanary学习笔记
- BlockCanary分析android卡顿
- BlockCanary分析android卡顿
- BlockCanary
- BlockCanary
- blockcanary UI卡顿优化框架源码解析
- Android UI卡顿监测框架BlockCanary原理分析
- BlockCanary简介
- 学习BlockCanary的实现
- BlockCanary检测ANR原因
- blockcanary原理理解
- 源码分析
- 源码分析
- 源码分析
- python打包为exe的简单操作
- 《C++多态的对象模型之单/多继承、菱形/菱形虚拟继承》
- 修改linux系统的最大打开文件数
- eclipse的基本快捷键
- 稳定排序和不稳定排序
- BlockCanary源码分析
- 面向对象思想的三种通俗解释
- 高可用集群之RHCS
- 554 DT:SPM 163 smtp3解决方案
- 关于DIV
- HDU4118:Holiday's Accommodation(思维 & dfs)
- 《数据库SQL实战》查找薪水涨幅超过15次的员工号emp_no以及其对应的涨幅次数t
- VPP启动
- 各种三角形的制作