BlockCanary分析android卡顿

在复杂的项目环境中,由于历史代码庞大,业务复杂,包含各种第三方库,所以在出现了卡顿的时候,我们很难定位到底是哪里出现了问题,即便知道是哪一个Activity/Fragment,也仍然需要进去里面一行一行看,动辄数千行的类再加上跳来跳去调来调去的,结果就是不了了之随它去了,实在不行了再优化吧。于是一拖再拖,最后可能压根就改不动了,客户端越来越卡。

Android应用卡顿是非常普遍的现象,偶尔出现ANR。只有当APP出现ANR,我们才能得到当前堆栈信息。当应用只是卡顿或只是不太流畅的时候,我们能不能找出卡顿元凶呢?不依赖Debug和源码的情况,能不能找出卡顿的堆栈信息呢?我们需要找到一种方法来检测哪些函数可能会使应用发生ANR,在开发阶段就能找出卡顿元凶,提高应用流畅度。

BlockCanary就是来解决这个问题的。告别打点,告别Debug,哪里卡顿,一目了然,让优化代码变得有的放矢。BlockCanary 地址:https://github.com/moduth/blockcanary

一. ANR原理

在Android中所有KeyEvent和TouchEvent都是按照先后顺序放入队列中,依次执行,并且只有当前一个事件执行完毕,才能开始执行下一个事件。每个正在执行的事件都被被保存在waitQueue中,执行完毕之后从waitQueue中移除。

当用户触发一个事件的时候,首先判断waitQueue是否为空,如果为空,可以立即响应该事件。如果队列不为空,说明还有事件没有执行完毕。判断当前时间和上一个事件响应时间是否大于超时时间(一般5秒,broadCastReceiver 10秒),如果超时,则会通过ActivityManagerService以弹窗的形式通知用户。

二. 卡顿(ANR)检测原理

ANR原理 可以知道,当一个事件处理时间超过阈值就会触发ANR。Android中所有事件(包括Activity,Service生命周期管理)都是通过Looper+MessageQueue+Handler来处理的。所有事件都被封装成Message,然后被放入MessageQueue中,Looper负责将Message交给对应的Handler去处理。

Looper是一个死循环,通过dispatchMessage分发Message到对应Handler中处理。我们可以近似认为dispatchMessage花费的时间就是每条消息处理时间。因此,我们只要记录每个Mesage dispatchMessage的执行时间,就可以得到事件花费的时间。

从Looper源码中我们发现,执行dispatchMessage前后都有一个logging打印,并且Looper提供了注册logging的方法。所有我们可以在MainThread Looper中注册一个logging,在每条消息dispatchMessage前后,都能收到一条打印记录。通过记录dispatchMessage之前的时间t1和dispatchMessage执行之后的log时间t2,totalTime = t2-t1得到该事件执行时间。

Looper.java

三. BlockCanary卡顿检测流程


BlockCanary启动一个线程负责保存UI线程当前堆栈信息,将堆栈信息以及CPU信息保存分别保存在 mThreadStackEntries和mCpuInfoEntries中,每条信息都以时间撮为key保存。

BlockCanary注册了logging来获取事件开始结束时间。如果检测到事件处理时间超过阈值(默认值1s),则从mThreadStackEntries中查找T1~T2这段时间内的堆栈信息,并且从mCpuInfoEntries中查找T1~T2这段时间内的CPU及内存信息。并且将信息格式化后保存到本地文件,并且通知用户。

四. BlockCanary使用

BlockCanary sdk在jcenter 中,目前最新的版本为1.2.0。

(1) 修改gradle

allprojects {    repositories {        mavenCentral()        jcenter()    }}dependencies {    // most often used way, enable notification to notify block event    compile 'com.github.moduth:blockcanary-ui:1.2.0'    // this way you only write block logs, without notification    // compile 'com.github.moduth:blockcanary-android:1.2.0'    // this way you only enable BlockCanary in debug package    // debugCompile 'com.github.moduth:blockcanary-ui:1.2.0'    // releaseCompile 'com.github.moduth:blockcanary-no-op:1.2.0'}

(2) 注册BlockCanary

在自定义application中注册。

public class DemoApplication extends Application {    @Override    public void onCreate() {        ...        // Do it on main process        BlockCanary.install(this, new AppBlockCanaryContext()).start();    }}

通过以上两个步骤,我们就能使用BlockCanary了。

(3) 修改事件超时阈值

事件处理时间阈值默认为1000ms,我们可以通过修改BlockCanaryContext.java 的getConfigBlockThreshold方法修改事件处理超时阈值。

 /**     * Config block threshold (in millis), dispatch over this duration is regarded as a BLOCK. You may set it     * from performance of device.     *     * @return threshold in mills     */    public int getConfigBlockThreshold() {        return 1000;    }

五 BlockCanary参数解读

cpuCore:手机cpu个数。
processName:应用包名。
freeMemory: 手机剩余内存,单位KB。
totalMemory: 手机内训总和,单位KB。

timecost: 该Message(事件)执行时间,单位 ms。
threadtimecost: 该Message(事件)执行线程时间(线程实际运行时间,不包含别的线程占用cpu时间),单位 ms。

cpubusy: true表示cpu负载过重,false表示cpu负载不重。cpu负载过重导致该Message(事件) 超时,错误不在本事件处理上。

cpuBusy判断:

CpuSampler.java public CpuSampler(long sampleIntervalMillis) {        super(sampleIntervalMillis);        BUSY_TIME = (int) (mSampleIntervalMillis * 1.2f);    }public boolean isCpuBusy(long start, long end) {        if (end - start > mSampleIntervalMillis) {            long s = start - mSampleIntervalMillis;            long e = start + mSampleIntervalMillis;            long last = 0;            synchronized (mCpuInfoEntries) {                for (Map.Entry entry :mCpuInfoEntries.entrySet()) {                    long time = entry.getKey();                    if (s < time && time < e) {                        if (last != 0 && time - last > BUSY_TIME) {                            return true;                        }                        last = time;                    }                }            }        }        return false;    }

BUSY_TIME = (int) (mSampleIntervalMillis * 1.2f);
CpuSampler在子线程中每隔mSampleIntervalMillis读取一次cpu信息,如果在BUSY_TIME 没有读取下一条cpu信息,表示cpu忙,来不及处理本pid的任务,导致应用出现超时。

更多相关文章

  1. Android事件系统(1)
  2. [APK破解]Smart Launcher,简体中文,无需注册
  3. android解析XML总结(SAX、Pull、Dom三种方式)附带DOM4J、JDOM
  4. Android(安卓)widget定时更新及事件处理
  5. Android:Touch事件拦截机制
  6. Android实现长按录音松开保存、播放及根据声贝动画展示
  7. Android中ViewGroup到View的Touch事件的传递机制
  8. Android(安卓)万能适配器 节省你的开发时间
  9. Android(安卓)系统Handler用法简介

随机推荐

  1. box-sizing, 相对定位与绝对定位
  2. PHP Mysql教程 PHP初中级开发者必学的MyS
  3. 线上mysql的binlog导致磁盘暴增的排查记
  4. 软测经典面试题(一)
  5. FTP文件传输协议介绍和常用命令
  6. html+css基础入门教程篇之伪元素
  7. 「知识点」JavaScript 中11个有趣的事实
  8. 前端vue面试题大全
  9. 前端开发」一篇文章概括目前流行的前端开
  10. 时序数据库丨DolphinDB流计算引擎如何实