滴滴開源庫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相關的分析先暫告一段,靜候官方的路線圖補充再算吧。

相關文章
相關標籤/搜索