在實際工做時,咱們爲了維護APP的崩潰率,常常須要面臨因爲安卓系統碎片化帶來的各類各樣奇葩的BUG,其中有一些是 ROM 的 BUG,ROM 平臺又不推送更新修復的,還有一些是沒有思路的棘手問題,好比:java
佔版本崩潰量 10% 的某一類 NullPointerException。android
Fatal Exception: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Class java.lang.Object.getClass()' on a null object reference
at android.os.Message.toString + 574(Message.java:574)
at android.os.Message.toString + 480(Message.java:480)
at android.os.Looper.loop + 187(Looper.java:187)
at android.app.ActivityThread.main + 5509(ActivityThread.java:5509)
at java.lang.reflect.Method.invoke(Method.java)
at java.lang.reflect.Method.invoke + 372(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run + 961(ZygoteInit.java:961)
at com.android.internal.os.ZygoteInit.main + 756(ZygoteInit.java:756)
複製代碼
佔版本崩潰量的 5% 的某一類 RemoteServiceException:git
Fatal Exception: android.app.RemoteServiceException: Bad notification posted from package *: Couldn't expand RemoteViews for: StatusBarNotification(pkg=* user=UserHandle{0} id=3000171 tag=null score=20: Notification(pri=2 contentView=*/0x1090064 vibrate=default sound=default defaults=0xffffffff flags=0x11 kind=[null]))
at android.app.ActivityThread$H.handleMessage + 1366(ActivityThread.java:1366)
at android.os.Handler.dispatchMessage + 102(Handler.java:102)
at android.os.Looper.loop + 136(Looper.java:136)
at android.app.ActivityThread.main + 5095(ActivityThread.java:5095)
at java.lang.reflect.Method.invokeNative(Method.java)
at java.lang.reflect.Method.invoke + 515(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run + 786(ZygoteInit.java:786)
at com.android.internal.os.ZygoteInit.main + 602(ZygoteInit.java:602)
at dalvik.system.NativeStart.main(NativeStart.java)
複製代碼
可是咱們發現,這種崩潰只集中在部分機型。github
而後又一頓操做,咱們肯定這個是 ROM BUG 了。微信
這時候,咱們就須要 Crash 白名單來整治這些問題,避免對用戶產生影響。app
當線程執行完可執行的代碼段後,會中止生命週期,而安卓中的主線程有 Looper.loop() 能夠開啓死循環,保證主線程的存活。ide
package android.os;
public final class Looper {
……
public static void loop() {
……
for (;;) {
……
}
}
}
複製代碼
這個 loop 是在 ActivityThread 的 main 方法中啓動死循環的,關於啓動流程,請自行腦補。工具
package android.app;
public final class ActivityThread extends ClientTransactionHandler {
……
public static void main(String[] args) {
……
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}
複製代碼
當咱們在程序中手動調用 Looper.loop(),咱們就成爲了 loop 的啓動方,處理主線程消息,這時候,咱們只要 try catch 住 Looper.loop(),那麼,調用棧從 loop 報出來的崩潰就均可以被咱們捕獲。oop
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
try {
Looper.loop();
} catch (Throwable e) {
Log.i(TAG, "loop crash:" + e);
}
}
}
});
複製代碼
不過這個並不新鮮,相似的實踐有不少(like StackOverflow),咱們不能一味的捕獲主線程中的崩潰,須要有一套白名單崩潰處理機制和 loop 協做,來整治棘手的崩潰問題。post
在gralde的dependencies中加入
compile 'com.imuxuan:whitecrash:1.0'
複製代碼
啓動白名單
WhiteCrash.get().catchCrash(new CrashInfo()) // 配置崩潰信息1
.catchCrash(new CrashInfo()) // 配置崩潰信息1
.catchCrash(new CrashInfo()) // 配置崩潰信息1
.setCustomInfo(deviceCustomInfo) // 配置自定義信息,如設備ID等
.start(); // 啓動白名單處理
複製代碼
崩潰信息配置
new CrashInfo().setExceptionName("NullPointerException") // 捕獲 NullPointerException
// 指定捕獲 Exception Info 包含以下信息的 NullPointerException
.setExceptionInfo("Attempt to invoke virtual method " +
"'java.lang.Class java.lang.Object.getClass()' on a null object reference")
.addDeviceInfo( // 指定捕獲機型1的該崩潰,不指定則所有捕獲
new DeviceInfo().setDeviceName(OPPO_R9PLUSTMA).addBuildVersion("5.1.1")
)
.addDeviceInfo( // 指定捕獲機型2的該崩潰,不指定則所有捕獲
new DeviceInfo().setDeviceName(OPPO_R9PLUS).addBuildVersion("5.1.1")
);
複製代碼
拼接規則以下:
Build.MANUFACTURER/Build.MODEL
複製代碼
其中,Build.MANUFACTURER 爲獲取設備製造商,Build.MODEL 爲獲取設備名,用「/」鏈接。
例如:
vivo/vivo X3L
複製代碼
或者,
OPPO/R9PlustmA
複製代碼
OPPO R9 的 NullPointerException 崩潰統計圖,崩潰已經降低,剩餘少許無白名單機制的舊版本應用還在崩潰:
Vivo X3 的 RemoteServiceException 崩潰統計圖,崩潰已經降低,剩餘少許無白名單機制的舊版本應用還在崩潰:
其餘的還在將來的路上,等你實踐。
二維碼用微信掃一掃會被攔截,請換其餘掃碼工具