需求
前一段時間由于同事的一個RecyclerView的錯誤使用方式,導致了線上的App有部分使用者出現(xiàn)了頁面閃退的情況,RecyclerView的LinearLayout拋出了IndexOutOfBoundsException.具體細節(jié)就不提了,由于數(shù)據(jù)刷新時列表滾動調(diào)用onLayoutChilden時報出的.
但是由于這件事情,萌生了一個想法,能不能前臺頁面報異常歸報異常,但盡可能減少閃退不要閃退.
構(gòu)思
用try catch包住所有的代碼段肯定是可行的,但是這方法也太蠢了….于是開始在網(wǎng)上查找資料,發(fā)現(xiàn)在一個地方可以動動手腳做做文章,就是Looper.
Android的系統(tǒng)運行機制里簡化的來看,一個App程序可以認為是在一個進程中執(zhí)行了了一段無限循環(huán)的Java代碼,反復的在從消息隊列里面取出消息并執(zhí)行,也就是Looper,MessageQueue(MQ),Handler,消息隊列我們動不了什么手腳,但是Looper和Handler就可以做做文章了
Android系統(tǒng)中的那段在無限循環(huán)讀取消息隊列的代碼,是在Looper.loop()方法中被調(diào)用的,而本身異常捕獲可以try catch,而多線程的異常則通過Java Thread的setDefaultUncaughtExceptionHandler來捕獲,思路就是醬紫了.
實現(xiàn)
創(chuàng)建了一個類GuardianAngel,其實定義了3個靜態(tài)成員變量,分別是標識位guarded,用來保存原本handler的sUncaughtExceptionHandler和用來處理捕獲到的異常的sExceptionHandler.核心方法如下
public static void protect(ExceptionHandler exceptionHandler) {
if (guarded) {
return;
}
if (exceptionHandler != null) {
sExceptionHandler = exceptionHandler;
}
sUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
sExceptionHandler.onException(e);
}
});
guarded = true;
while (guarded) {
try {
Looper.getMainLooper().loop();
} catch (Throwable e) {
sExceptionHandler.onException(e);
}
}
}
非常簡單粗暴,首先判斷標志位以及判斷傳進來的Handler是不是空的(類里面有個默認的Log.e輸出堆棧的實現(xiàn)),然后記錄下原有的UncaughtExceptionHandler,放一個新的由我們自己的sExceptionHandler代理的Handler,然后將狀態(tài)標志改變并寫一個死循環(huán),手動的調(diào)用MainLooper.loop()方法讀取messagequeue;
原本主線程的MainLooper執(zhí)行到我們的這個方法里的時候會在while (guarded)里陷入死循環(huán),但是由于我們手動調(diào)用著MainLooper.loop()方法,于是系統(tǒng)looper依然在按照原樣子讀消息隊列,只是這次loop方法已經(jīng)被我們try catch住了,Java異常都會被捕獲不再導致閃退.Native Crash和ANR依然會導致程序退出.
想終止這種就調(diào)用方法將guarded重新置為false,并且將原本的DefaultExceptionHandler重新set回去就好了,這段代碼蠻簡單的就不給出了.