??最近在項(xiàng)目中集成了Firebase的crash報(bào)告插件步咪,遇到了一個小的問題论皆,由于項(xiàng)目中之前也使用的自定義的Thread.UncaughtExceptionHandler(具體實(shí)現(xiàn)是重啟了app,并屏蔽掉了系統(tǒng)的應(yīng)用程序停止的彈框),導(dǎo)致覆蓋掉了Firebase這個對異常處理的設(shè)置.
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這個是個什么東西呢点晴,了解的同學(xué)可能大致都明白,它是一個由系統(tǒng)收集線程異常并可以被進(jìn)行處理的一個時機(jī)悯周,最終觸發(fā)uncaughtException這個方法的執(zhí)行粒督,意味著我們可以自定義設(shè)置這個handler,當(dāng)系統(tǒng)發(fā)生異常時,我們可以進(jìn)行自己的處理.首先分析下它的設(shè)值的實(shí)現(xiàn)禽翼,下面看代碼屠橄,其實(shí)比較好理解.
/**
* 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是一個靜態(tài)屬性锐墙,即說明了它在java程序中是全局唯一的對象.至于get方法,是在哪里被調(diào)用长酗,我們就不討論了.不知道大家是否有印象贮匕,每次我們開發(fā)的app出錯的時候,系統(tǒng)就給我們彈了一個經(jīng)典的提示花枫,應(yīng)用程序停止運(yùn)行,其實(shí)它里面也是根據(jù)這個UncaughtExceptionHandler來實(shí)現(xiàn)的.我們可以簡單看看掏膏,我們知道劳翰,一個應(yīng)用的啟動,系統(tǒng)是幫我們做了很多事情的馒疹,那么系統(tǒng)是在什么時候幫我們設(shè)置這個UncaughtExceptionHandler的呢.我以android 5.1的代碼為例子佳簸,簡單看看這個設(shè)置的地方.
在源碼RuntimeInit這個類中,定義了一個默認(rèn)的UncaughtHandler颖变,它的定義如下生均,實(shí)現(xiàn)了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);
}
}
}
當(dāng)系統(tǒng)出現(xiàn)異常時,常見的就是那幾種運(yùn)行時異常腥刹,空指針等等马胧,這個時候,它的uncaughtException方法就會被觸發(fā)衔峰,可以看到在這個方法中佩脊,try代碼塊中最重要的就是把這個事件上報(bào)給了AMS蛙粘,上報(bào)完成之后,最后finally代碼塊就是調(diào)用結(jié)束這個進(jìn)程.AMS中收到這個上報(bào)之后威彰,就會進(jìn)行一些相關(guān)處理出牧,比如系統(tǒng)那個"應(yīng)用程序停止",就是在AMS中進(jìn)行處理的.這里我也就不繼續(xù)跟進(jìn)了歇盼,畢竟android整個都是這樣一個C-S的架構(gòu)舔痕,感興趣的同學(xué),可以繼續(xù)源碼分析.
??講了這么多豹缀,似乎還沒有進(jìn)入本文的主題伯复,為什么自定義的CrashHandler和Firebase Crash會有一點(diǎn)沖突呢,原因就在于前面提到的耿眉,Thread的defaultUncaughtExceptionHandler在一個java程序中是全局唯一的對象(不考慮多classloader的情況边翼,抓住問題的重點(diǎn)),并且Firebase Crash初始化的時機(jī)比我們Application更早鸣剪,一般來講我們的CrashHandler大都是在application#onCreat中初始化组底,稍后我們再提到這個.下面我們先看看一般一個自定義CrashHandler的實(shí)現(xiàn).
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筐骇,它內(nèi)部會包裝一個當(dāng)前默認(rèn)的UncaughtExceptionHandler债鸡,進(jìn)行一次包裝,在我們不想要自己處理的時候铛纬,可以直接委托給mDefaultHandler厌均,讓它自行處理,不破壞整個流程.
到這里告唆,我想大家也應(yīng)該大致明白了棺弊,為什么會和firebase crash有所沖突,其實(shí)它也是根據(jù)這個原理擒悬,只不過在uncaughtException這個方法中模她,實(shí)現(xiàn)了自己的一套把錯誤信息上報(bào)的一個工作,并沒有什么很高大上的東西懂牧,我們簡單看看FirebaseCrash它的處理侈净,讓大家有個更深的認(rèn)識.它的代碼默認(rèn)是混淆過的,不過并不影響我們分析.在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實(shí)現(xiàn)類畜侦,和我們自定義的其實(shí)是一個思路,也是直接包裝了系統(tǒng)默認(rèn)的UncaughtExceptionHandler.然后在uncaughtException中躯保,是進(jìn)行了它的錯誤報(bào)告方法旋膳,接著,不打斷系統(tǒng)的默認(rèn)實(shí)現(xiàn)吻氧,委托給當(dāng)前默認(rèn)的UncaughtExceptionHandler繼續(xù)執(zhí)行溺忧,保證整個調(diào)用鏈的完整性.相當(dāng)于是加了一層處理咏连,任何時候,我們也應(yīng)該是要按這個思想編寫代碼鲁森,不破壞原有的行為下祟滴,實(shí)現(xiàn)我們自己想要的功能.
??到這里,CrashHandler和Firebase Crash的沖突我想大家已經(jīng)明白了歌溉,因?yàn)樗某跏蓟瘯r機(jī)比Application還要早垄懂,那么怎么解決這個問題呢,二者如果不可兼得痛垛,那就有點(diǎn)不完美了.其實(shí)解決這個問題有幾個方法草慧,第一就是我們在它之前初始化,提前一步包裝系統(tǒng)的UncaughtExceptionHandler匙头,這樣在Firebase uncaughtException中漫谷,自然會執(zhí)行到我們的CrashHandler的方法,完美解決問題.第二個方法就是蹂析,既然我們已經(jīng)慢了一部舔示,那么,就可以通過反射的方法电抚,達(dá)到第一種方法的效果惕稻,保證UncaughtExceptionHandler這樣一個順序,F(xiàn)irebase -> 自定義CrashHandler -> 系統(tǒng)默認(rèn)的handler蝙叛,這樣也是完全能夠解決問題.一旦系統(tǒng)的handler生效俺祠,我們就無法屏蔽"應(yīng)用程序停止的"彈框了,其實(shí)整體來講也不是什么沖突借帘,可能是看業(yè)務(wù)的需求吧.當(dāng)然上面兩種能滿足我這個業(yè)務(wù)蜘渣,但我不推薦第二種,如果我們有更好的選擇肺然,顯然反射是最后的殺手锏宋梧,因?yàn)樗幸恍┤秉c(diǎn),這里必須依賴于類的名字,會增加代碼的不穩(wěn)定性.
??如何在Firebase Crash插件之前初始化我們的UncaughtExceptionHandler狰挡,既然它也是屬于我們app下的一個模塊而已,那么我們肯定是有辦法在它之前初始化的.Firebase Crash它的初始化也沒有什么很高深的技術(shù)释涛,只不過是抓住了有點(diǎn),在android應(yīng)用程序中,ContentProvider的初始化更加早于application,這點(diǎn)大家可以自行看看源碼ActivityThread這個文件.那么現(xiàn)在問題就簡單了加叁,你不是在Provider里面加載,那么照葫蘆畫瓢唇撬,我們也建一個Provider只要優(yōu)先級比你的高它匕,那么肯定最先加載的是我的,所以最終我這個業(yè)務(wù)的解決方案就是窖认,自定義一個優(yōu)先級更高的Provider豫柬,初始化相關(guān)的代碼告希,保證了最終UncaughtExceptionHandler的包裝順序,F(xiàn)irebase -> 自定義CrashHandler -> 系統(tǒng)默認(rèn)的handler.這樣在我自己的CrashHandler里面烧给,就可以想做什么做什么了燕偶,要不要調(diào)用系統(tǒng)的handler也就完全由自己掌控了,同時crash報(bào)告也完美工作了.