Booster是一款專門爲移動應用設計的易用、輕量級且可擴展的質量優化框架,其目標主要是爲了解決隨着 APP 複雜度的提高而帶來的性能、穩定性、包體積等一系列質量問題。它提供了性能檢測、多線程優化、資源索引內聯、資源去冗餘、資源壓縮、系統 Bug 修復等一系列功能模塊,可使得穩定性可以提高 15% ~ 25%,包體積能夠減少 1MB ~ 10MB。java
本文就分析一下
booster
是如何實現系統bug修復功能的。android
這個組件能夠處理系統Crashgit
booster
hook了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
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
源碼能夠看出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
回調到咱們應用的一些事件。
這個組件修復了Toast在Android 7.1 上的 Bug
具體緣由分析能夠看這篇文章: 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
複製代碼
這個組件修復了"檢查覆蓋安裝致使的 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
動態插入到Application
的attachBaseContext()
和onCreate()
中。
即解決辦法是: 覆蓋安裝啓動應用時在Application.attachBaseContext/onCreate
中判斷資源是否加載,若是沒有加載的話直接kill掉應用
這個組件用來修復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));
}
...
}
複製代碼
這個組件用來修復finalizer
致使的TimeoutException
finalizer致使的
TimeoutException : 簡單的說就是對象的finalize()
執行時間過長。對於具體的分析詳見這篇文章:滴滴出行安卓端 finalize time out 的解決方案
這篇文章最終給出了2個解決方案是:
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線程
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
採用的方案是手動停掉 FinalizerWatchdogDaemon線程, 具體實現也是經過gradle transform
在Application
建立的時候新開一個線程調用停掉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();
}
}
複製代碼
到這裏booster
修復系統bug的feature
就簡單的過了一遍,後面會繼續分析booster
框架的其餘功能。
更多文章見 : AdvancedAdnroid