卡顿优化

2020-04-23 17:40:54 浏览数 (1)

耗时任务

StrictMode

帮助开发者检查代码不规范问题 严苛模式:Android 提供的一种运行检查机制 方便强大,容易被忽视,包含线程策略与虚拟机检测策略

线程策略

  1. 自定义耗时调用:detectCustomSlowCalls()
  2. 磁盘读取策略:detectDiskReads()
  3. 网络操作:detectNetwork()

虚拟机策略

  1. Activity泄漏:detectActivityLeaks()
  2. Sqlite泄漏:detectLeakedSqlitebjects()
  3. 检测实例数量:setClassInstanceLimit()
代码语言:javascript复制
通过日志进行输出不规范的信息,在日志输出窗口中使用StrictMode进行过滤输出的信息
//在release版本中不建议开启严格模式
    private boolean DEV_MODE = true;
 
    /**
     * StrictMode 用来检测代码规范问题,有两种测量,一种是线程策略,一种是虚拟机策略
     */
    private void initStrictMode() {
        if (DEV_MODE) {
            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                    .detectCustomSlowCalls()//检测执行缓慢的代码或者潜在的缓慢代码
                    .detectDiskReads()
                    .detectDiskWrites()
                    .detectNetwork()
                    .penaltyDialog()//弹窗违规提示框
                    .penaltyLog()//进行违规日志打印
                    .penaltyFlashScreen()
                    .build());
 
            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                    .detectActivityLeaks()//activity 泄漏
                    .detectLeakedClosableObjects()//未关闭的closeable对象泄漏
                    .detectLeakedSqlLiteObjects()//泄漏SQlite对象
                    .penaltyLog()
                    // .penaltyDeath()//使用严格模式,系统会做出相应的反应,比如打印日志,弹出对话框,以及崩溃,这里进行崩溃操作
                    .build());
        }
    }
 

注意

  1. 在线上环境即Release版本不建议开启严格模式。
  2. 严格模式无法监控JNI中的磁盘IO和网络请求。
  3. 应用中并非需要解决全部的违例情况,比如有些IO操作必须在主线程中进行(Android中io操作在主线程中还是很常见的,比如sp的读写啊,从文件中读取图片啊,缓存啊之类的,并不是都是异步的,只要是不太耗时的就行,但是android中从3.0后就不允许UI线程中进行联网操作了,所以联网是必须独立了)。

数据少量且快速的IO操作是可以放在UI线程的,比如说少量的文件数据读取或者是写入之类的。。 但是如果涉及到数据量较大或者速度较慢的IO操作比如网络请求或者是蓝牙通信,避免放在UI线程中,这会阻塞UI线程,严重的时候甚至会导致app直接闪退报错。。 建议这些耗时的IO操作都放入到新开辟的线程中进行。 UI线程最好只需要负责UI界面的显示更新之类的操作。

Android 监控组件 AndroidPerformanceMonitor

implementation 'com.github.markzhai:blockcanary-android:1.5.0'

AndroidPerformanceMonitor 是一个检测卡顿的开源库,前身是BlockCanary,更前身则是LeakCanary。而其使用与LeakCanary也比较相似,可以自主设置卡顿检测时间,检测到的卡顿同样是以Notification展示,在使用体验上也相当类似,与LeakCanary可以说是孪生兄弟。 原理: 利用了Looper.loop()中每个Message被分发前后的Log打印,而我们设置自己的Printer就可以根据Log的不同的处理: - Message分发前,使用HandlerThread延时发送一个Runnable,这个时间可自己设置; - Message在规定的时间内完成分发,则会取消掉这个Runnable; - Message没有在规定的时间内(实际上是规定时间的0.8)完成分发,那这个Runnable就会被执行,可以获取到当前的堆栈信息;

代码语言:javascript复制
// 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);
}
 
  1. 非侵入式
  2. 方便精准,定位到某一行代码

初始化操作:BlockCanary.install(this, new AppBlockCanaryContext()).start();

代码语言:javascript复制
/**
 * BlockCanary配置的各种信息
 */
public class AppBlockCanaryContext extends BlockCanaryContext {
 
    /**
     * Implement in your project.
     *
     * @return Qualifier which can specify this installation, like version   flavor.
     */
    @Override
    public String provideQualifier() {
        return "unknown";
    }
 
    /**
     * Implement in your project.
     *
     * @return user id
     */
    @Override
    public String provideUid() {
        return "uid";
    }
 
    /**
     * Network type
     *
     * @return {@link String} like 2G, 3G, 4G, wifi, etc.
     */
    @Override
    public String provideNetworkType() {
        return "unknown";
    }
 
    /**
     * Config monitor duration, after this time BlockCanary will stop, use
     * with {@code BlockCanary}'s isMonitorDurationEnd
     *
     * @return monitor last duration (in hour)
     */
    @Override
    public int provideMonitorDuration() {
        return -1;
    }
 
    /**
     * 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
     */
    @Override
    public int provideBlockThreshold() {
        return 500;
    }
 
    /**
     * Thread stack dump interval, use when block happens, BlockCanary will dump on main thread
     * stack according to current sample cycle.
     * <p>
     * Because the implementation mechanism of Looper, real dump interval would be longer than
     * the period specified here (especially when cpu is busier).
     * </p>
     *
     * @return dump interval (in millis)
     */
    @Override
    public int provideDumpInterval() {
        return provideBlockThreshold();
    }
 
    /**
     * Path to save log, like "/blockcanary/", will save to sdcard if can.
     *
     * @return path of log files
     */
    @Override
    public String providePath() {
        return "/blockcanary/";
    }
 
    /**
     * If need notification to notice block.
     *
     * @return true if need, else if not need.
     */
    @Override
    public boolean displayNotification() {
        return true;
    }
 
    /**
     * Implement in your project, bundle files into a zip file.
     *
     * @param src files before compress
     * @param dest files compressed
     * @return true if compression is successful
     */
    @Override
    public boolean zip(File[] src, File dest) {
        return false;
    }
 
    /**
     * Implement in your project, bundled log files.
     *
     * @param zippedFile zipped file
     */
    @Override
    public void upload(File zippedFile) {
        throw new UnsupportedOperationException();
    }
 
 
    /**
     * Packages that developer concern, by default it uses process name,
     * put high priority one in pre-order.
     *
     * @return null if simply concern only package with process name.
     */
    @Override
    public List<String> concernPackages() {
        return null;
    }
 
    /**
     * Filter stack without any in concern package, used with @{code concernPackages}.
     *
     * @return true if filter, false it not.
     */
    @Override
    public boolean filterNonConcernStack() {
        return false;
    }
 
    /**
     * Provide white list, entry in white list will not be shown in ui list.
     *
     * @return return null if you don't need white-list filter.
     */
    @Override
    public List<String> provideWhiteList() {
        LinkedList<String> whiteList = new LinkedList<>();
        whiteList.add("org.chromium");
        return whiteList;
    }
 
    /**
     * Whether to delete files whose stack is in white list, used with white-list.
     *
     * @return true if delete, false it not.
     */
    @Override
    public boolean deleteFilesInWhiteList() {
        return true;
    }
 
    /**
     * Block interceptor, developer may provide their own actions.
     */
    @Override
    public void onBlock(Context context, BlockInfo blockInfo) {
        Log.i("lz","blockInfo " blockInfo.toString());
    }
}
 

ANR

AMS让应用组件做的操作,没有在规定的时间内完成,就会弹出这个AppNotRespondingDialog弹框告诉该应用未响应。 1. KeyDispacthTimeOut 5s 2. BroadcastTimeout 前台10s, 后台60s 3. ServiceTimeout 前台20s,后台200s 在源码中appNotRespondingmUIHandler通过发送消息弹出一个DialogAppNotRespondingDialog,这里的mUIHandler不是systemServer的主线程,而是子线程

Service超时

realStartServiceLocked中进行启动service,如果应用没有在规定时间内启动服务,就会触发一个超时机制,scheduleServiceTimeoutLocked,就会触发serviceTimeout,弹出AppNotRespondingDialog。如果service正常启动完成就会在handleCreateService中调用AMS的serviceDoneExecutingLocked中移除超时消息

ANR 执行流程

  1. 发送ANR
  2. 进程接收异常终止信号,开始写入进程ANR信息
  3. 弹出ANR提示框(Room表现不一,有些手机厂商会把提示框给去掉)

ANR 解决方式

adb pull data/anr/traces.txt存储路径,然后分析CPU、IO及锁

ANR 测试

代码语言:javascript复制
 //给主线程造成卡顿,在子线程中获取锁,并让主线中等待20s,在让它获取锁
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (MainActivity.this){
                    try {
                        Thread.sleep(20000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
 //申请这把锁
        synchronized (MainActivity.this){
            LogUtils.i("");
        }
 

ANR 监控

  1. 通过FileObserver监控文件变化,高版本权限问题
  2. 使用ANR-WatchDog

ANR-WatchDog

非侵入式ANR检测组件 https://github.com/SalomonBrys/ANR-WatchDog 使用:new ANRWatchDog().start();

原理

ANR-WatchDog同样是一个检测卡顿的检测库,与AndroidPerformanceMonitor不一样的是它的原理相对简单: 原理是开启一个线程,持续循环不断的往UI线程中Post一个Runnable(修改一个数的大小),然后在规定时间之后检测这个Runnable是否被执行(数的大小有没有 被修改过来)。没有被执行的话说明主线程执行上一个Message超时,然后获取当前堆栈信息; ANR-WatchDog的原理更加简单,但是根据使用情况来看准确性不及AndroidPerformanceMonitor高,而且可设置的配置不如AndroidPerformanceMonitor丰富;

AndroidPerformanceMonitor与 ANR-WatchDog 区别
  • AndroidPerformanceMonitor:监控Msg
  • ANR-WatchDog:看最终结果
  • 前者适合监控卡顿,后者适合补充ANR监控
ANR-WatchDog优缺点

优点 1. 兼容性好,各个机型版本通用 2. 无需修改APP逻辑代码,非侵入式 3. 逻辑简单,性能影响不大

缺点 无法保证能捕捉所有ANR,对阈值的设置直接影响捕获概率

0 人点赞