最近在项目中集成了Firebase的crash报告插件,遇到了一个小的问题,由于项目中之前也使用的自定义的Thread.UncaughtExceptionHandler(具体实现是重启了app,并屏蔽掉了系统的应用程序停止的弹框),导致覆盖掉了Firebase这个对异常处理的设置.
public interface UncaughtExceptionHandler {
/**
* Method invoked when the given thread terminates due to the
* given uncaught exception.
* 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 null,
*/
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报告也完美工作了.