FirebaseCrash和自定义Thread.UncaughtExceptionHandler的兼容处理

  最近在项目中集成了Firebase的crash报告插件,遇到了一个小的问题,由于项目中之前也使用的自定义的Thread.UncaughtExceptionHandler(具体实现是重启了app,并屏蔽掉了系统的应用程序停止的弹框),导致覆盖掉了Firebase这个对异常处理的设置.

public interface UncaughtExceptionHandler {
    /**
     * Method invoked when the given thread terminates due to the
     * given uncaught exception.
     * <p>Any exception thrown by this method will be ignored by the
     * Java Virtual Machine.
     * @param t the thread
     * @param e the exception
     */
    void uncaughtException(Thread t, Throwable e);
}

  上面是UncaughtExceptionHandler这个接口的定义,Thread.UncaughtExceptionHandler这个是个什么东西呢,了解的同学可能大致都明白,它是一个由系统收集线程异常并可以被进行处理的一个时机,最终触发uncaughtException这个方法的执行,意味着我们可以自定义设置这个handler,当系统发生异常时,我们可以进行自己的处理.首先分析下它的设值的实现,下面看代码,其实比较好理解.

/**
 * Set the default handler invoked when a thread abruptly terminates
 * due to an uncaught exception, and no other handler has been defined
 * for that thread.
 *
 */
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
     defaultUncaughtExceptionHandler = eh;
 }

/**
 * Returns the default handler invoked when a thread abruptly terminates
 * due to an uncaught exception. If the returned value is <tt>null</tt>,
 */
public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
    return defaultUncaughtExceptionHandler;
}

  上面是它在Thread类中的set和get方法,可以看到,defaultUncaughtExceptionHandler是一个静态属性,即说明了它在java程序中是全局唯一的对象.至于get方法,是在哪里被调用,我们就不讨论了.不知道大家是否有印象,每次我们开发的app出错的时候,系统就给我们弹了一个经典的提示,应用程序停止运行,其实它里面也是根据这个UncaughtExceptionHandler来实现的.我们可以简单看看,我们知道,一个应用的启动,系统是帮我们做了很多事情的,那么系统是在什么时候帮我们设置这个UncaughtExceptionHandler的呢.我以android 5.1的代码为例子,简单看看这个设置的地方.

在源码RuntimeInit这个类中,定义了一个默认的UncaughtHandler,它的定义如下,实现了Thread类的UncaughtExceptionHandler接口.

/**
 * Use this to log a message when a thread exits due to an uncaught
 * exception.  The framework catches these for the main threads, so
 * this should only matter for threads created by applications.
 */
private static class UncaughtHandler implements Thread.UncaughtExceptionHandler {
    public void uncaughtException(Thread t, Throwable e) {
        try {
            // Don't re-enter -- avoid infinite loops if crash-reporting crashes.
            if (mCrashing) return;
            mCrashing = true;

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

            // Bring up crash dialog, wait for it to be dismissed
            ActivityManagerNative.getDefault().handleApplicationCrash(
                    mApplicationObject, new ApplicationErrorReport.CrashInfo(e));
        } catch (Throwable t2) {
            try {
                Clog_e(TAG, "Error reporting crash", 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);
        }
    }
}

当系统出现异常时,常见的就是那几种运行时异常,空指针等等,这个时候,它的uncaughtException方法就会被触发,可以看到在这个方法中,try代码块中最重要的就是把这个事件上报给了AMS,上报完成之后,最后finally代码块就是调用结束这个进程.AMS中收到这个上报之后,就会进行一些相关处理,比如系统那个"应用程序停止",就是在AMS中进行处理的.这里我也就不继续跟进了,毕竟android整个都是这样一个C-S的架构,感兴趣的同学,可以继续源码分析.

  讲了这么多,似乎还没有进入本文的主题,为什么自定义的CrashHandler和Firebase Crash会有一点冲突呢,原因就在于前面提到的,Thread的defaultUncaughtExceptionHandler在一个java程序中是全局唯一的对象(不考虑多classloader的情况,抓住问题的重点),并且Firebase Crash初始化的时机比我们Application更早,一般来讲我们的CrashHandler大都是在application#onCreat中初始化,稍后我们再提到这个.下面我们先看看一般一个自定义CrashHandler的实现.

public class CrashHandler implements Thread.UncaughtExceptionHandler {

    private Thread.UncaughtExceptionHandler mDefaultHandler;

    public CrashHandler(Thread.UncaughtExceptionHandler uncaughtExceptionHandler) {
        mDefaultHandler = uncaughtExceptionHandler;
    }

    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
      // ..... self handler.
    }
}

// init code.
CrashHandler crashHandler = new CrashHandler(Thread.getDefaultUncaughtExceptionHandler());
Thread.setDefaultUncaughtExceptionHandler(crashHandler);

一般的,我们自定义的CrashHandler,它内部会包装一个当前默认的UncaughtExceptionHandler,进行一次包装,在我们不想要自己处理的时候,可以直接委托给mDefaultHandler,让它自行处理,不破坏整个流程.

到这里,我想大家也应该大致明白了,为什么会和firebase crash有所冲突,其实它也是根据这个原理,只不过在uncaughtException这个方法中,实现了自己的一套把错误信息上报的一个工作,并没有什么很高大上的东西,我们简单看看FirebaseCrash它的处理,让大家有个更深的认识.它的代码默认是混淆过的,不过并不影响我们分析.在FirebaseCrash这个文件中,我的firebase版本是11.8.0

class zzc implements UncaughtExceptionHandler {
       private final UncaughtExceptionHandler zzmin;

       public zzc(@Nullable UncaughtExceptionHandler var2) {
           this.zzmik = FirebaseCrash.this;
           super();
           this.zzmin = var2;
       }

       public final void uncaughtException(Thread var1, Throwable var2) {
           Log.e("UncaughtException", "", var2);
           if(!this.zzmik.zzbsm()) {
               try {
                   Future var3;
                   if((var3 = this.zzmik.zzh(var2)) != null) {
                       var3.get(10000L, TimeUnit.MILLISECONDS);
                   }
               } catch (Exception var4) {
                   Log.e("UncaughtException", "Ouch! My own exception handler threw an exception.", var4);
               }
           }

           if(this.zzmin != null) {
               this.zzmin.uncaughtException(var1, var2);
           }

       }
   }

// init code.
com.google.firebase.crash.zzc var4 = new com.google.firebase.crash.zzc(var0, (String)null);
              Thread.setDefaultUncaughtExceptionHandler(var3.new zzc(Thread.getDefaultUncaughtExceptionHandler()));

可以看到,zzc这个就是它自定义的一个UncaughtExceptionHandler实现类,和我们自定义的其实是一个思路,也是直接包装了系统默认的UncaughtExceptionHandler.然后在uncaughtException中,是进行了它的错误报告方法,接着,不打断系统的默认实现,委托给当前默认的UncaughtExceptionHandler继续执行,保证整个调用链的完整性.相当于是加了一层处理,任何时候,我们也应该是要按这个思想编写代码,不破坏原有的行为下,实现我们自己想要的功能.

  到这里,CrashHandler和Firebase Crash的冲突我想大家已经明白了,因为它的初始化时机比Application还要早,那么怎么解决这个问题呢,二者如果不可兼得,那就有点不完美了.其实解决这个问题有几个方法,第一就是我们在它之前初始化,提前一步包装系统的UncaughtExceptionHandler,这样在Firebase uncaughtException中,自然会执行到我们的CrashHandler的方法,完美解决问题.第二个方法就是,既然我们已经慢了一部,那么,就可以通过反射的方法,达到第一种方法的效果,保证UncaughtExceptionHandler这样一个顺序,Firebase -> 自定义CrashHandler -> 系统默认的handler,这样也是完全能够解决问题.一旦系统的handler生效,我们就无法屏蔽"应用程序停止的"弹框了,其实整体来讲也不是什么冲突,可能是看业务的需求吧.当然上面两种能满足我这个业务,但我不推荐第二种,如果我们有更好的选择,显然反射是最后的杀手锏,因为它有一些缺点,这里必须依赖于类的名字,会增加代码的不稳定性.

  如何在Firebase Crash插件之前初始化我们的UncaughtExceptionHandler,既然它也是属于我们app下的一个模块而已,那么我们肯定是有办法在它之前初始化的.Firebase Crash它的初始化也没有什么很高深的技术,只不过是抓住了有点,在android应用程序中,ContentProvider的初始化更加早于application,这点大家可以自行看看源码ActivityThread这个文件.那么现在问题就简单了,你不是在Provider里面加载,那么照葫芦画瓢,我们也建一个Provider只要优先级比你的高,那么肯定最先加载的是我的,所以最终我这个业务的解决方案就是,自定义一个优先级更高的Provider,初始化相关的代码,保证了最终UncaughtExceptionHandler的包装顺序,Firebase -> 自定义CrashHandler -> 系统默认的handler.这样在我自己的CrashHandler里面,就可以想做什么做什么了,要不要调用系统的handler也就完全由自己掌控了,同时crash报告也完美工作了.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,076评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,658评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,732评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,493评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,591评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,598评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,601评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,348评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,797评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,114评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,278评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,953评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,585评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,202评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,180评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,139评论 2 352

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,014评论 25 707
  • 靠近你 那一叶草一瓣花 那一缕光一曲歌 亲临你的温度 正如 载着骤降的风度 在惆帐的夜里 慢慢渗透 于是 久积的暖...
    安清儿阅读 146评论 1 3
  • 有一种孤独是 与志同道合的人定下目标,没皮没脸地往前冲,等到离光明不远的时候,你扭头一看,却发现志同道合的人已经不...
    没有什么昵称阅读 123评论 0 0