這是本人的滴滴開源庫Booster源碼分析文章第二篇,第一篇請戳《滴滴開源庫Booster:架構運做及源碼分析》java
本想着按照Booster
劃分的「性能優化」、「Lint」、「資源壓縮」,分紅三篇文章安排得明明白白的。但仔細查看ClassTransformer
子類源碼後發現,一篇就能夠說完了。android
由於ClassTransformer
的子類是執行的主要入口,因此我是按照'-transform-'模塊名稱後綴的首字母順序來閱讀。而其中大部分的邏輯處理代碼,都須要對ASM的API有基本理解才容易梳理。web
'-instrument-'對應的模塊,則負責提供具體hook的方法的實現類,基本是以靜態方法爲主,且類的命名有較高可讀性:'ShadowXXX',一眼就能看出Hook的是哪一個類、哪一個方法。設計模式
因此本篇的主要內容以敘述hook方法邏輯背後的思想爲主,解決了什麼東西等,對於具體涉及使用ASM字節碼操做的代碼,不是本文敘述的重點。api
開始分析說明以前,我整理了如下說明圖性能優化
展現了transform和instrument模塊的主要工做內容,以及各自職責。bash
1.transformInvokeVirtual:架構
Thread.start()
方法 ->// 往opcode棧push String常量值 "\u200B"+類名
method.instructions.insertBefore(this, LdcInsnNode(makeThreadName(klass.className)))
// 插入方法調用指令,指向ShadowThread.setThreadName()
method.instructions.insertBefore(this, MethodInsnNode(Opcodes.INVOKESTATIC, SHADOW_THREAD, "setThreadName", "(Ljava/lang/Thread;Ljava/lang/String;)Ljava/lang/Thread;", false))
// 設置onwer
this.owner = THREAD
複製代碼
Thread.setName(String name)
方法 ->// 往opcode棧push String常量值 "\u200B"+類名
method.instructions.insertBefore(this, LdcInsnNode(makeThreadName(klass.className)))
// 插入方法調用指令,指向ShadowThread.makeThreadName(),上一句的常量值做爲方法的第一個參數
method.instructions.insertBefore(this, MethodInsnNode(Opcodes.INVOKESTATIC, SHADOW_THREAD, "makeThreadName", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", false))
// 設置onwer
this.owner = THREAD
複製代碼
2.transformInvokeStatic:app
Executor
全部涉及建立ExecutorService
的靜態方法進行hookEXECUTORS -> {
when (this.name) {
"defaultThreadFactory" -> {
val r = this.desc.lastIndexOf(')')
val desc = "${this.desc.substring(0, r)}Ljava/lang/String;${this.desc.substring(r)}"
logger.println(" * ${this.owner}.${this.name}${this.desc} => $SHADOW_EXECUTORS.${this.name}$desc: ${klass.name}.${method.name}${method.desc}")
this.owner = SHADOW_EXECUTORS
this.desc = desc
method.instructions.insertBefore(this, LdcInsnNode(makeThreadName(klass.className)))
}
"newCachedThreadPool",
"newFixedThreadPool",
"newSingleThreadExecutor",
"newSingleThreadScheduledExecutor",
"newScheduledThreadPool" -> {
val r = this.desc.lastIndexOf(')')
val name = this.name.replace("new", "newOptimized")
val desc = "${this.desc.substring(0, r)}Ljava/lang/String;${this.desc.substring(r)}"
logger.println(" * ${this.owner}.${this.name}${this.desc} => $SHADOW_EXECUTORS.$name$desc: ${klass.name}.${method.name}${method.desc}")
this.owner = SHADOW_EXECUTORS
this.name = name
this.desc = desc
method.instructions.insertBefore(this, LdcInsnNode(makeThreadName(klass.className)))
}
}
}
複製代碼
ShadowExecutor
提供的Hook方法,把建立線程的Fatory類改用NamedThreadFactory
建立,保證被建立線程的name、isDaemon、NORM_PRIORITY被安排得明明白白。異步
NamedThreadFactory:
@Override
public Thread newThread(final Runnable r) {
if (null == this.factory) {
final Thread t = new Thread(this.group, r, this.name + "#" + this.counter.getAndIncrement(), 0);
if (t.isDaemon()) {
t.setDaemon(false);
}
if (t.getPriority() != Thread.NORM_PRIORITY) {
t.setPriority(Thread.NORM_PRIORITY);
}
return t;
}
return setThreadName(this.factory.newThread(r), this.name);
}
複製代碼
3.transformInvokeSpecial
Thread
的全部構造方法進行hook。when (this.desc) {
"()V",
"(Ljava/lang/Runnable;)V",
"(Ljava/lang/ThreadGroup;Ljava/lang/Runnable;)V" -> {
method.instructions.insertBefore(this, LdcInsnNode(makeThreadName(klass.className)))
val r = this.desc.lastIndexOf(')')
val desc = "${this.desc.substring(0, r)}Ljava/lang/String;${this.desc.substring(r)}"
logger.println(" + $SHADOW_THREAD.makeThreadName(Ljava/lang/String;Ljava/lang/String;) => ${this.owner}.${this.name}${this.desc}: ${klass.name}.${method.name}${method.desc}")
logger.println(" * ${this.owner}.${this.name}${this.desc} => ${this.owner}.${this.name}$desc: ${klass.name}.${method.name}${method.desc}")
this.desc = desc
}
"(Ljava/lang/String;)V",
"(Ljava/lang/ThreadGroup;Ljava/lang/String;)V",
"(Ljava/lang/Runnable;Ljava/lang/String;)V",
"(Ljava/lang/ThreadGroup;Ljava/lang/Runnable;Ljava/lang/String;)V" -> {
method.instructions.insertBefore(this, LdcInsnNode(makeThreadName(klass.className)))
method.instructions.insertBefore(this, MethodInsnNode(Opcodes.INVOKESTATIC, SHADOW_THREAD, "makeThreadName", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", false))
logger.println(" + $SHADOW_THREAD.makeThreadName(Ljava/lang/String;Ljava/lang/String;) => ${this.owner}.${this.name}${this.desc}: ${klass.name}.${method.name}${method.desc}")
}
"(Ljava/lang/ThreadGroup;Ljava/lang/Runnable;Ljava/lang/String;J)V" -> {
method.instructions.insertBefore(this, InsnNode(Opcodes.POP2)) // discard the last argument: stackSize
method.instructions.insertBefore(this, LdcInsnNode(makeThreadName(klass.className)))
method.instructions.insertBefore(this, MethodInsnNode(Opcodes.INVOKESTATIC, SHADOW_THREAD, "makeThreadName", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", false))
logger.println(" + $SHADOW_THREAD.makeThreadName(Ljava/lang/String;Ljava/lang/String;) => ${this.owner}.${this.name}${this.desc}: ${klass.name}.${method.name}${method.desc}")
this.desc = "(Ljava/lang/ThreadGroup;Ljava/lang/Runnable;Ljava/lang/String;)V"
}
}
複製代碼
獲取自定義Handler
的子類H
,代理置換爲自定義的instrument中的ActivityThreadCallback
。 ActivityThreadCallback
的套路與CaughtCallback
類類似,只是捕獲的異常類型和數量不同。
@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;
}
複製代碼
在Application
的attachBaseContext()
方法前,添加hook方法FinalizerWatchdogDaemonKiller.kill()
val method = klass.methods?.find {
"${it.name}${it.desc}" == "attachBaseContext(Landroid/content/Context;)V"
} ?: klass.defaultAttachBaseContext
method.instructions?.findAll(RETURN, ATHROW)?.forEach {
method.instructions?.insertBefore(it, MethodInsnNode(INVOKESTATIC, FINALIZER_WATCHDOG_DAEMON_KILLER, "kill", "()V", false))
logger.println(" + $FINALIZER_WATCHDOG_DAEMON_KILLER.kill()V before @${if (it.opcode == ATHROW) "athrow" else "return"}: ${klass.name}.${method.name}${method.desc} ")
}
複製代碼
FinalizerWatchdogDaemonKiller
的職責:
java.lang.Daemons$FinalizerWatchdogDaemon
的線程。INSTANCE
對象,設置其thread
屬性爲null, 失敗的話調用stop()
方法這樣處理的緣由,參考滴滴技術提出的解決方案。
主要做用:在構建生產包(!debuggable)的時候,對因此日誌打印代碼實現屏蔽。 針對3個類:android.util.Log
、java.lang.Throwable
、java.lang.System
。
任何調用Log
的v,d,i,w,e,wtf,println
方法的指令,都替換owner
爲ShadowLog
類對應的空實現方法。
任何調用Throwable.printStackTrace()
方法的指令,改成靜態調用ShadowThrowable
的靜態空實現方法printStackTrace()
。
public final class ShadowThrowable {
public static void printStackTrace(final Throwable t) {
}
}
複製代碼
System.out
,System.err
的靜態get屬性指令,修改owner
改成ShadowSystem
,對應的out,err
對象爲空實現。public final class ShadowSystem {
public static final PrintStream out = new PrintStream(new OutputStream() {
@Override
public void write(final int b) {
}
});
public static final PrintStream err = out;
private ShadowSystem() {
}
}
複製代碼
對於調用 MediaPlayer.create()
方法,或者new MediaPlayer()
構造方法的方法指令,修改owner
指向ShadowMediaPlayer
類。
ShadowMediaPlayer
類,利用反射,HookMediaPlayer
對象的mEventHandler
屬性,置換爲CaughtCallback
。
private static MediaPlayer workaround(final MediaPlayer player) {
try {
final Handler handler = getEventHandler(player);
if (null == handler || !setFieldValue(handler, "mCallback", new CaughtCallback(handler))) {
Log.i(TAG, "Hook MediaPlayer.mEventHandler.mCallback failed");
}
} catch (final Throwable t) {
Log.e(TAG, "Hook MediaPlayer.mEventHandler.mCallback failed", t);
}
return player;
}
複製代碼
CaughtCallback類 定義在'booster-android-instrument'模塊中。負責代理傳入的Handler
對象,爲其handlerMessage(Message msg)
方法添加try-catch
塊,捕獲RuntimeException
達到fixbug的效果。主要對系統API類內部Handler對象進行置換時候使用。
public class CaughtCallback implements Handler.Callback {
private final Handler mHandler;
public CaughtCallback(final Handler handler) {
this.mHandler = handler; // 代理Handler對象
}
@Override
public boolean handleMessage(final Message msg) {
try {
this.mHandler.handleMessage(msg);
} catch (final RuntimeException e) {
// ignore
}
return true;
}
}
複製代碼
對Application
的子類,在調用的super.attachBaseContext()
語句以後,插入指令調用ResChecker.checkRes(Application application)
方法
checkRes()
方法,主要判斷Application.getAssets()
,Application.getResources()
public class ResChecker {
public static void checkRes(final Application app) {
if (null == app.getAssets() || null == app.getResources()) {
final int pid = Process.myPid();
Log.w(TAG, "Process " + pid + " is going to be killed");
Process.killProcess(pid);
System.exit(10);
}
}
}
複製代碼
對於Editor.commit()
,若是沒有使用其返回值,則改成ShadowEditor.apply(Editor)
。
對於Editor.apply()
方法,則改成異步調用ShadowEditor.apply(Editor)
。
ShadowEditor.apply(Editor)
的邏輯: 在主線程的話,通改成異步調用,不然直接調用commit()。
public class ShadowEditor {
public static void apply(final SharedPreferences.Editor editor) {
if (Looper.myLooper() == Looper.getMainLooper()) {
AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
editor.commit();
}
});
} else {
editor.commit();
}
}
}
複製代碼
1.刪除R$*.class
、R.class
, 默認ignore排除部分包路徑的類。 支持配置屬性 booster.transform.shrink.ignores
, 添加要排除的類。 2.刪除無用的常量屬性
把Toast.show()
方法,代理爲ShadowToast.show()
,
public static void show(final Toast toast) {
if (Build.VERSION.SDK_INT == 25) {
workaround(toast).show();
} else {
toast.show();
}
}
複製代碼
僅對SDK 25 作處理:
private static Toast workaround(final Toast toast) {
final Object tn = getFieldValue(toast, "mTN");
if (null == tn) {
Log.w(TAG, "Field mTN of " + toast + " is null");
return toast;
}
final Object handler = getFieldValue(tn, "mHandler");
if (handler instanceof Handler) {
if (setFieldValue(handler, "mCallback", new CaughtCallback((Handler) handler))) {
return toast;
}
}
final Object show = getFieldValue(tn, "mShow");
if (show instanceof Runnable) {
if (setFieldValue(tn, "mShow", new CaughtRunnable((Runnable) show))) {
return toast;
}
}
Log.w(TAG, "Neither field mHandler nor mShow of " + tn + " is accessible");
return toast;
}
複製代碼
Toast.mTN.mHandler
對象,委託給CaughtCallback
處理Toast.mTn.mShow
對象,委託給CaughtRunnable
處理根據設置的booster.transform.usage.apis
屬性,在構建時候打印匹配的api描述的方法狀況。若是沒有配置就不打印了。
在Application
中在super.onCreate()
被調用前,先調用ShadowWebView.preloadWebView()
方法。
android.webkit.WebViewFactory.getProvider()
-> WebViewFactoryProvider.startYourEngines()
實現預加載
其實上面敘述的只是表面的東西,不少內在的知識點,我還須要消化:
application
的attachBaseContext()
和 onCreate()
前添加方法?何時纔要添加SharedPreferenced.Editor
的commit
和apply
底層邏輯?FinalizerWatchdogDaemon
是如何監聽到對象的finalizer方法這些須要繼續研究其餘方面的知識點進行補充。就目前的狀況,booster相關的分析先暫告一段,靜候官方的路線圖補充再算吧。