一种减少 Android 系统异常崩溃的方案

背景

在 开发 Android 应用过程中,在 Looper 的 handleCallback 的方法中经常会发生一些异常而导致应用崩溃,其实很多异常是没必要导致整个应用崩溃的。但是我们无法直接对进行 try catch 处理,也无法得知这些 message/runnable 是在哪里被 post 到主线程的消息队列中的,这些 bug 常常占据应用崩溃率指标的一部分,影响用户体验。

问题分析

应用启动的时候,在系统的ActiivtyThread的 main 方法中会调用 Looper#loop 方法开启一个无限循环不断从 Looper 的 MessageQueue 中 取出 Message 交给 Handler 来处理, 伪代码如下:

 #Looper
 public static void loop() {
        final Looper me = myLooper();
      
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                return;
            }
             ....
            try {
            // 这里实际上是 Handler#dispatchMessage(msg)
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
              
            }
             ....
        }
    }
}

主线程Looper 的handleCallback 过程都是经过下面这句代码调用的:

//Handler#dispatchMessage(msg)
msg.target.dispatchMessage(msg);

实际上, Android 的生命周期函数的调用以及平常我们通过 Handler post 的 Message 和 Runnable 实际上都是通过Looper 往消息队列 post 消息的机制来完成的。
因此如果我们能对以上 主线程 Handler 的 dispatchMessage 做 try catch 处理,就能间接地忽略掉那些我们认为没必要导致系统崩溃的异常了。

方案探索

要想针对Looper的handleMessage中的某些异常做处理,实际上就是需要能找到处理Handler处理handleMessage 的 "勾子"函数,就可以对其进行异常处理了 ,有以下两种办法:

    1. 反射 Hook 所有 Handler 和 Looper 操作

使用反射的方式 对 Handler 的 handleMessage 方法做反射处理,替换成自定义的 Handler,在自定义的 HandleMessage 方法中对相应的指定的异常类型做 try catch 处理。
缺点:在不同厂商不同 Android 版本设备存在兼容性问题,不排除未来在某个 Android 版本中无法反射而导致机制失效

    1. 接管 Looper
      在系统 ActivityThread 启动主线程的时候,系统会开启Looper循环,死循环不断从消息队列中取出消息来执行 Handler#handleMessage 方法。只要能

优点:不需要反射,没有兼容性问题

接管Looper方案实现

我们可以在应用 Application 启动的时候,通过主线程 Handler往其中 post 一个无限循环的 Runnable,然后在 Runnable 内部调用 Looper.loop() 方法,即可让原来的 Looper 保存运转,同时拥有了 try-catch 的机会。


image.png

代码实现:

private void initLoop(){
        Handler mainHandler = new Handler(Looper.getMainLooper());
        mainHandler.post(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try{
                        Looper.loop();
                    }catch (Exception e){
                        e.printStackTrace();
                        String stack = Log.getStackTraceString(e);
                        if (e instanceof SecurityException){

                        }
                        else if (e instanceof WindowManager.BadTokenException){

                        }  else if (e instanceof IndexOutOfBoundsException){

                        }
                        else if (
                                stack.contains("SelectionHandleView")
                                        || stack.contains("Magnifier.show")
                                        || stack.contains("ViewRootImpl.handleDragEvent")
                                        ....
                        ) {
                            e.printStackTrace();
                        }else {
                            throw e;
                        }
                    }

                }
            }
        });
    }

结语

这种接管系统 Looper 的方案,不需要任何反射、没有任何兼容性问题,也不会引起系统异常。对于那些 try-catch 不会影响上下文混乱的异常,我们都可以采用这种方式来使我们的应用更加稳定。

参考

本文技术方案参考了来自 Drakeet 大神 在 [扔物线] 知识星球的分享

你可能感兴趣的:(一种减少 Android 系统异常崩溃的方案)