阅读 718

Crash动态兜底方案

Thread.UncaughtExceptionHandler

  • 对于未try catch的异常,会回调到接口Thread.UncaughtExceptionHandleruncaughtException(Thread t, Throwable e)方法。
  • uncaughtException()方法执行完后线程会阻塞住,因此系统默认实现是直接杀进程。系统默认实现的是com.android.internal.os.RuntimeInit中的KillApplicationHandler。我们可以看到在finally代码块中调用了Process.killProcess()然后退出程序。
 private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            try {
                ensureLogging(t, e);
                if (mCrashing) return;
                mCrashing = true;
                if (ActivityThread.currentActivityThread() != null) {
                    ActivityThread.currentActivityThread().stopProfiling();
                }
                ActivityManager.getService().handleApplicationCrash(
                        mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
            } catch (Throwable t2) {
                if (t2 instanceof DeadObjectException) {
                } else {
                    try {
                        Clog_e(TAG, "Error reporting crash", t2);
                    } catch (Throwable t3) {
                    }
                }
            } finally {
                Process.killProcess(Process.myPid());
                System.exit(10);
            }
        }
    }
复制代码
  • 可以通过调用Thread.setUncaughtExceptionHandler()给指定线程设置自定义UncaughtExceptionHandler。或者调用Thread.setDefaultUncaughtExceptionHandler()静态方法来给所有线程设置自定义UncaughtExceptionHandler,但这会导致无法通过调用Thread.getDefaultUncaughtExceptionHandler()来获取到系统的KillApplicationHandler。当然我们可以先在set之前进行缓存。

接管Looper

  • 我们可以自定义UncaughtExceptionHandler,在uncaughtException()方法中try catch住线程的Looper.loop()。如果是子线程要确保已经设置Looper。
public class MyCaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
    
    @Override
    public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
        if (handleException(t, e)){
            //设置在主线程
            if (t.getName().equals("main")) {
                for (;;) {
                    try {
                        Looper.loop();
                    } catch (Exception e1) {
                       handleException(t, e1);
                    }
                }
            }
        }
    }

    /**
     * 处理该异常,如果不能处理则交给系统默认的KillApplicationHandler
     */
    private boolean handleException(Thread t, Throwable e){
        if (e.getClass().getSimpleName().equals("NullPointerException")) {
            //进行上报等自定义操作处理异常
            Log.e("?????", "NullPointerException: ");
            return true;
        } else {
            //打印堆栈信息
            e.printStackTrace();
            Thread.UncaughtExceptionHandler uncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
            if (uncaughtExceptionHandler != null){
                //交给系统默认的KillApplicationHandler处理,会杀死进程
                uncaughtExceptionHandler.uncaughtException(t, e);
            }
            return false;
        }
    }
}
复制代码
  • 在主线程中通过死循环调用Looper.loop(),使得MessageQueue中之后的消息都会在这里处理。并对Looper.loop()进行try catch。当遇到异常的时候判断是否能处理异常,如果能就不会Crash,程序继续运行。如果不能处理我们可以再交回给KillApplicationHandler
  • 我们可以通过云端下发需要保护的具体Crash。比如:包含进程名,Android版本,App版本,方法名,异常全类名等。当catch住的异常能在云端下发的保护Crash名单中匹配到,则进行上报后不做处理。让程序继续正常运行。
  • 我们可以通过这种方式,对一些厂商的定制系统的兼容性问题、Android系统Bug、线上的灰度问题等都可以采用这个方法进行兜底。方案改动小,业务侧完全无感知,后期维护成本低。
文章分类
Android
文章标签