美文网首页
一种减少 Android 系统异常崩溃的方案

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

作者: 北疆小兵 | 来源:发表于2022-02-25 16:31 被阅读0次

    背景

    在 开发 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 系统异常崩溃的方案

          本文链接:https://www.haomeiwen.com/subject/exuxrrtx.html