导读
这是本人的滴滴开源库Booster源码分析文章第二篇,第一篇请戳《滴滴开源库Booster:架构运作及源码分析》
本想着按照Booster划分的“性能优化”、“Lint”、“资源压缩”,分成三篇文章安排得明明白白的。但仔细查看ClassTransformer子类源码后发现,一篇就可以说完了。
因为ClassTransformer的子类是执行的主要入口,所以我是按照'-transform-'模块名称后缀的首字母顺序来阅读。而其中大部分的逻辑处理代码,都需要对ASM的API有基本理解才容易梳理。
'-instrument-'对应的模块,则负责提供具体hook的方法的实现类,基本是以静态方法为主,且类的命名有较高可读性:'ShadowXXX',一眼就能看出Hook的是哪个类、哪个方法。
所以本篇的主要内容以叙述hook方法逻辑背后的思想为主,解决了什么东西等,对于具体涉及使用ASM字节码操作的代码,不是本文叙述的重点。
正文
开始分析说明之前,我整理了以下说明图
展示了transform和instrument模块的主要工作内容,以及各自职责。
Thread
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:
- 针对
Executor所有涉及创建ExecutorService的静态方法进行hook
EXECUTORS -> {
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"
}
}
ActivityThread
获取自定义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;
}
finalizer-watchdog-daemon
在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的职责:
- 尝试最多10次,查找类名为
java.lang.Daemons$FinalizerWatchdogDaemon的线程。 - 利用反射获取其单例
INSTANCE对象,设置其thread属性为null, 失败的话调用stop()方法
这样处理的原因,参考滴滴技术提出的解决方案。
Logcat
主要作用:在构建生产包(!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
-
对于调用
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;
}
}
res-check
对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);
}
}
}
shared-preferences
-
对于
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();
}
}
}
shrink
1.删除R$*.class、R.class, 默认ignore排除部分包路径的类。
支持配置属性 booster.transform.shrink.ignores, 添加要排除的类。
2.删除无用的常量属性
Toast
把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处理
Usage
根据设置的booster.transform.usage.apis属性,在构建时候打印匹配的api描述的方法情况。如果没有配置就不打印了。
WebView
在Application中在super.onCreate()被调用前,先调用ShadowWebView.preloadWebView()方法。
android.webkit.WebViewFactory.getProvider() -> WebViewFactoryProvider.startYourEngines() 实现预加载
小结
其实上面叙述的只是表面的东西,很多内在的知识点,我还需要消化:
- 为什么要在
application的attachBaseContext()和onCreate()前添加方法?什么时候才要添加 SharedPreferenced.Editor的commit和apply底层逻辑?FinalizerWatchdogDaemon是如何监听到对象的finalizer方法- shrink资源的方法细节
- kotlin的方法扩展的运用思路,需要经过实践加深认识
- 源码中运用到的设计模式
这些需要继续研究其他方面的知识点进行补充。就目前的情况,booster相关的分析先暂告一段,静候官方的路线图补充再算吧。