繼續上一篇的話題,滴滴booster功能分析講解,本篇仍是以系統bug修復爲主來說解。java
如下是transform的主要代碼android
if (context.isDebuggable || klass.name.startsWith(INSTRUMENT)) {
return klass
}
klass.methods.forEach { method ->
method.instructions?.iterator()?.asIterable()?.filter {
when (it.opcode) {
INVOKESTATIC -> (it as MethodInsnNode).owner == LOGCAT && SHADOW_LOG_METHODS.contains(it.name)
INVOKEVIRTUAL -> (it as MethodInsnNode).name == "printStackTrace" && it.desc == "()V" && context.klassPool.get(THROWABLE).isAssignableFrom(it.owner)
GETSTATIC -> (it as FieldInsnNode).owner == SYSTEM && (it.name == "out" || it.name == "err")
else -> false
}
}?.forEach {
when (it.opcode) {
INVOKESTATIC -> {
logger.println(" * ${(it as MethodInsnNode).owner}.${it.name}${it.desc} => $SHADOW_LOG.${it.name}${it.desc}: ${klass.name}.${method.name}${method.desc}")
it.owner = SHADOW_LOG
}
INVOKEVIRTUAL -> {
logger.println(" * ${(it as MethodInsnNode).owner}.${it.name}${it.desc} => $SHADOW_LOG.${it.name}${it.desc}: ${klass.name}.${method.name}${method.desc}")
it.apply {
itf = false
owner = SHADOW_THROWABLE
desc = "(Ljava/lang/Throwable;)V"
opcode = INVOKESTATIC
}
}
GETSTATIC -> {
logger.println(" * ${(it as FieldInsnNode).owner}.${it.name}${it.desc} => $SHADOW_LOG.${it.name}${it.desc}: ${klass.name}.${method.name}${method.desc}")
it.owner = SHADOW_SYSTEM
}
}
}
}
return klass
複製代碼
主要是對Log,System,Throwable這三個類的實現方法進行替換成本地的實現方式,對代碼中的日誌進行屏蔽,好比正式環境下的包就不須要日誌輸出這樣的功能。web
這個主要是要防止由於多線程致使的保存錯誤問題,看下面的transform面試
klass.methods.forEach { method ->
method.instructions?.iterator()?.asIterable()?.filterIsInstance(MethodInsnNode::class.java)?.filter {
it.opcode == Opcodes.INVOKEINTERFACE && it.owner == SHARED_PREFERENCES_EDITOR
}?.forEach { invoke ->
when ("${invoke.name}${invoke.desc}") {
"commit()Z" -> if (Opcodes.POP == invoke.next?.opcode) {
// if the return value of commit() does not used
// use asynchronous commit() instead
invoke.optimize(klass, method)
method.instructions.remove(invoke.next)
}
"apply()V" -> invoke.optimize(klass, method)
}
}
}
return klass
複製代碼
當方法爲commit的時候,替換commit的調用,使用本地的調用方式,實現以下:設計模式
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();
}
}
複製代碼
有沒有看到,就是這麼簡單,之後面試的時候能夠吹一波牛皮了,說解決了sharepreference的同步調用問題,哈哈bash
klass.methods?.forEach { method ->
method.instructions?.iterator()?.asIterable()?.filter {
when (it.opcode) {
Opcodes.INVOKESTATIC -> (it as MethodInsnNode).owner == MEDIA_PLAYER && it.name == "create"
Opcodes.NEW -> (it as TypeInsnNode).desc == MEDIA_PLAYER
else -> false
}
}?.forEach {
if (it.opcode == Opcodes.INVOKESTATIC) {
logger.println(" * ${(it as MethodInsnNode).owner}.${it.name}${it.desc} => $SHADOW_MEDIA_PLAYER.${it.name}${it.desc}: ${klass.name}.${method.name}${method.desc}")
it.owner = SHADOW_MEDIA_PLAYER
} else if (it.opcode == Opcodes.NEW) {
(it as TypeInsnNode).transform(klass, method, it, SHADOW_MEDIA_PLAYER)
logger.println(" * new ${it.desc}() => $SHADOW_MEDIA_PLAYER.newMediaPlayer:()L$MEDIA_PLAYER: ${klass.name}.${method.name}${method.desc}")
}
}
}
複製代碼
對於調用了media.create 或者是new MediaPlayer() 的地方進行方法替換,經過反射獲取到mEventHandler,來對這個設置一個callback來捕獲異常。多線程
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;
}
複製代碼
webview主要是用來作預加載用的,在application.create的地方作插莊處理。app
val method = klass.methods?.find {
"${it.name}${it.desc}" == "onCreate()V"
} ?: klass.defaultOnCreate.also {
klass.methods.add(it)
}
method.instructions?.let { insn ->
insn.findAll(RETURN, ATHROW).forEach { ret ->
insn.insertBefore(ret, VarInsnNode(ALOAD, 0))
insn.insertBefore(ret, MethodInsnNode(INVOKESTATIC, SHADOW_WEBVIEW, "preloadWebView", "(Landroid/app/Application;)V", false))
logger.println(" + $SHADOW_WEBVIEW.preloadWebView(Landroid/app/Application;)V before @${if (ret.opcode == ATHROW) "athrow" else "return"}: ${klass.name}.${method.name}${method.desc} ")
}
}
複製代碼
插入的代碼以下:框架
private static void startChromiumEngine() {
try {
final long t0 = SystemClock.uptimeMillis();
final Object provider = invokeStaticMethod(Class.forName("android.webkit.WebViewFactory"), "getProvider");
invokeMethod(provider, "startYourEngines", new Class[]{boolean.class}, new Object[]{true});
Log.i(TAG, "Start chromium engine complete: " + (SystemClock.uptimeMillis() - t0) + " ms");
} catch (final Throwable t) {
Log.e(TAG, "Start chromium engine error", t);
}
}
複製代碼
上面講到的內容,包括第一篇所講到的內容,都是比較簡單容易理解的,接下來的篇幅中,會對如下幾點進行深刻講解:async
一、res-check
二、lint
三、shrink
四、裏面所涉及到的設計模式
五、class類結構
六、asm知識點
七、如何搭建一個apm框架