Gradle再回首之重點歸納

回顧

Android應用的構建過程是一個複雜的過程,涉及到很多工具。首先所有的資源文件都會被編譯,並且在一個R文件中引用,然後Java代碼被編譯,通過dex工具轉換成dalvik字節碼。最後這些文件都會被打包成一個APK文件,此應用被最終安裝到設備之前,APK會被一個debug或者release的key文件簽名。

一句話定義Gradle

Gradle是一種構建工具,其構建基於Groovy(DSL) ------ 一種基於JVM的動態語言,用於申明構建和創建任務,讓依賴管理更簡單。

年少時第一次對Gradle總結的微博:Gradle 與 Android的三生三世:是我構建了你,你必將依賴於我



Point

1.閉包和動態配置

【project/ build.gradle文件】

buildscript {
    ext.kotlin_version = '1.3.41'
    repositories {
        google()
        jcenter()
        
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
allprojects {
    repositories {
        google()
        jcenter()
    }
}

【app/ build.gradle文件】

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
  • buildscript: 針對於下方dependencies區塊中的依賴路徑,即插件的倉庫配置。
  • allprojects: 針對所有project(app、module);

(1)閉包Closure

可以傳遞的代碼區塊。

java不能對方法進行引用

void buildscript(Closure configureClosure);

如上所示,buildscript內的代碼區塊將傳遞到buildscript中,稍後執行。

(2)動態配置

gradle中語法配置,是在runtime而不是build時段check,不同於編碼Java,例如在調用某個對象不存在的方法,編譯時就會報錯,gradle中方法是動態配置的。

buildscript {
		......
    dependencies {
				classpath 'com.android.tools.build:gradle:3.5.0'
      	//add語法,等價於上面的寫法
        add('classpath', 'com.android.tools.build:gradle:3.5.0');
       	......
    }
}

來看上述build.gradle文件中對plugin路徑語法配置 的一個例子,很少人知道路徑的配置還可以用 add(,)這種語法,就像調用Java對象方法,傳入2個參數,更像是一個萬能鑰匙,第一個參數是配置key,第二個參數是配置value。

沒錯,你的確可以這樣理解,在上述第一點閉包中講到,將區塊傳入void dependencies(Closure configureClosure)稍後執行,再快捷鍵點擊classpath 具體發現是DependencyHandler 接口,此接口具體實現類是DefaultDependencyHandler,類中有個方法叫做MethodMissing(String name, Object args),內部遍歷配置清單中是否有name方法,找到則內部繼續調用create(...)方法。

可見,Gradle是利用Groovy的特性,把基於Java虛擬機的語言改造成最基本的配置語法。因此,這裏建議瞭解gradle配置規則即可,感興趣者再去了解其中實現。

拓展

allprojects {
    repositories {
        google()
        jcenter()

    }
}

//上下寫法等價----------------------

allprojects(new Action<Project>() {  //很java的感覺
    @Override
    void execute(Project project) {
        repositories {
            google()
            jcenter()

        }
    }
})

2.buildTypeproductFlavors

【app/ build.gradle文件】

android {
    ......
    buildTypes {
        release {
            signingConfig signingConfigs.myConfigs
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
          	debuggable false
        }
      	debug{
						signingConfig signingConfigs.myConfigs
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            minifyEnabled false
            debuggable true
        }
    }
  	......
}

3.compile、 implementation 和 api

  • implementation:不會傳遞依賴;
  • compile / api:會傳遞依賴;api 是 compile 的替代品,效果完全等同。
    • 當依賴被傳遞時,⼆級依賴的改動會導致 0 級項⽬重新編譯;
    • 當依賴不不傳遞時,二級依賴的改動 不會導致 0 級項⽬重新編譯;

減少傳遞依賴帶來的重複編譯應該是implementation 誕生的最大意義了,在往常開發Coder都是一個compile 依賴走天下,單module下的表現不明顯,但目前公司項目大部分採用多module項目,例如

主App -----依賴----> 業務module -----依賴----> 工具module

  • 主App:一些基本APP信息配置、簽名、動態化處理;
  • 業務module:業務邏輯/UI處理;
  • 工具module:網絡請求、自定義控件、工具等;

以上三種是很常見的多module分配,這時使用implementation依賴是可以大大減少重複編譯的,因爲業務module會依賴 工具module,但主App中無需對工具module使用傳遞依賴。因此,修改工具module內容時,不會導致主App重新編譯。


4.task

./gradlew taskName

task的使用在平時開發過程中也是不可或缺的一部分,特別是用於編寫各種插件,例如靜態check、打包等需求支持,下面瞭解一下task重點。

Test1. clean task

首先看個簡單的例子,也是project/build.gradle文件中一個現成的task ------ clean,我們在此基礎上加幾個Log對比查看下:

println("outside the task: println")

task clean(type: Delete) {
    println("inside the task: before task")
    delete rootProject.buildDir
    println("inside the task: after task")
}

分別在終端terminal輸入:

  • ./gradlew :打印log(如上截圖),build文件夾沒有刪除;
  • ./gradlew clean:打印log(如上截圖),build文件夾刪除;

爲何2個命令都輸出了Log,但./gradlew 執行後,文件夾並沒有被刪除?

在第一點中我們講解到gradle原理一大特點 ------ 閉包,將代碼塊傳入方法中,內部有自己的處理邏輯。兩個命令,Log都打印了,意味着所有語句都執行過了。可./gradlew命令,delete語句似乎沒有起作用?突破口就在這裏,點擊delete進去,查看源碼實現:

package org.gradle.api.tasks;

public class Delete extends ConventionTask implements DeleteSpec {
    private Set<Object> delete = new LinkedHashSet<Object>();
  	......
    /**
     * Sets the files to be deleted by this task.
     *
     * @param target Any type of object accepted by {@link org.gradle.api.Project#files(Object...)}
     */
    public void setDelete(Object target) {
        delete.clear();
        this.delete.add(target);
    }
  	......
}

看到這裏真的是非常有意思,在第一點也說了gradle把基於Java虛擬機的語言改造成最基本的配置語法,所以其內部原理實現Java Coder可謂是一目瞭然,在編寫task clean(type: Delete),可以直接理解爲class clean extends Delete,這就是個繼承嘛。迴歸到問題本身,可見delete操作內部實則是個添加操作,內部維護着一個Set,在執行 ./gradlew命令時,只是在配置任務,等到直接執行clean任務時./gradlew clean 時,纔會把Set集中的刪除任務取出,do it。

以上解釋也帶出了task的2個重要階段:

  • configuration配置階段
  • execution執行階段

Test2. task 配置與執行

在上一個例子的基礎上加深,task代碼塊內部新增一個doLast閉包,輸入命令對比結果:

println("outside the task: println")

task clean(type: Delete) {
    println("inside the task: before task")
    delete rootProject.buildDir
    println("inside the task: after task")
    
    doLast{
        println("inside the task: doLast")
    }
}

分別在終端terminal輸入:

  • ./gradlew :打印log,但是並沒有打印出 doLast閉包內的Log,build文件夾沒有刪除;
  • ./gradlew clean:打印log(如上截圖),build文件夾刪除;

在上一個Test的基礎上,我們得知configuration配置階段 會將所有配置讀取一遍,配備好對應的task,直接執行task時纔會真正do it 。而此次試驗的doLast閉包正突出 execution執行階段 的特點:doLast裏的內容在 task 執⾏過程中才會被執行。

./gradlew 命令還是配置階段,因此最後輸出並沒有打印出doLast閉包中的內容;執行./gradlew clean直接執行task任務時,纔會去執行doLast閉包中的內容,打印出 > inside the task: doLast。至此,相信task的2個階段已經分辨清楚。

Test3. doFirst 和 doLast

在Test2的基礎上繼續加深,既然在上一點中介紹了doLast,相應地,doFirst 雖遲但到。上一點中我們點明doFirst 執行在execution階段,那麼doFirst亦然,這2者的區別似乎通過名字也可瞭解一二。

下面通過一個更有趣的例子來了解其區別:

task clean(type: Delete) {
    doFirst{
        println("inside the task: doFirst")
    }

    delete rootProject.buildDir

    doLast{
        println("inside the task: doLast")
    }
}

clean.doFirst {
    println("outside the task: doFirst")
}
clean.doLast {
    println("outside the task: doLast")
}

由於這兩個區塊只在task execution階段 執行,因此此次試驗輸入 ./gradlew clean即可,查看Log輸出:

輸出結果表明(執行階段):

  • 後面的doFirst中的Log輸出 先於 前面的輸出;
  • 後面的doLast中的Log輸出 後於 前面的輸出;

首先說明下後續新增的clean.dofirst 這種寫法,簡直就是Java中調用類的方法,其實在【Test1. clean task】中已經提過:

在編寫task clean(type: Delete),可以直接理解爲class clean extends Delete,這就是個繼承嘛。

因此,後續新增的這種寫法也是沒有問題的,重點還是放到doFirst()doLast() 的調用順序上來,老規矩,查看這2個閉包方法的內部源碼實現:

package org.gradle.api.internal;

public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
  	......
      
		private List<InputChangesAwareTaskAction> actions;

		@Override
    public Task doFirst(final Closure action) {
        ...
        taskMutator.mutate("Task.doFirst(Closure)", new Runnable() {
            public void run() {
                getTaskActions().add(0, convertClosureToAction(action, "doFirst {} action"));
            }
        });
        return this;
    }
  
  	@Override
    public Task doLast(final Closure action) {
        ...
        taskMutator.mutate("Task.doLast(Closure)", new Runnable() {
            public void run() {
                getTaskActions().add(convertClosureToAction(action, "doLast {} action"));
            }
        });
        return this;
    }
  	
		......
}

一目瞭然,一個task中維護了一個Action集合List,而 doFirst方法每次調用都會向列表頭部插入Action,而 doLast方法每次調用都會向列表尾部插入Action。因此在此次實驗中,後面的doFirst中的Log輸出 先於 前面的輸出,後面的doLast中的Log輸出 後於 前面的輸出。


總結

至此,對於以上三個小實驗,做一個簡單的總結:

一個標準的task結構

task taskName { 
    初始化代碼
    
  	doFirst { 
      	task 代碼
    }
    
  	doLast {
      	task 代碼 
    }
}

doFirst() 、doLast() 和普通代碼段的區別

  • **普通代碼段:**在 task 創建過程中就會被執行,發生在 configuration階段
  • **doFirst() 和 doLast():**在 task 執⾏過程中被執行,發生在 execution階段。如果用戶沒有 直接或間接 執行 task,那麼它的 doLast()doFirst() 代碼不會被執⾏;
    • doFirst()doLast() 都是 task 代碼,其中 doFirst() 是往隊列的前⾯插入代碼,doLast() 是往隊列的後面插入代碼。

拓展 ------ task 的依賴

可以使用 task taskA(dependsOn: b)的形式來指定依賴。指定依賴後,task 會在⾃己執行前先執⾏依賴的 task。


5.gradle 執⾏的⽣命週期

(1)三個階段

  1. **[Initialization] 初始化階段:**執行 settings.gradle,確定主 project 和子 project ;

    根據項⽬結構來確定項目組成,如下:

    • 單 project:確定根目錄下的 build.gradle 文件即可;

    • 多 project:由配置了多個module的 settings.gradle 文件開始查找 settings 的順序:

      1. 當前⽬錄

      2. 兄弟⽬錄 master

      3. 父目錄

  2. **[Configuration] 配置階段:**執行每個 project 的 bulid.gradle,確定出所有 task 所組成的 有向⽆環圖

  3. [Execution] 執行階段:按照上⼀階段所確定出的有向無環圖來執⾏指定的 task;

(2)階段之間插入代碼

  1. ⼀二階段之間:settings.gradle 的最後;
  2. 二三階段之間:
afterEvaluate { 
  	插⼊入代碼
}



Plugin實踐

Gradle Plugin到底是什麼?

**本質就是將一些獨立邏輯的代碼封裝並抽取出來,加以複用。**但不同於module、library,它所處理的邏輯並非業務性質,而是作爲一個項目組織者,更關心各module的配置信息,因此提供了一系列配置、task執行相關API。

一個Plugin的寫法:

  • 直接寫到/app/build.gradle 配置文件
  • 獨立封裝到項目中的buildSrc 目錄
  • 獨立一個項目上傳到倉庫,項目直接引入即可

0. Groovy語法基礎要求

  • getter / setter

每個 field,Groovy 會⾃自動創建它的 gettersetter 方法,從外部可以直接調用,並且在使用 object.fieldA 來獲取值或者使用 object.fieldA = newValue 來賦值的時候,實際上會自動轉⽽調⽤ object.getFieldA()object.setFieldA(newValue)。 (跟Kotlin一樣)

  • 字符中的單雙引號

單引號是不帶轉義的,⽽雙引號內的內容可以使⽤ "string1${var}string2"的⽅式來轉義。(跟Vue一樣)


1. 配置信息Extension

這一部分配置相當自由,從 “配置類名” 到 “屬性”都是自主定義,後續從plugin中獲取,就像

/app/build.gradle 中對android編譯版本各種配置,以下舉個例子:

permissionsCheckList {
    //明確暫禁用的權限列表
    forbiddenPermissions = ['android.permission.GET_ACCOUNTS',
                            'android.permission.READ_CONTACTS']
}

2. Plugin實現

(1)直接在/app/build.gradle 實現

【注意:此部分需要寫到 apply 引入之前】

要不怎麼說Groovy是基於Java虛擬機而制定的DSL,寫法部分不同,但是直接寫implements實現,“like class”理解。

如下代碼,這裏實現一個只有print功能的PermissionCheck插件,

  1. 實現Plugin接口,內部實現void apply(Project target) 方法.
  2. 可以通過參數target的target.extensions.create可以獲取到項目配置的Extension信息,根據配置信息實例化創建 XXXExtension類。
  3. 因此也需要構建相關的XXXExtension類,注意需要定義到Plugin前。
  4. 通過類的get/set方法獲取具體屬性信息,做自定義題配置邏輯處理。
    • 邏輯處理幾個重點:執行順序。
class PermissionsCheckListExtension {
    def forbiddenPermissions = []
}

class PermissionCheck implements Plugin<Project> {
    @Override
    void apply(Project target) {
        println 'PermissionCheck apply'
        def extension = target.extensions.create('permissionsCheckList', PermissionsCheckListExtension)
      	println "PermissionCheck (forbiddenPermissions): ${extension.forbiddenPermissions}"
        target.afterEvaluate {
            println "PermissionCheck afterEvaluate (forbiddenPermissions):  ${extension.forbiddenPermissions}!"
        }
    }
}

apply plugin: PermissionCheck

permissionsCheckList {
    //明確暫禁用的權限列表
    forbiddenPermissions = ['android.permission.GET_ACCOUNTS',
                            'android.permission.READ_CONTACTS']
}

以上,執行gradle配置命令./gradlew,輸出見下圖:

輸出發現執行到apply plugin: PermissionCheck 時,配置gradle階段,注意此時還沒有讀取到項目中permissionsCheckList的配置信息,因此此時輸出的forbiddenPermissions爲[],也是初始化定義時的值。

而調用 target.afterEvaluate方法內傳入閉包內容稍後執行,Point中grad le生命週期有提到,afterEvaluate執行時期是在「配置階段」之後和「執行階段」之前,也就是說所有配置結束後,最後一個“配置”來執行閉包裏的內容,所以此時自定義配置已經可以獲取到了 。而後輸出的forbiddenPermissions列表也就是我們後續配置的GET_ACCOUNTS、READ_CONTACTS權限。

(2)project中封裝實現到buildSrc 目錄下

當然真正實踐到項目中,並不會像第一種寫法一股腦寫在 build.gradle 文件中,即冗雜又缺失服用性,下面介紹第二張實現方式。

創建一個Java Library,修改 /src 裏的目錄如下圖所示,具體如何實現,網上教程太多,這裏重點強調幾個點,爲什麼要創建這樣的目錄。

目錄結構

main文件夾下:

  • groovy文件夾替代初始java文件及,並創建包名目錄,新增Plugin類。

  • 創建資源文件 resources/META-INF/gradle-plugins,其下的 *.properties 中的* 代指插件名稱,即最終引入插件語句: apply plugin: '*'。最後,在 *.properties 文件中只需要進行一個配置:Plugin的路徑地址,具體格式如下,

 implementation-class=com.hencoder.plugin_demo.DemoPlugin

下面先做一個小測試,在buildSrc 目錄下的build.gradle 配置文件中新增一個print輸出,輸入./gradlew 命令查看輸出結果:

有趣的是buildSrc 目錄下的配置文件中的輸出語句被執行了兩次,爲何?之前講到 gradle生命週期的三個階段,難道是配置階段被執行了兩次?

並非如此,只是因爲buildSrc 目錄下的配置內容被執行了兩次!buildSrc**,是一個默認的目錄,gradle在執行的時候首要Top1優先級就是讀取此文件夾下配置。因此如果setting.gradle 中還有buildSrc文件夾的配置信息,,buildSrc中配置內容則會被執行兩次。(注:在創建buildSrc 目錄時,IDE會自動將此library名稱添加到項目根目錄下的setting.gradle配置文件中)

綜上,將根目錄下的setting.gradle配置文件中的 :buildSrc 刪除,則輸出就只有一句了。

buildSrc 目錄重點總結

  • 這是 gradle 中的⼀個特殊⽬錄,此⽬錄下的 build.gradle 配置會自動被執行,即使不配置到settings.gradle
  • buildSrc 的執⾏早於任何⼀個 project,也早於settings.gradle,它是⼀個獨立的存在。
  • buildSrc 所配置出來的 Plugin 會被 自動添加到編譯過程中的每⼀個 project 的 classpath, 因此它們纔可以直接使用 apply plugin: 'xxx'的⽅式來便捷應⽤這些 plugin
  • settings.gradle 中如果配置了了 ':buildSrc'buildSrc ⽬錄就會被當做是⼦ Project , 因此會被執⾏兩遍。所以在settings.gradle ⾥面應該刪掉 ':buildSrc'的配置

(3)單獨抽成項目發佈


3. Transform工具

(1)定義

Android 提供的一個⼯具,在項⽬構建過程中,可以將編譯後的⽂件(jar 文件和 class 文件) 添加自定義中間處理過程

(2)添加依賴

注意:Transform是Android提供的一個工具類,在com.android.build包下,但是按理說其他module或者library添加時,也不需要特殊考慮build包的依賴,因爲在項目本目錄下的build.gradle配置文件中已有 allProject的倉庫地址統一配置:

【根目錄/build.gradle】

allprojects {
    repositories {
        google()
        jcenter()
    }
}

但是在上一點也強調過,buildSrc 的執⾏早於任何⼀個 project,也早於settings.gradle,它是⼀個獨立的存在。因此 settings.gradle中倉庫的配置無效,需要額外在buildSrc目錄下的 build.gradle 配置文件中添加庫依賴:

 // 因爲 buildSrc 早於任何一個 project 執⾏行,因此需要⾃己添加倉庫 
repositories {
    google()
    jcenter() 
}
dependencies {
    implementation 'com.android.tools.build:gradle:3.1.4'
}

(3)類方法使用

import com.android.build.api.transform.Transform 
......
  
class CustomTestTransform extends Transform{

    @Override
    String getName() {
        return "CustomTestTransform"
    }

    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }

    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT
    }

    @Override
    boolean isIncremental() {
        return false
    }

    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        super.transform(transformInvocation)
    }
}  

Transform實現方法介紹一覽

  • getName():對應的task名稱。後續打包的時候,會根據task名稱生成對應的任務。
  • getInputTypes():指定轉換結果類型,例如字節碼或者資源文件or else
  • getScopes():指定適用轉換範圍,例如整個project或者or else。
  • transform(...): 自定義轉換邏輯。(重點方法,下面細講)

transform轉換方法內部機制

如上演示代碼,最基本構建一個CustomTestTransform類,且void transform(TransformInvocation transformInvocation) 方法中空實現,而後將其註冊到Plugin。此時安裝運行程序會直接報錯,如下圖:

如上圖可見程序安裝失敗,爲何?

尋常思路思考:註冊自定義轉換類,從父類繼承的transform方法即使空實現(父類也應該會做基礎流程過渡的吧),也不應該影響程序正常運行呀。

但其實父類Transformtransform方法就是空實現!因此這裏Android運行邏輯不是說把處理完的結果交給你自定義Transform去加工,而是類似於一種上下游機制,上游將結果傳遞給自定義Transform,下游在等着數據接收。因此如果自定義子類Transform中的transform方法是空實現,會使得流程滯留,程序異常。

綜上,transform方法可以先不顧自定義特殊邏輯的實現,但必須需要做的一點是 將從上游接受的數據結果(處理 or 未處理)返回給下游, 即入口接收數據再輸送給出口。以下的模版型代碼,無任何特殊自定義邏輯,僅做傳輸作用:

@Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        def inputs = transformInvocation.inputs
        def outputProvider = transformInvocation.outputProvider
        inputs.each {
            //jarInputs: 各個依賴編譯的jar文件
            it.jarInputs.each {
                File dest = outputProvider.getContentLocation(it.name, it.contentTypes, it.scopes, Format.JAR)
                println "jarInputs: ${it.file}"
                FileUtils.copyFile(it.file, dest)
            }

            //directoryInputs: 本地project編譯成的多個class文件
            it.directoryInputs.each {
                File dest = outputProvider.getContentLocation(it.name, it.contentTypes, it.scopes, Format.DIRECTORY)
                println "directoryInputs: ${it.file}"
                FileUtils.copyDirectory(it.file, dest)
            }
        }
    }

上述代碼邏輯簡單一覽:

  1. 從參數transformInvocation 分別獲取入口、出口
  2. 遍歷入口數據
    • 獲取jarInputs ------ 編譯依賴的jar文件集合,再遍歷,copy輸出到出口;
    • 獲取directoryInputs ------ 本地project編譯後的class文件集合,再遍歷,copy輸出到出口;

爲了更好地理解從入口獲取的這些class、jar文件集合,print文件路徑觀察log輸出結果,輸入./gradlew assembleDebug 打包。

  • jarInputs集合路徑

jarInputs 集合路徑如上,這裏只截圖了一部分,觀察這些jar文件路徑不難發現,都是項目編譯所依賴的庫,且存於 /.gradle/caches/* 緩存文件夾中。(便於各個項目共用這些依賴庫

  • directoryInputs集合路徑

directoryInputs集合路徑如上,都存於項目名稱/app/build/* 即本地project編譯後的build目錄下,而且進一步點進classes目錄下,都是R文件。

其實這都是屬於各種依賴庫的文件,只是AS編譯完成項目後,屬於此項目project的class文件

  • 自定義Transform路徑

如下圖可見,這是我們自定義Transform ------ CustomTestTransform的路徑: 項目名稱/app/build/intermediates/transforms/CustomTestTransform ,而且此目錄下的文件就是自定義Transform轉換後的jar、class文件。(class文件在圖二)

你可以做一個小測試,將build文件夾刪除,再把CustomTestTransformtransform 方法恢復空實現,也就是我們之前解釋過的導致dex文件打包失敗的**「上下流機制」**,運行./gradlew assembleDebug

此時程序安裝運行是失敗的,見上圖CustomTestTransform目錄下的文件是空的,沒有jar/class文件了,這也是程序爲何安裝失敗的原因:根據「上下流機制」,自定義Transform沒有將入口文件運輸(處理 or 未處理)到出口,而Android Plugin會將該目錄下的所有jar、class文件打包進一個dex文件,因此如果此目錄下沒有文件,打包後的Dex是一個空殼,屆時安裝肯定出錯。

祭出打包過程神圖如下,來源於《Gradle For Android》

(4)Transform落地業務場景

此部分提供的例子CustomTestTransform 只是模版化地將編譯完的內容原封不不動搬運到⽬目標位置, 無實際作用,在日常開發中,通常是結合javassist工具(面向切面編程),來修改字節碼

其實在瞭解Transform提供的功能後,其落地業務場景皆由此爲基礎拓展,以下介紹幾個常見場景。

  • 方法耗時統計

    通過一個自定義Transform過濾每一個class/jar文件,將所有方法摘出來,插樁計時代碼。

  • 方法、API搜索

    黑名單方法搜索,例如Android系統升級,個別API失效。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章