一. 序
Android系統(tǒng)中,拋出Exception 或者 Error都會(huì)導(dǎo)致Crash.進(jìn)而導(dǎo)致App強(qiáng)制退出.簡(jiǎn)單的來(lái)說(shuō)就是因?yàn)閽伋霎惓5拇a.并未被Try catch包圍..就會(huì)導(dǎo)致進(jìn)程被殺.
二. 原理
從Fork進(jìn)程伊始,就已經(jīng)存在的UncaughtExceptionHandler(大致描述了AMS對(duì)于異常處理的過(guò)程.).
1. 進(jìn)程Fork之后就注冊(cè)了一個(gè)UncaughtHandler
//RuntimeInit.java中的zygoteInit函數(shù)
public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
throws ZygoteInit.MethodAndArgsCaller {
............
//跟進(jìn)commonInit
commonInit();
............
}
private static final void commonInit() {
...........
/* set default handler; this applies to all threads in the VM */
//到達(dá)目的地!
Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler());
...........
}
2. 異常處理
當(dāng)UncaughtHandler接收到未捕獲異常的時(shí)候.進(jìn)程會(huì)自殺,并且彈出大家最熟悉不過(guò)的Force Close對(duì)話(huà)框.
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 {
//打印進(jìn)程的crash信息
.............
}
.............
// Bring up crash dialog, wait for it to be dismissed
//調(diào)用AMS的接口,進(jìn)行處理
ActivityManagerNative.getDefault().handleApplicationCrash(
mApplicationObject, new ApplicationErrorReport.CrashInfo(e));
} catch (Throwable t2) {
if (t2 instanceof DeadObjectException) {
// System process is dead; ignore
} else {
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.
//crash的最后,會(huì)殺死進(jìn)程
Process.killProcess(Process.myPid());
//并exit
System.exit(10);
}
}
}
又發(fā)現(xiàn)了神秘的System.exit(10);這里面的魔法數(shù)字.
Difference in System. exit(MagicCode) in Java
3.UncaughtExceptionHandler
3.1 簡(jiǎn)單說(shuō)說(shuō)UncaughtExceptionHandler
UncaughtExceptionHandler存在于Thread中.當(dāng)異常發(fā)生且未捕獲時(shí).異常會(huì)透過(guò)UncaughtExceptionHandler拋出.并且該線(xiàn)程會(huì)消亡.所以在Android中子線(xiàn)程死亡是允許的.主線(xiàn)程死亡就會(huì)導(dǎo)致ANR.
下面是相關(guān)源碼的截取.仔細(xì)閱讀會(huì)發(fā)現(xiàn).Thread中存在兩個(gè)UncaughtExceptionHandler.一個(gè)是靜態(tài)的defaultUncaughtExceptionHandler,另一個(gè)是非靜態(tài)uncaughtExceptionHandler.
- defaultUncaughtExceptionHandler:設(shè)置一個(gè)靜態(tài)的默認(rèn)的UncaughtExceptionHandler.來(lái)自所有線(xiàn)程中的Exception在拋出,并且為捕獲的情況下.都會(huì)從此路過(guò).大家可以看到進(jìn)程fork的時(shí)候設(shè)置的就是這個(gè)靜態(tài)的defaultUncaughtExceptionHandler.管轄范圍為整個(gè)進(jìn)程.
- uncaughtExceptionHandler:為單個(gè)線(xiàn)程設(shè)置一個(gè).屬于線(xiàn)程自己的uncaughtExceptionHandler.也就是說(shuō).他的管轄范圍比較小.
public class Thread implements Runnable {
...........
@FunctionalInterface
public interface UncaughtExceptionHandler {
void uncaughtException(Thread t, Throwable e);
}
// null unless explicitly set
private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
// null unless explicitly set
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
defaultUncaughtExceptionHandler = eh;
}
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
checkAccess();
uncaughtExceptionHandler = eh;
}
...
}
3.2 UncaughtExceptionHandler的"職責(zé)鏈"
當(dāng)我們自定義一個(gè)
CrashHandler并register(),本質(zhì)上這個(gè)CrashHandler就已經(jīng)持有進(jìn)程中上一個(gè)注冊(cè)成DefaultUncaughtExceptionHandler的引用..并且將自己設(shè)置成進(jìn)程中DefaultUncaughtExceptionHandler.異常來(lái)了.我們先在
uncaughtException中處理,如果不攔截.就包裝一些擴(kuò)展信息,并且交給我這持有的引用mUncaughtExceptionHandler繼續(xù)處理.大家可能看出來(lái)了.這是一個(gè)鏈?zhǔn)降慕Y(jié)構(gòu).直到丟給最后進(jìn)程中的UncaughtExceptionHandler.然后就ForceClose了.
public class CrashHandler implements Thread.UncaughtExceptionHandler {
private Thread.UncaughtExceptionHandler mUncaughtExceptionHandler;
......
void register() {
mUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
}
}
@Override public void uncaughtException(Thread thread, Throwable throwable) {
//做些事情
......
if(心情不好){
return;
}
//心情不好的話(huà),異常就不能繼續(xù)傳遞了.
mUncaughtExceptionHandler.uncaughtException(thread, facadeThrowable);
}
......
}
總結(jié):很重要!很重要!!很重要!!!
UncaughtExceptionHandler是以鏈?zhǔn)浇Y(jié)構(gòu)存在.原則上,誰(shuí)后注冊(cè)的,誰(shuí)優(yōu)先處理異常,并且決定,這個(gè)異常是否交給上一個(gè)注冊(cè)的.這點(diǎn)很重要,牢記!假如我們注冊(cè)在其他UncaughtExceptionHandler后邊很有可能導(dǎo)致,因?yàn)樗麄儾⑽蠢^續(xù)傳遞Exception.導(dǎo)致一些其他問(wèn)題.所以我們要注冊(cè)在最后.以便優(yōu)先處理.
三. App層可以做的Crash防護(hù)
1.Crash統(tǒng)計(jì)平臺(tái)
例如Fabric等錯(cuò)誤日志上報(bào)平臺(tái),可以當(dāng)Crash發(fā)生時(shí),收集異常信息.到平臺(tái),此處不擴(kuò)展講.網(wǎng)上相關(guān)文檔很多.本質(zhì)也是注冊(cè)一個(gè)UncaughtExceptionHandler然后將Throw上報(bào)給服務(wù)器.后續(xù)介紹都會(huì)以Fabric為例說(shuō)明.
2. try-catch大法好.
Java的異常處理可以讓程序具有更好的容錯(cuò)性,程序更加健壯。當(dāng)程序運(yùn)行出現(xiàn)意外時(shí),系統(tǒng)會(huì)自動(dòng)生成一個(gè)Exception對(duì)象來(lái)通知程序。大家肯定會(huì)考慮到性能損耗問(wèn)題。畢竟做了“額外”的事情。這里我從兩種方式去探究一下:
寫(xiě)兩個(gè)一樣邏輯的函數(shù),只不過(guò)一個(gè)包含try-catch代碼塊,一個(gè)不包含,分別循環(huán)調(diào)用百萬(wàn)次,通過(guò)System.nanoTime()來(lái)比較兩個(gè)函數(shù)百萬(wàn)次調(diào)用的耗時(shí)。本機(jī)跑了一下基本上沒(méi)什么區(qū)別。
可以看看.java文件經(jīng)過(guò)編譯生成的JVM可以執(zhí)行的.class文件里的字節(jié)碼指令。
javap -verbose ReturnValueTest xx.class 命令可以查看字節(jié)碼
《深入Java虛擬機(jī)》作者Bill Venners于1997年所寫(xiě)的文章How the Java virtual machine handles exceptions比較詳盡地分析了一番。文章從反編譯出的指令發(fā)現(xiàn)加了try-catch塊的代碼跟沒(méi)有加的代碼運(yùn)行時(shí)的指令是完全一致的(你也可以按照上面命令自行進(jìn)行對(duì)比)。 如果程序運(yùn)行過(guò)程中不產(chǎn)生異常的話(huà)try catch 幾乎是不會(huì)對(duì)運(yùn)行產(chǎn)生任何影響的。只是在產(chǎn)生異常的時(shí)候jvm會(huì)追溯異常調(diào)用棧。這部分耗時(shí)就相對(duì)較高了。
3.上文地提到的"職責(zé)連"
我們能做的就是在所有第三方的UncaughtExceptionHandler注冊(cè)之后,注冊(cè)一個(gè)自己的CrashHandler。這樣我們就可以在第一時(shí)間接收到異常之后。做異常攔截或者異常包裝。
4.異常攔截
上文同樣提到了。我們注冊(cè)一個(gè)自己的CrashHandler的目的之一,就是優(yōu)先與異常見(jiàn)面。當(dāng)發(fā)現(xiàn)可以攔截的異常的時(shí)候。就不將其繼續(xù)傳遞.異常攔截 最重要的原則 就是不能攔截主線(xiàn)程中的異常:
這是一段異常攔截的代碼:
@Override public void uncaughtException(Thread thread, Throwable throwable) {
//先嘗試攔截?cái)r截
if (crashInterceptor()) {
return;
}
uncaughtExceptionHandler.uncaughtException(thread, facadeThrowable);
}
//異常攔截器
public static boolean crashInterceptor(Throwable throwable, Thread thread) {
if (thread.getId() == 1
|| throwable == null
|| throwable.getMessage() == null
|| throwable.getStackTrace() == null) {
//異常發(fā)生之后,所在線(xiàn)程會(huì)掛掉.所以主線(xiàn)程異常,攔截了也沒(méi)用.主線(xiàn)程也會(huì)死掉.
//除非,后續(xù)判斷,app在前臺(tái),觸發(fā)APP重啟.
return false;
}
String classpath = null;
if (throwable.getStackTrace() != null && throwable.getStackTrace().length > 0) {
classpath = throwable.getStackTrace()[0].toString();
}
if (classpath == null) {
return false;
}
//攔截GMS異常.
if (throwable.getMessage().contains("Results have already been set") && classpath.contains(
"com.google.android.gms")) {
logException(throwable);
return true;
}
//攔截GMS的 NPE.
if (classpath.contains("com.google.android.gms") && throwable instanceof NullPointerException) {
CrashHelper.logException(CrashFacade.facadeThrowable(throwable));
return true;
}
//攔截ssl_NPE
if (throwable instanceof NullPointerException && throwable.getMessage()
.contains("ssl_session == null")) {
CrashHelper.logException(CrashFacade.facadeThrowable(throwable));
return true;
}
return false;
}
5.異常信息包裝上傳
當(dāng)我們用了平臺(tái)之后,發(fā)現(xiàn)除了我們自己能看到的又明確調(diào)用棧的異常信息。還有許許多多看不到調(diào)用棧的。或者是第三方SDK里的Crash.這些Crash因?yàn)闆](méi)有調(diào)用棧。一直是個(gè)很頭疼的問(wèn)題。
此處我們追加的部分信息的截圖.兩個(gè)例子.沒(méi)有調(diào)用棧的情況.
這是我在StackOverFlow上和Google Issue Tracker上的提問(wèn)
StackOverFlow : GMS IllegalStateException : Results have already been set?
Google Issue Tracker : GMS Results have already been set
- GMS IllegalStateException
- finalize() timedout after 10 seconds
追加擴(kuò)展信息代碼如下:
@Override public void uncaughtException(Thread thread, Throwable throwable) {
//先嘗試攔截?cái)r截
if (crashInterceptor()) {
return;
}
//再包裝擴(kuò)展信息,交給Fabric上報(bào)服務(wù)器
Throwable facadeThrowable = facadeThrowable(throwable , "<HelloWorld>");
uncaughtExceptionHandler.uncaughtException(thread, facadeThrowable);
}
//通過(guò)反射,在detailMessage后面追加信息
public static Throwable facadeThrowable(Throwable throwable , String facadeMessage) {
try {
Field field = getDeclaredField(throwable, "detailMessage");
if (field == null) {
return throwable;
}
field.setAccessible(true);
String originDetailMessage = (String) field.get(throwable);
String newDetailMessage = originDetailMessage + facadeMessage;
field.set(throwable, newDetailMessage);
} catch (Exception ignore) {
CrashHelper.logExceptionWithoutFacade(ignore);
}
return throwable;
}
我這里都是先通過(guò)包裝Crash.收集沒(méi)有調(diào)用棧信息異常和第三方庫(kù)的異常的所在線(xiàn)程.在謹(jǐn)慎的增加對(duì)應(yīng)的異常攔截.確保沒(méi)有在主線(xiàn)程中攔截異常..畢竟ANR了..也不合適.就直接掛掉吧...所以先收集包裝信息.再?zèng)Q定攔截哪些異常
WARNING:之前嘗試.在UnCaughtHandler中,將Exception放到一個(gè)new Throwable()的cause中.并追加信息.這種方式會(huì)導(dǎo)致平臺(tái)日志堆疊,因?yàn)閚ew Throwable都產(chǎn)生在同樣的地方.平臺(tái)會(huì)把日志合并.所以才考慮用反射的方法加到detailMessage后面的.
Fabric會(huì)給與
- Crash堆疊在了一起
- 調(diào)用棧都跑到我的uncaughtException里了.
大結(jié)局
- finalize() timedout after 10 seconds
哦?FinalizerWatchdogDaemon是什么線(xiàn)程?
引發(fā)了我研究從Daemons到finalize timed out after 10 seconds這個(gè)問(wèn)題



