Cockroach

Introduction: 打不死的小强,永不 crash 的 Android
More: Author   ReportBugs   
Tags:

为什么开发这个库

很多时候由于一些微不足道的 bug 导致 app 崩溃很可惜,android 默认的异常杀进程机制简单粗暴,但很多时候让 app 崩溃其实也并不能解决问题。

有些 bug 可能是系统 bug,对于这些难以预料的系统 bug 我们不好绕过,还有一些 bug 是我们自己编码造成的,对于有些 bug 来说直接忽略掉的话可能只是导致部分不重要的功能没法使用而已,又或者对用户来说完全没有影响,这种情况总比每次都崩溃要好很多。

下面介绍几个真实案例来说明这个库的优势:

  • 有一款特殊的手机,每次开启某个 Activity 时都报错,提示没有在清单中声明,但其他几百万机型都没问题,这种情况很可能就是系统 bug 了,由于是在 onclick 回调里直接使用 startActivity 来开启 Activity,onclick 里没有其他逻辑,对于这种情况的话直接忽略掉是最好的选择,因为 onclick 回调是在一个单独的 message 中的,执行完了该 message 就接着执行下一个 message,该 message 执行不完也不会影响下一个 message 的执行,调用 startactivity 后会同步等待 ams 返回的错误码,结果这款特殊的机型返回了没有声明这个 Activity,所以对于这种情况可以直接忽略掉,唯一的影响就是这个 Activity 不会显示,就跟没有调用 onClick 一样

  • 我们在 app 中集成了个三方的数据统计库,这个库是在 Application 的 onCreate 的最后初始化的,但上线后执行初始化时却崩溃了,对于这种情况直接忽略掉也是最好的选择。根据 app 的启动流程来分析,Application 的创建以及 onCreate 方法的调用都是在同一个 message 中执行的,该 message 执行的最后调用了 Application 的 onCreate 方法,又由于这个数据统计库是在 onCreate 的最后才初始化的,所以直接忽略的话也没有影响,就跟没有初始化过一样

  • 我们做了个检查 app 是否需要升级的功能,若需要升级,则使用 context 开启一个 dialog 风格的 Activity 提示是否需要升级,测试阶段没有任何问题,但一上线就崩溃了,提示没有设置 FLAG_ACTIVITY_NEW_TASK,由于启动 Activity 的 context 是 Application,但在高版本 android 中,可以使用 Application 启动 Activity 并且不设置这个 FLAG,但在低版本中必须要设置这个 FLAG,对于这种问题也可以直接忽略

    API28 ContextImpl startActivity 源码

    public void startActivity(Intent intent, Bundle options) {
          warnIfCallingFromSystemProcess();
    
          // Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
          // generally not allowed, except if the caller specifies the task id the activity should
          // be launched in. A bug was existed between N and O-MR1 which allowed this to work. We
          // maintain this for backwards compatibility.
          final int targetSdkVersion = getApplicationInfo().targetSdkVersion;
    
          if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
                  && (targetSdkVersion < Build.VERSION_CODES.N
                          || targetSdkVersion >= Build.VERSION_CODES.P)
                  && (options == null
                          || ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) {
              throw new AndroidRuntimeException(
                      "Calling startActivity() from outside of an Activity "
                              + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                              + " Is this really what you want?");
          }
          mMainThread.getInstrumentation().execStartActivity(
                  getOuterContext(), mMainThread.getApplicationThread(), null,
                  (Activity) null, intent, -1, options);
      }
    
  • 还有各种执行 onclick 时触发的异常,这些很多时候都是可以直接忽略掉的

更新日志

  • 修复 Android P 反射限制导致的 Activity 生命周期异常无法 finish Activity 问题

cockroach1.0 版在这

Cockroach 2.0 新特性

  • Cockroach 2.0 减少了 Cockroach 1.0 版本中 Activity 生命周期中抛出异常黑屏的问题。
  • Cockroach 1.0 未雨绸缪,提前做好准备,等待异常到来。Cockroach 2.0 马后炮,只有当抛出异常时才去拯救。
  • Cockroach 2.0 试图在 APP 即将崩溃时尽量去挽救,不至于情况更糟糕。

用一张图片来形容就是

img

特别注意: 当 view 的 measure,layout,draw,以及 recyclerview 的 bindviewholder 方法抛出异常时会导致 viewrootimpl 挂掉,此时会回调 onMayBeBlackScreen 方法,建议直接杀死 app。目前可以拦截到抛出异常的 ViewRootImpl,具体参考这https://github.com/android-notes/SwissArmyKnife/blob/master/saklib/src/main/java/com/wanjian/sak/system/traversals/ViewTraversalsCompact.java

使用姿势

  • 必须要在 Application 初始化时装载

例如:


    package com.wanjian.demo;

    import android.app.Application;
    import android.os.Handler;
    import android.os.Looper;
    import android.util.Log;
    import android.widget.Toast;

    import com.wanjian.cockroach.Cockroach;

    /**
     * Created by wanjian on 2018/5/19. */

    public class App extends Application {

        @Override
        public void onCreate() {
            super.onCreate();
            install();
        }


        private void install() {
            Cockroach.install(new ExceptionHandler() {
                       @Override
                       protected void onUncaughtExceptionHappened(Thread thread, Throwable throwable) {
                           Log.e("AndroidRuntime", "--->onUncaughtExceptionHappened:" + thread + "<---", throwable);
                           new Handler(Looper.getMainLooper()).post(new Runnable() {
                               @Override
                               public void run() {
                                   toast.setText(R.string.safe_mode_excep_tips);
                                   toast.show();
                               }
                           });
                       }

                       @Override
                       protected void onBandageExceptionHappened(Throwable throwable) {
                           throwable.printStackTrace();//打印警告级别 log,该 throwable 可能是最开始的 bug 导致的,无需关心
                           toast.setText("Cockroach Worked");
                           toast.show();
                       }

                       @Override
                       protected void onEnterSafeMode() {
                           int tips = R.string.safe_mode_tips;
                           Toast.makeText(App.this, getResources().getString(tips), Toast.LENGTH_LONG).show();

                       }

                       @Override
                       protected void onMayBeBlackScreen(Throwable e) {
                           Thread thread = Looper.getMainLooper().getThread();
                           Log.e("AndroidRuntime", "--->onUncaughtExceptionHappened:" + thread + "<---", e);
                           //黑屏时建议直接杀死 app
                           sysExcepHandler.uncaughtException(thread, new RuntimeException("black screen"));
                       }

                   });

        }
    }

原理分析

cockroach2.0 通过替换ActivityThread.mH.mCallback,实现拦截 Activity 生命周期, 通过调用 ActivityManager 的finishActivity结束掉生命周期抛出异常的 Activity

相关视频 https://raw.githubusercontent.com/android-notes/Cockroach/master/cockroach.mp4?raw=true

相关原理分析

相关连接

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools