booster分析-修復系統bug

Booster是一款專門爲移動應用設計的易用、輕量級且可擴展的質量優化框架,其目標主要是爲了解決隨着 APP 複雜度的提高而帶來的性能、穩定性、包體積等一系列質量問題。它提供了性能檢測、多線程優化、資源索引內聯、資源去冗餘、資源壓縮、系統 Bug 修復等一系列功能模塊,可使得穩定性可以提高 15% ~ 25%,包體積能夠減少 1MB ~ 10MB。java

本文就分析一下booster是如何實現系統bug修復功能的。android

booster-transform-activity-thread

這個組件能夠處理系統Crashgit

實現原理

boosterhook了ActivityThread.mH.mCallback:github

ActivityThreadHooker.javasegmentfault

public static void hook() {
    try {
        final Handler handler = getHandler(thread); //經過反射獲取到了ActivityThread.mH
        if (null == handler || !(hooked = setFieldValue(handler, "mCallback", new ActivityThreadCallback(handler)))) {
            Log.i(TAG, "Hook ActivityThread.mH.mCallback failed");
        }
    } catch (final Throwable t) {
        Log.w(TAG, "Hook ActivityThread.mH.mCallback failed", t);
    }
}
複製代碼

即上面用ActivityThreadCallback代理ActivityThread.mH.mCallback。它作了如下處理:bash

ActivityThreadCallback.java多線程

private final Handler mHandler; //代理的 Handler

@Override
public final boolean handleMessage(final Message msg) {
    try {
        this.mHandler.handleMessage(msg);
    } catch (final NullPointerException e) {
        if (hasStackTraceElement(e, ASSET_MANAGER_GET_RESOURCE_VALUE, LOADED_APK_GET_ASSETS)) {//沒有棧信息,直接殺掉應用
            abort(e);
        }
        rethrowIfNotCausedBySystem(e);  //若是不是系統異常就拋出去
    } catch (final SecurityException
            | IllegalArgumentException
            | AndroidRuntimeException
            | WindowManager.BadTokenException e) {
        rethrowIfNotCausedBySystem(e);
    } catch (final Resources.NotFoundException e) {
        rethrowIfNotCausedBySystem(e);
        abort(e);
    } catch (final RuntimeException e) {
        final Throwable cause = e.getCause();
        if (((Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) && isCausedBy(cause, DeadSystemException.class))
                || (isCausedBy(cause, NullPointerException.class) && hasStackTraceElement(e, LOADED_APK_GET_ASSETS))) {
            abort(e);
        }
        rethrowIfNotCausedBySystem(e);
    } catch (final Error e) {
        rethrowIfNotCausedBySystem(e);
        abort(e);
    }
    return true;
}
複製代碼

若是mHandler.handleMessage()發生了異常而且是系統異常的話就catch住app

問題

catch哪些系統異常呢?

booster會檢查異常堆棧,若是是以系統包名打頭就認爲是系統異常:框架

private static final String[] SYSTEM_PACKAGE_PREFIXES = {
        "java.",
        "android.",
        "androidx.",
        "dalvik.",
        "com.android.",
        ActivityThreadCallback.class.getPackage().getName() + "."
};


private static boolean isSystemStackTrace(final StackTraceElement element) {
    final String name = element.getClassName();
    for (final String prefix : SYSTEM_PACKAGE_PREFIXES) {
        if (name.startsWith(prefix)) {
            return true;
        }
    }
    return false;
}
複製代碼

ActivityThread.Handler.handleMessage() 是何時調用的呢?

閱讀ActivityThread源碼能夠看出ActivityHread.mH的類型是ActivityThread.H,它的handleMessage主要處理了下面這些事件:ide

public static final int BIND_APPLICATION        = 110;
public static final int EXIT_APPLICATION        = 111;
public static final int SERVICE_ARGS            = 115;
public static final int RECEIVER                = 113;
public static final int CREATE_SERVICE          = 114;
public static final int BIND_SERVICE            = 121;
public static final int UNBIND_SERVICE          = 122;
public static final int RELAUNCH_ACTIVITY = 160;
public static final int INSTALL_PROVIDER        = 145;        
...
複製代碼

即基本上就是系統經過IPC回調到咱們應用的一些事件。

booster-transform-toast

這個組件修復了Toast在Android 7.1 上的 Bug

Toast在Android 7.1 上的問題

具體緣由分析能夠看這篇文章: Android7.1.1Toast崩潰解決方案

簡單的說就是Android 7.1上彈Toast的代碼沒有try catch。 解決辦法也很簡單,就是try catch住。 那booster是怎麼作的呢?

ShadowToast.java

public class ShadowToast {

    /**
     * Fix {@code WindowManager$BadTokenException} for Android N
     * @param toast The original toast
     */
    public static void show(final Toast toast) {
        if (Build.VERSION.SDK_INT == 25) {
            workaround(toast).show(); 
        } else {
            toast.show();
        }
    }
}
複製代碼

上面workaround(toast).show()的實現就是經過反射try-catch住了可能crash的代碼。

ShadowToast怎麼生效的呢?它實際上是經過:

自定義gradle transform在編譯期間把全部的Taost.show()變爲了ShadowToast.show(), 能夠查看構建報告:

*android/widget/Toast.show()V => com/didiglobal/booster/instrument/ShadowToast.apply(Lcom/didiglobal/booster/instrument/ShadowToast;)V: com/draggable/library/extension/glide/GlideHelper$downloadPicture$2.accept(Ljava/io/File;)V
複製代碼

booster-transform-res-check

這個組件修復了"檢查覆蓋安裝致使的 Resources 和 Assets 未加載的 Bug"

對於這個bug我沒有遇到過,我在網上簡單的查找了一下也沒有找到,不過看一看booster的解決方案吧:

ResChecker

public class ResChecker {
    public static void checkRes(final Application app) {
        if (null == app.getAssets() || null == app.getResources()) {
            final int pid = Process.myPid();
            Process.killProcess(pid);
            System.exit(10);
        }
    }
}
複製代碼

上面這段代碼 ResChecker.checkRes()會經過gradle transform動態插入到ApplicationattachBaseContext()onCreate()中。

即解決辦法是: 覆蓋安裝啓動應用時在Application.attachBaseContext/onCreate中判斷資源是否加載,若是沒有加載的話直接kill掉應用

booster-transform-media-player

這個組件用來修復MediaPlayer的崩潰

解決方案也是相似於上面的組件,就是把崩潰的地方try-catch住,而後經過gradle transform動態替換掉代碼中的MediaPlayer。具體try-catch的範圍是:

public final class ShadowMediaPlayer implements Constants {

    public static MediaPlayer newMediaPlayer() {
        return workaround(new MediaPlayer());
    }

    public static MediaPlayer create(Context context, Uri uri) {
        return workaround(MediaPlayer.create(context, uri));
    }

    public static MediaPlayer create(final Context context, final Uri uri, final SurfaceHolder holder) {
        return workaround(MediaPlayer.create(context, uri, holder));
    }

    public static MediaPlayer create(final Context context, final Uri uri, final SurfaceHolder holder, final AudioAttributes audioAttributes, final int audioSessionId) {
        return workaround(MediaPlayer.create(context, uri, holder, audioAttributes, audioSessionId));
    }

    public static MediaPlayer create(final Context context, final int resid) {
        return workaround(MediaPlayer.create(context, resid));
    }

    public static MediaPlayer create(final Context context, final int resid, final AudioAttributes audioAttributes, final int audioSessionId) {
        return workaround(MediaPlayer.create(context, resid, audioAttributes, audioSessionId));
    }
    ...
}
複製代碼

booster-transform-finalizer-watchdog-daemon

這個組件用來修復finalizer致使的TimeoutException

finalizer致使的TimeoutException : 簡單的說就是對象的finalize()執行時間過長。對於具體的分析詳見這篇文章:滴滴出行安卓端 finalize time out 的解決方案

這篇文章最終給出了2個解決方案是:

手動停掉 FinalizerWatchdogDaemon 線程

try {
    Class clazz = Class.forName("java.lang.Daemons$FinalizerWatchdogDaemon");
    Method method = clazz.getSuperclass().getDeclaredMethod("stop");
    method.setAccessible(true);
    Field field = clazz.getDeclaredField("INSTANCE");
    field.setAccessible(true);
    method.invoke(field.get(null));
} catch (Throwable e) {
    e.printStackTrace();
}
複製代碼

即經過反射停掉FinalizerWatchdogDaemon線程

try-cathch 住異常

final Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        if (t.getName().equals("FinalizerWatchdogDaemon") && e instanceof TimeoutException) {
             //ignore it
        } else {
            defaultUncaughtExceptionHandler.uncaughtException(t, e);
        }
    }
});
複製代碼

booster使用的方案

booster採用的方案是手動停掉 FinalizerWatchdogDaemon線程, 具體實現也是經過gradle transformApplication建立的時候新開一個線程調用停掉FinalizerWatchdogDaemon線程的代碼:

public class FinalizerWatchdogDaemonKiller {
    private static final int MAX_RETRY_TIMES = 10;
    private static final long THREAD_SLEEP_TIME = 5000;

    @SuppressWarnings("unchecked")
    public static void kill() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int retry = 0; isFinalizerWatchdogDaemonExists() && retry < MAX_RETRY_TIMES; retry++) {
                    try {
                        final Class clazz = Class.forName("java.lang.Daemons$FinalizerWatchdogDaemon");
                        final Field field = clazz.getDeclaredField("INSTANCE");
                        field.setAccessible(true);
                        final Object watchdog = field.get(null);
                        try {
                            final Field thread = clazz.getSuperclass().getDeclaredField("thread");
                            thread.setAccessible(true);
                            thread.set(watchdog, null);
                        } catch (final Throwable t) {
                            try {
                                final Method method = clazz.getSuperclass().getDeclaredMethod("stop");
                                method.setAccessible(true);
                                method.invoke(watchdog);
                        ....
            }
        }, "FinalizerWatchdogDaemonKiller").start();
    }
}
複製代碼

END

到這裏booster修復系統bug的feature就簡單的過了一遍,後面會繼續分析booster框架的其餘功能。

更多文章見 : AdvancedAdnroid

相關文章
相關標籤/搜索