Crash白名單:Crash整治大殺器,一招終結ROM BUG與疑難雜症

使用場景

在實際工做時,咱們爲了維護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

image-20190903151058768

而後又一頓操做,咱們肯定這個是 ROM BUG 了。微信

image-20190903151116187

這時候,咱們就須要 Crash 白名單來整治這些問題,避免對用戶產生影響。app

適用範圍

  • 主線程調用棧中出現Looper.loop
  • 某一類特殊 BUG
  • 疑難的 ROM BUG
  • 捕獲後不影響正常生命週期的崩潰

運行原理

當線程執行完可執行的代碼段後,會中止生命週期,而安卓中的主線程有 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")
        );
複製代碼

DevieName 拼接方式

拼接規則以下:

Build.MANUFACTURER/Build.MODEL
複製代碼

其中,Build.MANUFACTURER 爲獲取設備製造商,Build.MODEL 爲獲取設備名,用「/」鏈接。

例如:

vivo/vivo X3L
複製代碼

或者,

OPPO/R9PlustmA
複製代碼

實踐效果

OPPO R9 的 NullPointerException 崩潰統計圖,崩潰已經降低,剩餘少許無白名單機制的舊版本應用還在崩潰:

image-20190911111339387

Vivo X3 的 RemoteServiceException 崩潰統計圖,崩潰已經降低,剩餘少許無白名單機制的舊版本應用還在崩潰:

image-20190911111429030

其餘的還在將來的路上,等你實踐。

下載測試

點擊下載

二維碼用微信掃一掃會被攔截,請換其餘掃碼工具

image-20190911142747718

項目地址

github.com/leotyndale/…

相關文章
相關標籤/搜索