这是本人的滴滴开源库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相关的分析先暂告一段,静候官方的路线图补充再算吧。