滴滴开源库Booster:模块功能做用分析说明

导读

这是本人的滴滴开源库Booster源码分析文章第二篇,第一篇请戳《滴滴开源库Booster:架构运做及源码分析》java

本想着按照Booster划分的“性能优化”、“Lint”、“资源压缩”,分红三篇文章安排得明明白白的。但仔细查看ClassTransformer子类源码后发现,一篇就能够说完了。android

由于ClassTransformer的子类是执行的主要入口,因此我是按照'-transform-'模块名称后缀的首字母顺序来阅读。而其中大部分的逻辑处理代码,都须要对ASM的API有基本理解才容易梳理。web

'-instrument-'对应的模块,则负责提供具体hook的方法的实现类,基本是以静态方法为主,且类的命名有较高可读性:'ShadowXXX',一眼就能看出Hook的是哪一个类、哪一个方法。设计模式

因此本篇的主要内容以叙述hook方法逻辑背后的思想为主,解决了什么东西等,对于具体涉及使用ASM字节码操做的代码,不是本文叙述的重点。api

正文

开始分析说明以前,我整理了如下说明图性能优化

transform、instrument职责描述

展现了transform和instrument模块的主要工做内容,以及各自职责。bash

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:app

  • 针对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中的ActivityThreadCallbackActivityThreadCallback的套路与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

ApplicationattachBaseContext()方法前,添加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.Logjava.lang.Throwablejava.lang.System

  • 任何调用Logv,d,i,w,e,wtf,println方法的指令,都替换ownerShadowLog类对应的空实现方法。

  • 任何调用Throwable.printStackTrace()方法的指令,改成静态调用ShadowThrowable的静态空实现方法printStackTrace()

public final class ShadowThrowable {

    public static void printStackTrace(final Throwable t) {
    }
}
复制代码
  • 任何调用System.outSystem.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$*.classR.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() 实现预加载

小结

其实上面叙述的只是表面的东西,不少内在的知识点,我还须要消化:

  • 为何要在applicationattachBaseContext()onCreate()前添加方法?何时才要添加
  • SharedPreferenced.Editorcommitapply底层逻辑?
  • FinalizerWatchdogDaemon是如何监听到对象的finalizer方法
  • shrink资源的方法细节
  • kotlin的方法扩展的运用思路,须要通过实践加深认识
  • 源码中运用到的设计模式

这些须要继续研究其余方面的知识点进行补充。就目前的状况,booster相关的分析先暂告一段,静候官方的路线图补充再算吧。

相关文章
相关标签/搜索