04.Android崩溃Crash库之Loop拦截崩溃和ANR

Wesley13
• 阅读 800

目录总结

  • 01.能否利用Looper拦截崩溃
  • 02.思考几个问题分析
  • 03.App启动时自动开启Looper
  • 04.拦截主进程崩溃

前沿

01.能否利用Looper拦截崩溃

  • 问题思考一下

    • 能否基于 Handler 和 Looper 拦截全局崩溃(主线程),避免 APP 退出。
    • 能否基于 Handler 和 Looper 实现 ANR 监控。
  • 测试代码如下所示

    public class App extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
            CrashTestDemo.test();
        }
    }
    
    //测试代码
    public class CrashTestDemo {
    
        private static long startWorkTimeMillis = 0L;
        public static void test(){
            Looper.getMainLooper().setMessageLogging(new Printer() {
                @Override
                public void println(String it) {
                    if (it.startsWith(">>>>> Dispatching to Handler")) {
                        startWorkTimeMillis = System.currentTimeMillis();
                    } else if (it.startsWith("<<<<< Finished to Handler")) {
                        long duration = System.currentTimeMillis() - startWorkTimeMillis;
                        if (duration > 100) {
                            Log.e("Application---主线程执行耗时过长","$duration 毫秒,$it");
                        }
                    }
                }
            });
            Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        try {
                            Looper.loop();
                        } catch (Throwable e){
                            if (e.getMessage()!=null && e.getMessage().startsWith("Unable to start activity")){
                                android.os.Process.killProcess(android.os.Process.myPid());
                                break;
                            }
                            e.printStackTrace();
                            Log.e("Application---Looper---",e.getMessage());
                        }
                    }
                }
            });
            Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                @Override
                public void uncaughtException(Thread t, Throwable e) {
                    e.printStackTrace();
                    Log.e("Application-----","uncaughtException---异步线程崩溃,自行上报崩溃信息");
                }
            });
        }
    }
    
  • 通过上面的代码就可以就可以实现拦截UI线程的崩溃,耗时性能监控。但是也并不能够拦截所有的异常,如果在Activity的onCreate出现崩溃,导致Activity创建失败,那么就会显示黑屏。

02.思考几个问题分析

  • 通过上面简单的代码,我们就实现崩溃和ANR的拦截和监控,但是我们可能并不知道是为何实现的,包括我们知道出现了ANR,但是我们还需要进一步分析为何处出现ANR,如何解决。
  • 今天分析的问题有:
    • 如何拦截全局崩溃,避免APP退出。如何实现 ANR 监控。拦截到了之后可以做什么处理,如何优化?

03.App启动时自动开启Looper

  • 先从APP启动开始分析,APP的启动方法是在ActivityThread中,在main方法中创建了主线程的Looper,也就是当前进程创建。

    • 在main方法的最后调用了 Looper.loop(),在这个方法中处理主线程的任务调度,一旦执行完这个方法就意味着APP被退出了。

    • 如果我们要避免APP被退出,就必须让APP持续执行Looper.loop()。注意这句话非常重要!!!

      public final class ActivityThread extends ClientTransactionHandler { ... public static void main(String[] args) { ... Looper.prepareMainLooper(); ... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } }

  • 那进一步分析Looper.loop()方法

    • 在这个方法中写了一个循环,只有当 queue.next() == null 的时候才退出,看到这里我们心里可能会有一个疑问,如果没有主线程任务,是不是Looper.loop()方法就退出了呢?

    • 实际上queue.next()其实就是一个阻塞的方法,如果没有任务或没有主动退出,会一直在阻塞,一直等待主线程任务添加进来。

    • 当队列有任务,就会打印信息 Dispatching to ...,然后就调用 msg.target.dispatchMessage(msg);执行任务,执行完毕就会打印信息 Finished to ...,我们就可以通过打印的信息来分析 ANR,一旦执行任务超过5秒就会触发系统提示ANR,但是我们对自己的APP肯定要更加严格,我们可以给我们设定一个目标,超过指定的时长就上报统计,帮助我们进行优化。

      public final class Looper { final MessageQueue mQueue; public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger final Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } try { msg.target.dispatchMessage(msg); } finally {} if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } msg.recycleUnchecked(); } } public void quit() { mQueue.quit(false); } }

  • 如何让app崩溃后不会退出

    • 如果主线程发生了异常,就会退出循环,意味着APP崩溃,所以我们我们需要进行try-catch,避免APP退出,我们可以在主线程再启动一个 Looper.loop() 去执行主线程任务,然后try-catch这个Looper.loop()方法,就不会退出。

04.拦截主进程崩溃

  • 拦截主进程崩溃其实也有一定的弊端,因为给用户的感觉是点击没有反应,因为崩溃已经被拦截了。如果是Activity.create崩溃,会出现黑屏问题,所以如果Activity.create崩溃,必须杀死进程,让APP重启,避免出现改问题。

    public class MyApplication extends Application {
        @Override
        protected void attachBaseContext(Context base) {
            super.attachBaseContext(base);
            new Handler(getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        try {
                            Looper.loop();
                        } catch (Throwable e) {
                            e.printStackTrace();
                            // TODO 需要手动上报错误到异常管理平台,比如bugly,及时追踪问题所在。
                            if (e.getMessage() != null && e.getMessage().startsWith("Unable to start activity")) {
                                // 如果打开Activity崩溃,就杀死进程,让APP重启。
                                android.os.Process.killProcess(android.os.Process.myPid());
                                break;
                            }
                        }
                    }
                }
            });
        }
    }
    

项目地址:https://github.com/yangchong211/YCAndroidTool

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Cef经典N大问题
1、cef启动、退出的时候怎么崩溃了答:如果是启动时崩溃,请看资源目录是否文件都齐全.退出的话见https://github.com/fanfeilong/cefutil/blob/master/doc/CEF\_Close.md如果是centos6.4内核上跑CEF启动时候崩溃,原因是不支持一个uid有关的东西。启动时增加参数di
Wesley13 Wesley13
3年前
03.Android崩溃Crash库之ExceptionHandler分析
目录总结00.异常处理几个常用api01.UncaughtExceptionHandler02.Java线程处理异常分析03.Android中线程处理异常分析04.为何使用setDefaultUncaughtExceptionHandler前沿上一篇整体介绍了crash崩溃
Wesley13 Wesley13
3年前
01.Android崩溃Crash封装库
目录介绍01.该库具有的功能02.该库优势分析03.该库如何使用04.降低非必要crash05.异常恢复原理06.后续的需求说明07.异常栈轨迹原理08.部分问题反馈09.其他内容说明01.该库具有的功能1.1功能说明
Stella981 Stella981
3年前
Android蓝牙连接汽车OBD设备
//设备连接public class BluetoothConnect implements Runnable {    private static final UUID CONNECT_UUID  UUID.fromString("0000110100001000800000805F9B34FB");
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这