【云+社区年度征文】能否让APP永不崩溃—小光和我的对决

前言

关于拦截异常,想必大家都知道可以通过Thread.setDefaultUncaughtExceptionHandler来拦截App中发生的异常,然后再进行处理。

于是,我有了一个不成熟的想法。。。

让我的APP永不崩溃

既然我们可以拦截崩溃,那我们直接把APP中所有的异常拦截了,不杀死程序。这样一个不会崩溃的APP用户体验不是杠杠的?

  • 有人听了摇摇头表示不赞同,这不小光跑来问我了:

“老铁,出现崩溃是要你解决它不是掩盖它!!”

  • 我拿把扇子扇了几下,有点冷但是故作镇定的说:

“这位老哥,你可以把异常上传到自己的服务器处理啊,你能拿到你的崩溃原因,用户也不会因为异常导致APP崩溃,这不挺好?”

  • 小光有点生气的说:

“这样肯定有问题,听着就不靠谱,哼,我去试试看”

小光的实验

于是小光按照网上一个小博主—积木的文章,写出了以下捕获异常的代码:

代码语言:txt
复制
//定义CrashHandler
class CrashHandler private constructor(): Thread.UncaughtExceptionHandler {
    private var context: Context? = null
    fun init(context: Context?) {
        this.context = context
        Thread.setDefaultUncaughtExceptionHandler(this)
    }
override fun uncaughtException(t: Thread, e: Throwable) {}

companion object {
    val instance: CrashHandler by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
        CrashHandler() }
}

}

//Application中初始化
class MyApplication : Application(){
override fun onCreate() {
super.onCreate()
CrashHandler.instance.init(this)
}
}

//Activity中触发异常
class ExceptionActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_exception)

    btn.setOnClickListener {
        throw RuntimeException("主线程异常")
    }
    btn2.setOnClickListener {
        thread {
            throw RuntimeException("子线程异常")
        }
    }
}

}

小光一顿操作,写下了整套代码,为了验证它的猜想,写了两种触发异常的情况:子线程崩溃和主线程崩溃

  • 运行,点击按钮2,触发子线程异常崩溃:

“咦,还真没啥影响,程序能继续正常运行”

  • 然后点击按钮1,触发主线程异常崩溃:

“嘿嘿,卡住了,再点几下,直接ANR了”

主线程崩溃

“果然有问题,但是为啥主线程会出问题呢?我得先搞懂再去找老铁对峙。”

小光的思考(异常源码分析)

首先科普下java中的异常,包括运行时异常非运行时异常

  • 运行时异常。是RuntimeException类及其子类的异常,是非受检异常,比如系统异常或者是程序逻辑异常,我们常遇到的有NullPointerException、IndexOutOfBoundsException等。遇到这种异常,Java Runtime会停止线程,打印异常,并且会停止程序运行,也就是我们常说的程序崩溃。
  • 非运行时异常。是属于Exception类及其子类,是受检异常,RuntimeException以外的异常。这类异常在程序中必须进行处理,如果不处理程序都无法正常编译,比如NoSuchFieldException,IllegalAccessException这种。

ok,也就是说我们抛出一个RuntimeException异常之后,所在的线程会被停止。如果主线程中抛出这个异常,那么主线程就会被停止,所以APP就会卡住无法正常操作,时间久了就会ANR。而子线程崩溃了并不会影响主线程也就是UI线程的操作,所以用户还能正常使用。

这样好像就说的通了。

等等,那为什么遇到setDefaultUncaughtExceptionHandler就不会崩溃了呢?

我们还得从异常的源码开始说起:

一般情况下,一个应用中所使用的线程都是在同一个线程组,而在这个线程组里只要有一个线程出现未被捕获异常的时候,JAVA 虚拟机就会调用当前线程所在线程组中的 uncaughtException()方法。

代码语言:txt
复制
// ThreadGroup.java
private final ThreadGroup parent;

public void uncaughtException(Thread t, Throwable e) {
    if (parent != null) {
        parent.uncaughtException(t, e);
    } else {
        Thread.UncaughtExceptionHandler ueh =
            Thread.getDefaultUncaughtExceptionHandler();
        if (ueh != null) {
            ueh.uncaughtException(t, e);
        } else if (!(e instanceof ThreadDeath)) {
            System.err.print("Exception in thread \""
                             + t.getName() + "\" ");
            e.printStackTrace(System.err);
        }
    }
}</code></pre></div></div><p><code>parent</code>表示当前线程组的父级线程组,所以最后还是会调用到这个方法中。接着看后面的代码,通过<code>getDefaultUncaughtExceptionHandler</code>获取到了系统默认的异常处理器,然后调用了<code>uncaughtException</code>方法。那么我们就去找找本来系统中的这个异常处理器——<code>UncaughtExceptionHandler</code>。</p><p>这就要从APP的启动流程说起了,之前也说过,所有的<code>Android进程</code>都是由<code>zygote进程fork</code>而来的,在一个新进程被启动的时候就会调用<code>zygoteInit</code>方法,这个方法里会进行一些应用的初始化工作:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">    public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) {
    if (RuntimeInit.DEBUG) {
        Slog.d(RuntimeInit.TAG, &#34;RuntimeInit: Starting application from zygote&#34;);
    }

    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, &#34;ZygoteInit&#34;);
    //日志重定向
    RuntimeInit.redirectLogStreams();
    //通用的配置初始化  
    RuntimeInit.commonInit();
    // zygote初始化
    ZygoteInit.nativeZygoteInit();
    //应用相关初始化
    return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
}</code></pre></div></div><p>而关于异常处理器,就在这个通用的配置初始化方法当中:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">    protected static final void commonInit() {
    if (DEBUG) Slog.d(TAG, &#34;Entered RuntimeInit!&#34;);

   //设置异常处理器
    LoggingHandler loggingHandler = new LoggingHandler();
    Thread.setUncaughtExceptionPreHandler(loggingHandler);
    Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));

    //设置时区
    TimezoneGetter.setInstance(new TimezoneGetter() {
        @Override
        public String getId() {
            return SystemProperties.get(&#34;persist.sys.timezone&#34;);
        }
    });
    TimeZone.setDefault(null);

    //log配置
    LogManager.getLogManager().reset();
    //***    

    initialized = true;
}</code></pre></div></div><p>找到了吧,这里就设置了应用默认的异常处理器——<code>KillApplicationHandler</code>。</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
    private final LoggingHandler mLoggingHandler;

    
    public KillApplicationHandler(LoggingHandler loggingHandler) {
        this.mLoggingHandler = Objects.requireNonNull(loggingHandler);
    }

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        try {
            ensureLogging(t, e);
            //...    
            // Bring up crash dialog, wait for it to be dismissed
            ActivityManager.getService().handleApplicationCrash(
                    mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
        } catch (Throwable t2) {
            if (t2 instanceof DeadObjectException) {
                // System process is dead; ignore
            } else {
                try {
                    Clog_e(TAG, &#34;Error reporting crash&#34;, t2);
                } catch (Throwable t3) {
                    // Even Clog_e() fails!  Oh well.
                }
            }
        } finally {
            // Try everything to make sure this process goes away.
            Process.killProcess(Process.myPid());
            System.exit(10);
        }
    }

    private void ensureLogging(Thread t, Throwable e) {
        if (!mLoggingHandler.mTriggered) {
            try {
                mLoggingHandler.uncaughtException(t, e);
            } catch (Throwable loggingThrowable) {
                // Ignored.
            }
        }
    }</code></pre></div></div><p>看到这里,小光欣慰一笑,被我逮到了吧。在<code>uncaughtException</code>回调方法中,会执行一个<code>handleApplicationCrash</code>方法进行异常处理,并且最后都会走到<code>finally</code>中进行进程销毁,<code>Try everything to make sure this process goes away</code>。所以程序就崩溃了。</p><p>关于我们平时在手机上看到的崩溃提示弹窗,就是在这个<code>handleApplicationCrash</code>方法中弹出来的。不仅仅是java崩溃,还有我们平时遇到的<code>native_crash、ANR</code>等异常都会最后走到<code>handleApplicationCrash</code>方法中进行崩溃处理。</p><p>另外有的朋友可能发现了构造方法中,传入了一个<code>LoggingHandler</code>,并且在<code>uncaughtException</code>回调方法中还调用了这个<code>LoggingHandler</code>的<code>uncaughtException</code>方法,难道这个<code>LoggingHandler</code>就是我们平时遇到崩溃问题,所看到的崩溃日志?进去瞅瞅:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">private static class LoggingHandler implements Thread.UncaughtExceptionHandler {
    public volatile boolean mTriggered = false;

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        mTriggered = true;
        if (mCrashing) return;

        if (mApplicationObject == null &amp;&amp; (Process.SYSTEM_UID == Process.myUid())) {
            Clog_e(TAG, &#34;*** FATAL EXCEPTION IN SYSTEM PROCESS: &#34; + t.getName(), e);
        } else {
            StringBuilder message = new StringBuilder();
            message.append(&#34;FATAL EXCEPTION: &#34;).append(t.getName()).append(&#34;\n&#34;);
            final String processName = ActivityThread.currentProcessName();
            if (processName != null) {
                message.append(&#34;Process: &#34;).append(processName).append(&#34;, &#34;);
            }
            message.append(&#34;PID: &#34;).append(Process.myPid());
            Clog_e(TAG, message.toString(), e);
        }
    }
}

private static int Clog_e(String tag, String msg, Throwable tr) {
    return Log.printlns(Log.LOG_ID_CRASH, Log.ERROR, tag, msg, tr);
}</code></pre></div></div><p>这可不就是吗?将崩溃的一些信息——比如线程,进程,进程id,崩溃原因等等通过Log打印出来了。来张崩溃日志图给大家对对看:</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1723349410525063461.jpeg" /></div><div class="figure-desc">崩溃日志图</div></div></div></figure><p>好了,回到正轨,所以我们通过<code>setDefaultUncaughtExceptionHandler</code>方法设置了我们自己的崩溃处理器,就把之前应用设置的这个崩溃处理器给顶掉了,然后我们又没有做任何处理,自然程序就不会崩溃了,来张总结图。</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1723349410933053684.png" /></div><div class="figure-desc">崩溃调用图</div></div></div></figure><h3 id="9q0mh" name="%E5%B0%8F%E5%85%89%E5%8F%88%E6%9D%A5%E6%89%BE%E6%88%91%E5%AF%B9%E5%B3%99%E4%BA%86">小光又来找我对峙了</h3><ul class="ul-level-0"><li>搞清楚这一切的小光又来找我了:</li></ul><p>“老铁,你瞅瞅,这是我写的<code>Demo</code>和总结的资料,你那套根本行不通,主线程崩溃就GG了,我就说有问题吧”</p><ul class="ul-level-0"><li>我继续<strong>故作镇定</strong>:</li></ul><p>“老哥,我上次忘记说了,只加这个<code>UncaughtExceptionHandler</code>可不行,还得加一段代码,发给你,回去试试吧”</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">    Handler(Looper.getMainLooper()).post {
    while (true) {
        try {
            Looper.loop()
        } catch (e: Throwable) {
        }
    }
}</code></pre></div></div><p>“这,,能行吗”</p><h3 id="lvb0" name="%E5%B0%8F%E5%85%89%E5%86%8D%E6%AC%A1%E7%9A%84%E5%AE%9E%E9%AA%8C">小光再次的实验</h3><p>小光把上述代码加到了程序里面(Application—onCreate),再次运行:</p><p>我去,<code>真的没问题了</code>,点击主线程崩溃后,还是可以正常操作app,这又是什么原理呢?</p><h3 id="46bjf" name="%E5%B0%8F%E5%85%89%E7%9A%84%E5%86%8D%E6%AC%A1%E6%80%9D%E8%80%83%EF%BC%88%E6%8B%A6%E6%88%AA%E4%B8%BB%E7%BA%BF%E7%A8%8B%E5%B4%A9%E6%BA%83%E7%9A%84%E6%96%B9%E6%A1%88%E6%80%9D%E6%83%B3%EF%BC%89">小光的再次思考(拦截主线程崩溃的方案思想)</h3><p>我们都知道,在主线程中维护着<code>Handler</code>的一套机制,在应用启动时就做好了<code>Looper</code>的创建和初始化,并且调用了<code>loop</code>方法开始了消息的循环处理。应用在使用过程中,主线程的所有操作比如事件点击,列表滑动等等都是在这个循环中完成处理的,其本质就是将消息加入<code>MessageQueue</code>队列,然后循环从这个队列中取出消息并处理,如果没有消息处理的时候,就会依靠epoll机制挂起等待唤醒。贴一下我浓缩的<code>loop</code>代码:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">    public static void loop() {
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
    for (;;) {
        Message msg = queue.next(); 
        msg.target.dispatchMessage(msg);
    }
}</code></pre></div></div><p>一个死循环,不断取消息处理消息。再回头看看刚才加的代码:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">    Handler(Looper.getMainLooper()).post {
    while (true) {
        //主线程异常拦截
        try {
            Looper.loop()
        } catch (e: Throwable) {
        }
    }
}</code></pre></div></div><p>我们通过<code>Handler</code>往主线程发送了一个<code>runnable</code>任务,然后在这个<code>runnable</code>中加了一个死循环,死循环中执行了<code>Looper.loop()</code>进行消息循环读取。这样就会导致后续所有的主线程消息都会走到我们这个<code>loop</code>方法中进行处理,也就是一旦发生了主线程崩溃,那么这里就可以进行异常捕获。同时因为我们写的是while死循环,那么捕获异常后,又会开始新的<code>Looper.loop()</code>方法执行。这样主线程的Looper就可以一直正常读取消息,主线程就可以一直正常运行了。</p><p>文字说不清楚的图片来帮我们:</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1723349411524175486.png" /></div></div></div></figure><p>同时之前<code>CrashHandler</code>的逻辑可以保证子线程也是不受崩溃影响,所以两段代码都加上,齐活了。</p><p>但是小光还不服气,他又想到了一种崩溃情况。。。</p><h4 id="9tgsm" name="%E5%B0%8F%E5%85%89%E5%8F%88%E5%8F%88%E5%8F%88%E4%B8%80%E6%AC%A1%E5%AE%9E%E9%AA%8C">小光又又又一次实验</h4><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">class Test2Activity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_exception)

    throw RuntimeException(&#34;主线程异常&#34;)
}

}

诶,我直接在onCreate里面给你抛出个异常,运行看看:

黑漆漆的一片~没错,黑屏了

最后的对话(Cockroach库思想)

  • 看到这一幕,我主动找到了小光:

“这种情况确实比较麻烦了,如果直接在Activity生命周期内抛出异常,会导致界面绘制无法完成,Activity无法被正确启动,就会白屏或者黑屏了

这种严重影响到用户体验的情况还是建议直接杀死APP,因为很有可能会对其他的功能模块造成影响。或者如果某些Activity不是很重要,也可以只finish这个Activity。”

  • 小光思索地问:
    “那么怎么分辨出这种生命周期内发生崩溃的情况呢?”

“这就要通过反射了,借用Cockroach开源库中的思想,由于Activity的生命周期都是通过主线程的Handler进行消息处理,所以我们可以通过反射替换掉主线程的Handler中的Callback回调,也就是ActivityThread.mH.mCallback,然后针对每个生命周期对应的消息进行trycatch捕获异常,然后就可以进行finishActivity或者杀死进程操作了。”

主要代码:

代码语言:txt
复制
		Field mhField = activityThreadClass.getDeclaredField("mH");
mhField.setAccessible(true);
final Handler mhHandler = (Handler) mhField.get(activityThread);
Field callbackField = Handler.class.getDeclaredField("mCallback");
callbackField.setAccessible(true);
callbackField.set(mhHandler, new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (Build.VERSION.SDK_INT >= 28) {
//android 28之后的生命周期处理
final int EXECUTE_TRANSACTION = 159;
if (msg.what == EXECUTE_TRANSACTION) {
try {
mhHandler.handleMessage(msg);
} catch (Throwable throwable) {
//杀死进程或者杀死Activity
}
return true;
}
return false;
}

            //android 28之前的生命周期处理
            switch (msg.what) {
                case RESUME_ACTIVITY:
                //onRestart onStart onResume回调这里
                    try {
                        mhHandler.handleMessage(msg);
                    } catch (Throwable throwable) {
                        sActivityKiller.finishResumeActivity(msg);
                        notifyException(throwable);
                    }
                    return true;</code></pre></div></div><p>代码贴了一部分,但是原理大家应该都懂了吧,就是通过替换主线程<code>Handler</code>的<code>Callback</code>,进行声明周期的异常捕获。</p><p>接下来就是进行捕获后的<strong>处理工作</strong>了,要不杀死进程,要么杀死Activity。</p><ul class="ul-level-0"><li>杀死进程,这个应该大家都熟悉</li></ul><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">  Process.killProcess(Process.myPid())

exitProcess(10)

  • finish掉Activity

这里又要分析下Activity的finish流程了,简单说下,以android29的源码为例。

代码语言:txt
复制
    private void finish(int finishTask) {
if (mParent == null) {

        if (false) Log.v(TAG, &#34;Finishing self: token=&#34; + mToken);
        try {
            if (resultData != null) {
                resultData.prepareToLeaveProcess(this);
            }
            if (ActivityTaskManager.getService()
                    .finishActivity(mToken, resultCode, resultData, finishTask)) {
                mFinished = true;
            }
        } 
    } 

}


@Override
public final boolean finishActivity(IBinder token, int resultCode, Intent resultData,
        int finishTask) {
    return mActivityTaskManager.finishActivity(token, resultCode, resultData, finishTask);
}    </code></pre></div></div><p>从Activity的<code>finish源码</code>可以得知,最终是调用到<code>ActivityTaskManagerService</code>的<code>finishActivity</code>方法,这个方法有四个参数,其中有个用来标识<code>Activity</code>的参数也就是最重要的参数——<code>token</code>。所以去源码里面找找token~</p><p>由于我们捕获的地方是在<code>handleMessage</code>回调方法中,所以只有一个参数<code>Message</code>可以用,那我么你就从这方面入手。回到刚才我们处理消息的源码中,看看能不能找到什么线索:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0"> class H extends Handler {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case EXECUTE_TRANSACTION: 
                final ClientTransaction transaction = (ClientTransaction) msg.obj;
                mTransactionExecutor.execute(transaction);
                break;              
        }        
    }
}

public void execute(ClientTransaction transaction) {
    final IBinder token = transaction.getActivityToken();
    executeCallbacks(transaction);
    executeLifecycleState(transaction);
    mPendingActions.clear();
    log(&#34;End resolving transaction&#34;);
}    </code></pre></div></div><p>可以看到在源码中,Handler是怎么处理<code>EXECUTE_TRANSACTION</code>消息的,获取到<code>msg.obj</code>对象,也就是<code>ClientTransaction</code>类实例,然后调用了<code>execute</code>方法。而在<code>execute</code>方法中。。。咦咦咦,这不就是token吗?</p><p>(找到的过于快速了哈,主要是<code>activity</code>启动销毁这部分的源码解说并不是今天的重点,所以就一笔带过了)</p><p>找到<code>token</code>,那我们就通过反射进行Activity的销毁就行啦:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">    private void finishMyCatchActivity(Message message) throws Throwable {
    ClientTransaction clientTransaction = (ClientTransaction) message.obj;
    IBinder binder = clientTransaction.getActivityToken();
   
   Method getServiceMethod = ActivityManager.class.getDeclaredMethod(&#34;getService&#34;);
    Object activityManager = getServiceMethod.invoke(null);

    Method finishActivityMethod = activityManager.getClass().getDeclaredMethod(&#34;finishActivity&#34;, IBinder.class, int.class, Intent.class, int.class);
    finishActivityMethod.setAccessible(true);
    finishActivityMethod.invoke(activityManager, binder, Activity.RESULT_CANCELED, null, 0);
}</code></pre></div></div><p>啊,终于搞定了,但是小光还是一脸疑惑的看着我:</p><p>“我还是去看<code>Cockroach</code>库的源码吧~”</p><p>“我去,,”</p><h3 id="efmdu" name="%E6%80%BB%E7%BB%93">总结</h3><p>今天主要就说了一件事:如何捕获程序中的异常不让APP崩溃,从而给用户带来最好的体验。主要有以下做法:</p><ul class="ul-level-0"><li>通过在主线程里面发送一个消息,捕获主线程的异常,并在异常发生后继续调用<code>Looper.loop</code>方法,使得主线程继续处理消息。</li><li>对于子线程的异常,可以通过<code>Thread.setDefaultUncaughtExceptionHandler</code>来拦截,并且子线程的停止不会给用户带来感知。</li><li>对于在生命周期内发生的异常,可以通过替换<code>ActivityThread.mH.mCallback</code>的方法来捕获,并且通过<code>token</code>来结束Activity或者直接杀死进程。</li></ul><p>可能有的朋友会问,为什么要让程序不崩溃呢?会有<strong>哪些情况</strong>需要我们进行这样操作呢?</p><p>其实还是有很多时候,有些异常我们<code>无法预料</code>或者给用户带来几乎是<code>无感知</code>的异常,比如:</p><ul class="ul-level-0"><li>系统的一些bug</li><li>第三方库的一些bug</li><li>不同厂商的手机带来的一些bug</li></ul><p>等等这些情况,我们就可以通过这样的操作来让<code>APP</code>牺牲掉这部分的功能来维护系统的稳定性。</p><h3 id="4r9po" name="%E5%8F%82%E8%80%83">参考</h3><p>Cockroach</p><p>一文读懂 Handler 机制全家桶</p><p>zyogte进程(Java篇)</p><p>wanAndroid</p><h3 id="bs1uc" name="%E6%8B%9C%E6%8B%9C">拜拜</h3><p>好了,到了说再见的时候了。</p><p>最后给大家推荐一个剧—<strong>棋魂</strong>,嘿嘿,小光就是里面的主角。</p><p>这些优秀的开源库又何尝不是指引我们前行进步的光呢~</p><blockquote><p>感谢大家的阅读,有一起学习的小伙伴可以关注下我的公众号——<strong>码上积木</strong>❤️❤️    </p><p>每日三问知识点/面试题,积少成多。</p><p>公众号回复“111”,还可以获取面试题《思考与解答》往期期刊哦~每月更新</p></blockquote>