調試研究Shadow對字節碼編輯的正確姿式

Shadow是經過字節碼編輯技術向插件插入中間層,完成插件技術的核心工做的。因此,有必要給新接觸字節碼編輯技術的同窗分享一下研究這項技術的入門姿式。java

構建過程介紹

Android 官方的構建過程提供了名爲TransForm的API,詳見這裏 。這個API容許第三方插件在class轉換成dex以前編輯class。Shadow就寫了這樣一個第三方API,就是在插件的build.gradle中添加的apply plugin: 'com.tencent.shadow.plugin'android

關於Transform,要注意,一次構建容許有多個Transform。可是構建系統不保證這些Transform的順序。就是多個Transform誰先執行,誰後執行,你在設計Transform時是不能假定的。Shadow的Transform會修改一些類的父類,或者一些類的名字。若是其餘Transform試圖以類型或名字查找類的話,那它在Shadow的Transform前執行和以後執行的效果就不同了。對於這種狀況,建議修改Shadow的Transform,將兩個Transform寫在同一個Plugin中,這樣在Plugin內部就能夠控制順序了。編程

插件應用了Shadow的Plugin後,在構建過程當中就會調用到com.tencent.shadow.core.gradle.ShadowPlugin#apply。它是經過projects/sdk/core/gradle-plugin/build.gradle中的這段配置找到目標類的。api

gradlePlugin {
    plugins {
        shadow {
            id = "com.tencent.shadow.plugin"
            implementationClass = "com.tencent.shadow.core.gradle.ShadowPlugin"
        }
    }
}
複製代碼

apply中,咱們經過這段代碼向構建系統註冊了咱們的Transform程序。bash

plugin.extension.registerTransform(ShadowTransform(...))
複製代碼

會執行一個Transform任務,好比transformClassesWithShadowTransformForDebug。這個任務會調用ShadowTransform對象的父類方法com.tencent.shadow.core.transform_kit.ClassTransform#transform,進入咱們Transform的真正入口markdown

Gradle任務的增量構建檢測機制只檢查任務的輸入和輸出是否發生了變化。這在通常狀況下顯然是合理的。輸入沒變,輸出都在,確定是不用從新執行任務的。可是在Shadow的開發中,Transform這個任務也是咱們源碼的一部分。所以在這種默認機制下,插件原始字節碼做爲輸入,插件編輯後字節碼做爲輸出,若是隻修改Transform的邏輯,好比將類A重命名爲B的邏輯改爲A重命名爲C。因爲輸入沒變,輸出都在,這個更新的Transform邏輯就不會執行了。所以在com.tencent.shadow.core.transform_kit.ClassTransform#getSecondaryFiles中,咱們將Transform程序自己也作爲了Transform程序的附帶輸入。這樣Transform任務就能判斷出輸入由於Transform程序自己變化而變化了,於是從新執行Transform任務。這樣我才達到了編輯Transform源碼,也能一鍵運行的效果。app

查看Transform先後的插件字節碼

咱們在開發中把sample-plugin作成一個aar庫,而後分別包裝成正常安裝的sample-normal-app和插件sample-plugin-app,就是爲了能比較方便的同時查看應用Transform先後的字節碼差別。oop

在Android Studio中直接雙擊打開一個apk文件(下圖中1),而後在詳情中選中dex文件(下圖中2),就能顯示出dex中的類。點擊下圖中3指向的按鈕,隱藏掉沒有真正打包在當前dex中的類。而後選擇一個類(下圖4),按右鍵,選擇Show Bytecode學習

好比查看正常安裝的apk的字節碼,以下:gradle

.class public Lcom/tencent/shadow/sample/plugin/app/lib/usecases/activity/TestActivityReCreate;
.super Landroid/app/Activity;
.source "TestActivityReCreate.java"

# direct methods
.method public constructor <init>()V
    .registers 1

    .line 31
    invoke-direct {p0}, Landroid/app/Activity;-><init>()V

    return-void
.end method
複製代碼

再查看插件apk中相同類的字節碼,以下:

.class public Lcom/tencent/shadow/sample/plugin/app/lib/usecases/activity/TestActivityReCreate;
.super Lcom/tencent/shadow/core/runtime/ShadowActivity;
.source "TestActivityReCreate.java"

# direct methods
.method public constructor <init>()V
    .registers 1

    .line 31
    invoke-direct {p0}, Lcom/tencent/shadow/core/runtime/ShadowActivity;-><init>()V

    return-void
.end method
複製代碼

能夠對比出來,第二行的.super發生了變化,表示這個類的父類被改變了。下面方法中的invoke-direct調用的方法也變化了。

修改Transform程序

好比前面例子中涉及到的系統Activity替換成ShadowActivity,就是在類com.tencent.shadow.core.transform.specific.ActivityTransform中實現的。你們能夠直接修改這個類的源碼,而後從新運行sample-host就能夠看到效果了。

Shadow對全部字節碼的修改邏輯都放在了com.tencent.shadow.core.transform.specific包中。

Shadow的transform-kit是Shadow在作字節碼編輯工做時沉澱的通用代碼,應該能夠直接用在Android上進行任何字節碼編輯工做。好比直接接入業務,經過實現SpecificTransform進行AOP編程。

transform-kit的設計

transform-kit的設計確實沒有久經考驗。但願你們用起來以後可以開源共建的改進它。

它目前主要解決了這樣幾個問題。

  1. ClassTransform解決如何將App本身的類和依賴jar包,輸入到內存中待編輯,而後在編輯後再輸出到文件。
  2. JavassistTransform解決如何將ClassTransform和Javassist聯繫起來。
  3. AbstractTransform負責組織Transform的抽象過程,就是例如先setup後fire這樣的順序,在這裏固定下來。
  4. AbstractTransformManagerSpecificTransformTransformStep,聯合起來組織各個平行不相關的Transform。規定每一個SpecificTransform由多個TransformStep構成。先配置Step,再統一執行。這個設計是爲了能讓每一個Transform串行工做,每一個Transform工做時都能處理全部的類。而不是每一個類按順序通過全部的Transform,Shadow最先的代碼就犯了這個錯誤。每一個類去作完全部Transform,可能再下一個類引用其餘類時出現其餘類已經處理過或者沒有處理過兩種狀況。
  5. AndroidClassPoolBuilder完成如何將Android SDK的類導入Javassist中使用。

先分享這麼多,相信一部分人瞭解這些就能夠經過查閱Javassist的文檔學習Javassist的用法,而後查看com.tencent.shadow.core.transform.specific包中已有的代碼,就能完全學會如何擴展Shadow的功能,也能利用Shadow的transform-kit在其餘應用中進行AOP編程了。

相關文章
相關標籤/搜索