Android应用篇 - block 如何分析

今天来聊聊卡顿的问题,除了内存泄漏,页面的流畅度也非常重要。我目前使用的最流畅的 app 就是 Telegram 了。那么在 Android 中如何检测卡顿呢?

目录:

  1. 卡顿的原因
  2. BlockCannary
  3. StrictMode

1. 卡顿的原因

我总结了以下几点:

  1. 在主线程处理耗时任务,比如处理 IO,操作数据库,数据计算等。
  2. 在主线程进行网络请求,当然在 Android 4.0 后,在主线程进行网络请求会抛出异常。
  3. 解析 xml 布局或者 new 的视图层级过多。
  4. 调用系统硬件,比如相机。
  5. 加载图片没有处理好。
  6. 启动的线程过多,cpu 占用率过高。

那么如何来查找卡顿呢?有两种方式:

  • 1. 使用工具 BlockCannary,带有图形界面,可以设置卡顿的阀值。
  • 2. 使用 Android 自带的工具类 StrictMode。

2. BlockCannary

  • 2.1 简介

BlockCanary 是国内开发者 MarkZhai 开发的一套性能监控组件,它对主线程操作进行了完全透明的监控,并能输出有效的信息,帮助开发分析、定位到问题所在,迅速优化应用。

其特点有:

  • 非侵入式,简单的两行就打开监控,不需要到处打点,破坏代码优雅性。
  • 精准,输出的信息可以帮助定位到问题所在 (精确到行),不需要像 Logcat 一样,慢慢去找。
    目前包括了核心监控输出文件,以及 UI 显示卡顿信息功能。

使用文档:https://github.com/markzhai/AndroidPerformanceMonitor/blob/master/README_CN.md

  • 2.2 原理

Android 中主线程 ActivityThread 创建一个Looper (Looper.prepare),而 Looper 又会关联一个 MessageQueue,主线程 Looper会在应用的生命周期内不断轮询 (Looper.loop),从 MessageQueue 取出 Message  更新 UI。

public static void loop() {
    ...
    for (;;) {
        ...
        // 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);
        }
        ...
    }
}

msg.target 其实就是 Handler,看一下 dispatchMessage() 的逻辑:

/**
 * Handle system messages here.
 */ 
public void dispatchMessage(Message msg) { 
    if (msg.callback != null) { 
        handleCallback(msg); 
    } else { 
        if (mCallback != null) { 
            if (mCallback.handleMessage(msg)) { 
                return; 
            } 
        } 
        handleMessage(msg); 
    } 
}
  • 如果消息是通过 Handler.post(runnable) 方式投递到 MQ 中的,那么就回调 runnable#run 方法;
  • 如果消息是通过 Handler.sendMessage() 的方式投递到 MQ 中,那么回调 handleMessage 方法;

不管是哪种回调方式,回调一定发生在 UI 线程。因此如果应用发生卡顿,一定是在 dispatchMessage() 中执行了耗时操作。我们通过给主线程的 Looper 设置一个 Printer,打点统计 dispatchMessage() 方法执行的时间,如果超出阀值,表示发生卡顿,则dump 出各种信息,提供开发者分析性能瓶颈。

3. StrictMode

  • 3.1 简介

StrictMode 严格模式,主要用来检测程序中违例情况的开发者工具。最常用的场景就是检测主线程中本地磁盘、网络读写等耗时的操作以及 Activity 泄露等,但该模式不建议在 Release 版本开启,此外该模式无法监控 JNI 中的磁盘 IO 和网络请求且其违例情况仅供参考,需结合实际开发需求予以解决。

主要采用 ThreadPolicy (线程策略) 和 VmPolicy (Vm 策略)进行检测,各策略检测内容如下:

ThreadPolicy

线程策略检测的内容有:

  • 自定义的耗时调用使用 detectCustomSlowCalls() 开启。
  • 磁盘读取操作使用 detectDiskReads() 开启。
  • 磁盘写入操作使用 detectDiskWrites() 开启。
  • 网络操作使用 detectNetwork() 开启。

VmPolicy

虚拟机策略检测的内容有:

  • Activity 泄露使用 detectActivityLeaks() 开启。
  • 未关闭的 closable 对象泄露使用 detectLeakedClosableObjects() 开启。
  • 泄露的 sqlite 对象使用 detectLeakedSqlLiteObjects() 开启。
  • 检测实例数量使用 setClassInstanceLimit() 开启。
  • 3.2 使用
public class DebugUtil {  

    public static void startStrictModeVmPolicy(){  
        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()  
        .detectActivityLeaks()/*检测Activity内存泄露*/  
        .detectLeakedClosableObjects()/*检测未关闭的Closable对象*/  
        .detectLeakedSqlLiteObjects() /*检测Sqlite对象是否关闭*/     
        /*也可以采用detectAll()来检测所有想检测的东西*/  
        .penaltyLog().build());  
    }  

    public static void startStrictModeThreadPolicy(){  
        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()  
        .detectDiskReads()/*磁盘读取操作检测*/  
        .detectDiskWrites()/*检测磁盘写入操作*/  
        .detectNetwork() /*检测网络操作*/     
        /*也可以采用detectAll()来检测所有想检测的东西*/  
        .penaltyLog().build());  
    }  
}

查看日志输出:

D/StrictMode( 9730): StrictMode policy violation; ~duration=20 ms: android.os.StrictMode$StrictModeDiskReadViolation: policy=31 violation=2  
            D/StrictMode( 9730):    at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1176)  
            D/StrictMode( 9730):    at libcore.io.BlockGuardOs.open(BlockGuardOs.java:106)  
            D/StrictMode( 9730):    at libcore.io.IoBridge.open(IoBridge.java:390)  
            D/StrictMode( 9730):    at java.io.FileOutputStream.<init>(FileOutputStream.java:88)  
            D/StrictMode( 9730):    at com.example.strictmodedemo.MainActivity.writeToExternalStorage(MainActivity.java:56)  
            D/StrictMode( 9730):    at com.example.strictmodedemo.MainActivity.onCreate(MainActivity.java:30)  
            D/StrictMode( 9730):    at android.app.Activity.performCreate(Activity.java:4543)
发布了126 篇原创文章 · 获赞 215 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/u014294681/article/details/88718826
今日推荐