android中通过BlockCanary分析android卡顿的原因

原文:https://blog.csdn.net/bazhongren/article/details/51125113

 

 

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

looper

looper

三. 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'
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

(2) 注册BlockCanary

在自定义application中注册。

public class DemoApplication extends Application {
    @Override
    public void onCreate() {
        ...
        // Do it on main process
        BlockCanary.install(this, new AppBlockCanaryContext()).start();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

通过以上两个步骤,我们就能使用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;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

五 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<Long, String> 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;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

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

 

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页