Android穩(wěn)定性:可遠程配置化的Looper兜底框架
正文
App Crash對于用戶來講是一種最糟糕的體驗,它會導致流程中斷、app口碑變差、app卸載、用戶流失、訂單流失等。相關(guān)數(shù)據(jù)顯示,當Android App的崩潰率超過0.4%的時候,活躍用戶有明顯下降態(tài)勢。
目前受益于我司采取的一系列的治理、監(jiān)控、防劣化體系,java crash率降低到了一個十萬分級別的數(shù)字**,**今天分享的就是穩(wěn)定性治理過程中的一個重要工具,下面開整。
1. 為什么拋出異常時app會退出
不細致分析了,網(wǎng)上隨便找一下就是一堆博客,簡單來說就是沒有被catch的崩潰拋出時,會調(diào)用 Thread#dispatchUncaughtException(throwable)
來進行處理,而在進程初始化時,RuntimeInit#commonInit
里會注入默認殺進程的 KillApplicationHandler
,如果我們沒有實現(xiàn)自定義的 UncaughtExceptionHandler
時,dispatchUncaughtException
被調(diào)用就會走到 KillApplicationHandler
里,把當前的進程殺掉,即產(chǎn)生了一次用戶感知的崩潰行為。
2. 有沒有辦法打造一個永不崩潰的app
這個問題問出來的前提是指發(fā)生的崩潰是來自于 java 層面的未捕獲的異常,c 層就是另一回事了。我們來嘗試回答一下這個問題:
答:可以,至少可以做到把所有的異常都吃掉。
問:那么怎么做呢?
答:當某個線程發(fā)生異常時,只要不讓 KillApplicationHandler
處理這個異常就行了,即只要覆蓋掉默認的 UncaughtExceptionHandler
就行了噢。
問:那當這樣做的時候,比如主線程拋出一個異常被吃掉了,app還能正常運行嗎?
答:不能了,因為不做任何處理的話,當前線程的 Looper.loop()
就被終止了。如果是主線程的話,此時你將會獲得一個 anr
。
問:怎么才能在吃掉異常的同時,讓主線程繼續(xù)運行呢?
答:當由于異常拋出,導致線程的 Looper.loop()
終止之后,接管 Looper.loop()
。代碼大概長下面這樣:
public class AppCrashHandler implements UncaughtExceptionHandler { @Override public void uncaughtException(@NonNull Thread thread, @NonNull Throwable ex) { while (true) { try { if (Looper.myLooper() == null) { Looper.prepare(); } Looper.loop(); } catch (Exception e) { } } } }
上面這段代碼,就是我標題中被描述為 Looper 兜底框架的實現(xiàn)機制。但是對于一個正常的app,線上是不可能這樣無腦的catch,然后 Looper.loop
的,這是因為:
- 不是所有的異常都需要被catch住,如:
OOM、launcher Activity onCreate
之類的。 - 穩(wěn)定性不是靠屏蔽問題,而是靠解決問題,當異常無法解決或者解決成本太高,且異常被屏蔽對用戶、業(yè)務來說并沒有啥實質(zhì)性的影響時,可以被屏蔽,當異常拋出時已經(jīng)對業(yè)務產(chǎn)生了破壞,但是通過保護住然后重試可以讓業(yè)務恢復運作時,也可以被屏蔽,只是多了個環(huán)節(jié),即修復異常。
問:到底什么異常需要被吃掉呢?
上一個回答中我們大致將需要被吃掉的異常分了兩類
- 異常我們無法解決或者解決成本太高
舉個例子,假如公司有使用 react native 之類的三方大框架,當業(yè)務拋出來一個如下的異常時,我們就可以認為這無法解決。
com.facebook.react.bridge.JSApplicationIllegalArgumentException: connectAnimatedNodes: Animated node with tag (child) [30843] does not exist at com.facebook.react.animated.NativeAnimatedNodesManager.connectAnimatedNodes(NativeAnimatedNodesManager.java:7) at com.facebook.react.animated.NativeAnimatedModule$16.execute at com.facebook.react.animated.NativeAnimatedModule$ConcurrentOperationQueue.executeBatch(NativeAnimatedModule.java:7) at com.facebook.react.animated.NativeAnimatedModule$3.execute at com.facebook.react.uimanager.UIViewOperationQueue$UIBlockOperation.execute at com.facebook.react.uimanager.UIViewOperationQueue$1.run(UIViewOperationQueue.java:19) at com.facebook.react.uimanager.UIViewOperationQueue.flushPendingBatches(UIViewOperationQueue.java:10) at com.facebook.react.uimanager.UIViewOperationQueue.access$2600 at com.facebook.react.uimanager.UIViewOperationQueue$DispatchUIFrameCallback.doFrameGuarded(UIViewOperationQueue.java:6) at com.facebook.react.uimanager.GuardedFrameCallback.doFrame(GuardedFrameCallback.java:1) at com.facebook.react.modules.core.ReactChoreographer$ReactChoreographerDispatcher.doFrame(ReactChoreographer.java:7) at com.facebook.react.modules.core.ChoreographerCompat$FrameCallback$1.doFrame at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1118) at android.view.Choreographer.doCallbacks(Choreographer.java:926) at android.view.Choreographer.doFrame(Choreographer.java:854) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1105) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:238) at android.os.Looper.loop(Looper.java:379) at android.app.ActivityThread.main(ActivityThread.java:9271) at java.lang.reflect.Method.invoke(Method.java) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:567) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1018)
2. 異常被屏蔽對用戶、業(yè)務來說并沒有實質(zhì)性影響
- 比如老生常談的 Android 7.x toast的 BadTokenException
之類的系統(tǒng)崩潰,一呢發(fā)生概率非常低,二呢在Android 8
上的修復方式也只是 try-catch
住。
- 一些不影響業(yè)務、用戶使用的三方庫崩潰,比如瞎說一個,當使用 OkHttp 在請求接口時,內(nèi)部切了個線程執(zhí)行了個更新緩存的任務,結(jié)果里面拋出了一個 NPE 。外面沒法 try-catch
,而且這個異常拋出時,頂多下次請求不走緩存,實際上沒啥太大影響。
3. 異常很嚴重,但是吃掉之后通過修復運行環(huán)境能夠讓用戶所使用的業(yè)務恢復正常運行
比如我們想要保存一張圖片到磁盤上,但是磁盤滿了, 拋出了一個no space left
,這時候我們就可以將異常吃掉,同時清空app的磁盤緩存,并且告知用戶重試,就可以成功的讓用戶保存圖片成功
3. 如何Looper兜底框架輔助穩(wěn)定性治理
我們先明確一下什么崩潰需要通過這種手段來治理、兜底:
系統(tǒng)崩潰,如老生常談的 Android 7.x toast的 BadTokenException
三方庫的無痛崩潰,比如公司有使用 react native
之類的三方大框架,沒有能力改或者不想改一些相關(guān)的 ui 引起的 崩潰,比如做動畫時莫名其妙的拋出異常
一些特殊崩潰,如磁盤空間不足引發(fā)的 no space left
,可以嘗試通過抓住崩潰同時清理一波app的磁盤緩存,再嘗試繼續(xù)運行。
其他...
問:為什么我的標題中強調(diào)了可遠程配置化呢?
答:因為可遠程配置化能夠為框架本身賦能更多。
問:比如?
答:可以提供一種簡易的線上容災機制,假如線上在某個頁面發(fā)生了一個崩潰,這個崩潰突然發(fā)生而且崩潰發(fā)生的點本身對業(yè)務來說無關(guān)緊要(比如有個開發(fā)手xx,Integer.parse
整了個漢字,拋異常了),通過熱修復來修吧,流程復雜,要改代碼、打補丁包、配補丁包。緊急發(fā)版吧,成本比熱修高了不知多少倍,這時如果有一個可配置化的Looper兜底框架,我通過更新我的配置,保護住這個 Integer.parse 異常,就能很輕松的解決線上問題。
4. 可配置化配置的是什么東西
首先這是一個崩潰保護的框架,那么配置的肯定是能描述崩潰的內(nèi)容,那么什么東西能描述一個崩潰呢?無非就是以下元素:
- throwable class name
- throwable message
- throwable stacktrace
- Android version
- app version
- model
- brand
- ...
大致就是對崩潰做個標簽匹配:這是個什么崩潰,發(fā)生在哪個Android版本,發(fā)生在哪個App版本,發(fā)生在哪個廠商哪個系統(tǒng)版本上。
5. 我們怎么做的
我們的畫像標簽大致長下面這樣:
[ { "class": "", "message": "No space left on device", "stack": [], "app_version": [], "clear_cache": 1, "finish_page": 0, "toast": "", "os_version": [], "model": [] }, { "class": "BadTokenException", "message": "", "stack": [], "app_version": [], "clear_cache": 0, "finish_page": 0, "toast": "", "os_version": [], "model": [] } ]
配置里還加了一些額外的東西,比如:
- 崩潰被保護住的時候,要不要清理下app的緩存
- 崩潰被保護住的時候,要不要彈個 toast 告知用戶
- 崩潰被保護住的時候,要不要退出當前頁面
就這樣,我們的可配置化的Looper兜底框架的全貌就描述完了,最后再總結(jié)一下具體的工作流程吧。
Looper兜底流程:
我們會注入自己的 UncaughtExceptionHandler
,當App產(chǎn)生了一個未捕獲的異常時,我們通過對這個異常進行幾個標簽的匹配來判斷當前的崩潰是否要進行保護,當需要保護時,接管Looper.loop
,讓線程繼續(xù)運行。
配置更新、生效流程:
當App啟動時,拉取遠程的崩潰畫像配置,當未捕獲的異常發(fā)生時,讀取本地最新的配置,進行標簽匹配,如果標簽匹配成功,進行Looper兜底。
以上就是Android穩(wěn)定性:可遠程配置化的Looper兜底框架的詳細內(nèi)容,更多關(guān)于Android遠程配置化Looper框架的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android開發(fā)之PopupWindow創(chuàng)建彈窗、對話框的方法詳解
這篇文章主要介紹了Android開發(fā)之PopupWindow創(chuàng)建彈窗、對話框的方法,結(jié)合實例形式詳細分析了Android使用PopupWindow創(chuàng)建對話框相關(guān)操作技巧,需要的朋友可以參考下2019-03-03Android中ListView綁定CheckBox實現(xiàn)全選增加和刪除功能(DEMO)
本文通過實例給大家講解了Android中ListView綁定CheckBox實現(xiàn)全選增加和刪除功能(DEMO)的代碼,對android checkbox全選相關(guān)知識感興趣的朋友一起學習吧2016-08-08詳解Android的網(wǎng)絡數(shù)據(jù)存儲
LeanCloud是一種簡單高效的數(shù)據(jù)和文件存儲服務,本文主要介紹了利用LeanCloud來進行網(wǎng)絡數(shù)據(jù)的存儲的實現(xiàn)方法。具有很好的參考價值,需要的朋友一起來看下吧2016-12-12Android開發(fā)之計算器GridLayout布局實現(xiàn)方法示例
這篇文章主要介紹了Android開發(fā)之計算器GridLayout布局實現(xiàn)方法,結(jié)合實例形式分析了Android計算器界面布局及表達式計算相關(guān)操作技巧,需要的朋友可以參考下2019-03-03android開發(fā)教程之實現(xiàn)toast工具類
這篇文章主要介紹了android開發(fā)中需要的toast工具類,需要的朋友可以參考下2014-05-05