Android 干货分享: 字节码插桩(1)—— 熟悉 Gradle

我正在参加「掘金·启航计划」

目录

  • Android 干货分享: 字节码插桩(1)—— 熟悉 Gradle
  • Android 干货分享: 字节码插桩(2)—— ASM 使用 (待更新)

前言

我在学习这方面内容时也参阅了很多讲解插桩的博客,很少对 Gradle Task 相关东西进行讲解,就导致看完教程感觉会了但是上手写很困难,单独去全身心学习 Gradle 知识周期又比较长,所以第一篇博客先来分享下关联的 Gradle 部分内容,但不会深入的讲解,因为本系列博客的作用是能尽快上手写代码实现插桩。

Gradle 基础

Gradle是一款基于JVM的构建工具,用于构建、编译和打包各种类型的应用程序和库,在使用 Android Studio 开发时我们点击运行按钮 app debug 版本就会装在我们的手机上,这其中的构建流程是由 Gradle 来执行一连串的 Task 实现。

在初学 Gradle 时完全没必要立即掌握 Groovy 语法,因为 Gradle 是基于 JVM 的,可以与 Java 类无缝衔接,直接用 Java or Kotlin 来写就好。

结构

在 Android 项目中根目录下的 settings.gradlebuild.gradle 文件再配上各个模块目录下的 build.gradle 文件就构成了最基础的 gradle 项目;

image.png

执行阶段

  1. 配置阶段(Initialization):Gradle 加载构建脚本,配置项目,构建对象模型。在此阶段,Gradle 创建并配置了 Project 对象,初始化了项目的属性、依赖项、插件等信息,但并不执行具体的任务。
  2. 配置执行阶段(Configuration):Gradle 会依次执行所有的 Task,在执行 Task 前,先执行任务的配置,确定任务的执行方式。在此阶段,Gradle 根据用户定义的 Task 配置信息来创建 Task 对象,并将 Task 加入执行队列。
  3. 执行阶段(Execution):Gradle 依次执行加入执行队列中的 Task,并根据 Task 之间的依赖关系,确定执行顺序。在此阶段,Gradle 执行具体的任务,完成构建过程。执行阶段是整个构建过程中最为关键的阶段。
  4. 结束阶段(Finalization):Gradle 执行完所有 Task 后,进行资源清理,以及一些收尾工作。在此阶段,Gradle 执行一些清理工作,例如删除一些临时文件,释放占用的资源等。

这么说可能会一脸懵,下面来结合 Android 项目来了解下。

  1. 配置阶段:具体体现为读取 settings.gradle 中的配置,获取项目名称、子模块名称;接着创建 Project 对象;再接着应用项目中定义的插件;根据各个 build.gradle 文件中定义的 Task 创建任务,并且将任务添加到任务图(有向无环图)中;
  2. 配置执行阶段:具体体现为依次执行 settings.gradle、build.gradle 和各个子模块下的 build.gradle 中的配置代码。注意:配置代码不是执行全部代码。接着所有 Task、Plugin 的配置代码;注意这一步的操作只会执行配置代码,后面的例子中会体现。
  3. 执行阶段:具体体现为按照依赖顺序(任务图)执行各个 Task 中的代码;
  4. 结束阶段:也就是收尾工作,具体体现为输出一些构建相关信息(构建耗时、内存),清理任务(临时文件目录)等;

枯燥的理论知识部分结束了,对于这些描述有不明白的不用着急,下面小节中的例子都会体现出来,阅读完回过头再来对照一下就很容易理解了。

闭包

用过 Kotlin 的肯定很容易理解这个闭包,我就直接上代码来简单表达下:

task hello {
// doLast 就理解为执行一段代码 下面会说到
doLast { // 闭包
println("hello world")
}
// 接受的是一个 Closure 对象
doLast(new Closure(this) {
@Override
Object call() {
println("hello world")
return super.call()
}
})
}
task hello {







    // doLast 就理解为执行一段代码 下面会说到
    doLast { // 闭包
        println("hello world")
    }

    
    // 接受的是一个 Closure 对象
    doLast(new Closure(this) {
        @Override
        Object call() {
            println("hello world")
            return super.call()
        }
    })
}
task hello { // doLast 就理解为执行一段代码 下面会说到 doLast { // 闭包 println("hello world") } // 接受的是一个 Closure 对象 doLast(new Closure(this) { @Override Object call() { println("hello world") return super.call() } }) }

两个 doLast 的效果是一样的,都是输出 hello world,可以看作是一种匿名函数。闭包可以有多个参数,也可以没有参数,可以有返回值,也可以没有返回值。

Gradle Task

Gradle 的核心就在于执行一个个的 Task,接下来通过一些实例代码来了解一下常见的操作。

手写 Task

为了方便直接在项目根目录下新建一个 study.gradle 文件并写入以下代码:

study.gradle

class Utils{
void hello(){
println("hello world!")
}
void goodbye(){
println("goodbye!")
}
}
Utils utils = new Utils()
task hello {
utils.hello()
}
task bye {
utils.goodbye()
}
class Utils{
    void hello(){
        println("hello world!")
    }






    void goodbye(){
        println("goodbye!")
    }


}




Utils utils = new Utils()



task hello {
    utils.hello()
}


task bye {
    utils.goodbye()
}
class Utils{ void hello(){ println("hello world!") } void goodbye(){ println("goodbye!") } } Utils utils = new Utils() task hello { utils.hello() } task bye { utils.goodbye() }

其他形式

上一小节中定义了 hello,bye 两个 Task,定义的方式是使用闭包,除了闭包还有其他的定义方式:

class MyTask extends DefaultTask{
@TaskAction
void doSomethings(){
Utils utils = new Utils()
utils.hello()
}
}
task hello(type: MyTask)
// or
Task myTask = tasks.create("hello"){
// 配置任务其他属性
dependsOn(otherTask) // 任务依赖
group("study") // 任务分组
description("invoke Utils.hello method to print") // 任务描述
doLast{
Utils utils = new Utils()
utils.hello()
}
}
class MyTask extends DefaultTask{
    @TaskAction
    void doSomethings(){
        Utils utils = new Utils()
        utils.hello()
    }
}
task hello(type: MyTask)

// or

Task myTask = tasks.create("hello"){
    // 配置任务其他属性
    dependsOn(otherTask) // 任务依赖
    group("study") // 任务分组
    description("invoke Utils.hello method to print") // 任务描述

    doLast{
        Utils utils = new Utils()
        utils.hello()
    }
}
class MyTask extends DefaultTask{ @TaskAction void doSomethings(){ Utils utils = new Utils() utils.hello() } } task hello(type: MyTask) // or Task myTask = tasks.create("hello"){ // 配置任务其他属性 dependsOn(otherTask) // 任务依赖 group("study") // 任务分组 description("invoke Utils.hello method to print") // 任务描述 doLast{ Utils utils = new Utils() utils.hello() } }

运行效果是一样的。

Task 执行

gradle 文件可以通过以下命令执行:

./gradlew -b study.gradle
./gradlew -b study.gradle
./gradlew -b study.gradle

输出结果:

> Configure project :
hello world!
goodbye!
> Configure project :



hello world!



goodbye!
> Configure project : hello world! goodbye!

嗯,看着一切正常,接着来执行下单个 Task:

./gradlew -b study.gradle hello
./gradlew -b study.gradle hello
./gradlew -b study.gradle hello

输出结果:

> Configure project :
hello world!
goodbye!
> Configure project :                                                                      
hello world!                                                                               
goodbye!     
> Configure project : hello world! goodbye!

执行单个 Task 和执行整个文件的结果居然一样?这里就体现出来前面小节中的执行配置阶段,注意输出的第一行内容 >Configure project : ,这显然是配置项目的意思。

修改一下 hello 这个 Task 代码:

task hello {
doLast{
utils.hello()
}
}
task hello {







    doLast{
        utils.hello()

    }





}
task hello { doLast{ utils.hello() } }

再次执行./gradlew -b study.gradle hello后输出结果:

> Configure project :
goodbye!
> Task :hello
hello world!
> Configure project :



goodbye!

> Task :hello
hello world!
> Configure project : goodbye! > Task :hello hello world!

可以看到多了一些输出,Configure project 中没有了 hello world 输出,而是移动到了 > Task :hello 之后输出。而 > Task :hello 正是前面提到的执行阶段。

再次修改代码来体验下:

task hello {
// 配置阶段
utils.hello()
// 执行阶段
doLast{
utils.hello()
}
}
task hello {







    // 配置阶段

    utils.hello()

    
    // 执行阶段

    doLast{

        utils.hello()

    }


}
task hello { // 配置阶段 utils.hello() // 执行阶段 doLast{ utils.hello() } }

再次执行./gradlew -b study.gradle hello后输出结果:

> Configure project :
hello world!
goodbye!
> Task :hello
hello world!
> Configure project :



hello world!



goodbye!





> Task :hello
hello world!
> Configure project : hello world! goodbye! > Task :hello hello world!

相信看到这里就能理解配置阶段、执行阶段的含义了,在配置阶段会加载所有的 Task 并且执行其配置代码,执行阶段会执行 doLast、doFirst 等等闭包中的代码;

doFirst

字面意思就很容易理解,在任务之前执行一些操作,直接上代码来解释:

task hello {
doFirst {
println("hello3")
}
doFirst {
println("hello2")
}
doFirst {
println("hello1")
}
}
task hello {







    doFirst {
        println("hello3")
    }





    doFirst {
        println("hello2")

    }

    doFirst {
        println("hello1")
    }


}
task hello { doFirst { println("hello3") } doFirst { println("hello2") } doFirst { println("hello1") } }

输出结果:

> Task :hello
hello1
hello2
hello3
> Task :hello

hello1

hello2

hello3
> Task :hello hello1 hello2 hello3

每次调用 doFirst 都会将闭包中的逻辑插到任务执行阶段的最前面。

doLast

和 doFrist 相反,doLast 是将逻辑插到任务执行阶段的最后,示例:

task hello {
doLast {
println("hello1")
}
doLast {
println("hello2")
}
doLast {
println("hello3")
}
}
task hello {







    doLast {
        println("hello1")
    }





    doLast {

        println("hello2")

    }

    doLast {
        println("hello3")
    }


}
task hello { doLast { println("hello1") } doLast { println("hello2") } doLast { println("hello3") } }

输出结果:

> Task :hello
hello1
hello2
hello3
> Task :hello

hello1

hello2

hello3
> Task :hello hello1 hello2 hello3

afterEvaluate

afterEvaluate 是在配置阶段完成后的回调,示例:

task hello {
// 配置阶段
utils.hello()
// 执行阶段
doLast{
utils.hello()
}
}
task bye {
utils.goodbye()
}
// 在配置阶段之后执行
afterEvaluate {
println("After evaluate")
// 可以在这里执行进一步的操作,如注册任务、修改属性等
}
task hello {







    // 配置阶段

    utils.hello()




    // 执行阶段

    doLast{

        utils.hello()

    }


}




task bye {
    utils.goodbye()
}


// 在配置阶段之后执行
afterEvaluate {
    println("After evaluate")
    // 可以在这里执行进一步的操作,如注册任务、修改属性等
}
task hello { // 配置阶段 utils.hello() // 执行阶段 doLast{ utils.hello() } } task bye { utils.goodbye() } // 在配置阶段之后执行 afterEvaluate { println("After evaluate") // 可以在这里执行进一步的操作,如注册任务、修改属性等 }

运行 ./gradlew -b study.gradle 输出结果:

> Configure project :
hello world!
goodbye!
After evaluate
> Configure project :



hello world!



goodbye!


After evaluate
> Configure project : hello world! goodbye! After evaluate

在任务配置阶段都执行完成后触发了 afterEvaluate 闭包中的逻辑。

Task 依赖

Android Studio 在构建过程中最常见的 Task 就是 assembleDebug ,点击运行按钮后可以看到控制台输出的可不是只有一个 assembleDebug 任务:

image.png

比较长没有截图完整,不过不影响,可以看得出在执行 assembleDebug 时先执行了一系列与打包有关的任务,这就需要用到任务依赖。示例:

task hello {
doLast{
utils.hello()
}
}
// 创建任务时设置依赖
// bye 任务依赖于 hello 任务
task bye (dependsOn: hello) {
doLast{
utils.goodbye()
}
}
task hello {







    doLast{


        utils.hello()

    }





}





// 创建任务时设置依赖
// bye 任务依赖于 hello 任务
task bye (dependsOn: hello) {
    doLast{
        utils.goodbye()
    }
}
task hello { doLast{ utils.hello() } } // 创建任务时设置依赖 // bye 任务依赖于 hello 任务 task bye (dependsOn: hello) { doLast{ utils.goodbye() } }

执行 ./gradlew -b study.gradle bye 来运行 bye 任务,输出结果:

> Configure project :
> Task :hello
hello world!
> Task :bye
goodbye!
> Configure project :

> Task :hello
hello world!

> Task :bye
goodbye!
> Configure project : > Task :hello hello world! > Task :bye goodbye!

可以看出先执行了依赖的 hello 任务后才会执行 bye 任务。

finalizedBy

dependsOn 是在当前任务之前执行,finalizedBy 则是在当前任务完成后执行,示例代码:

task hello {
doLast{
utils.hello()
}
}
task bye{
doLast{
utils.goodbye()
}
}
hello.finalizedBy(bye)
task hello {







    doLast{


        utils.hello()
    }





}





task bye{
    doLast{
        utils.goodbye()
    }


}





hello.finalizedBy(bye)
task hello { doLast{ utils.hello() } } task bye{ doLast{ utils.goodbye() } } hello.finalizedBy(bye)

运行 ./gradlew -b study.gradle hello 后输出结果:

> Task :hello
hello world!
> Task :bye
goodbye!
> Task :hello
hello world!




> Task :bye
goodbye!
> Task :hello hello world! > Task :bye goodbye!

可以看到 bye 在 hello 之后执行了。

文件读写

读写相对简单,直接上代码:

task studyIO {
doLast{
String path = "D:/info.txt"
File file = new File(path)
// 创建文件
file.createNewFile()
//写入
file.withPrintWriter {printWriter ->
printWriter.println("time --> " + System.currentTimeMillis())
}
//读取
println("read file start")
println(file.text)
println("read file end")
}
}
task studyIO {
    doLast{


        String path = "D:/info.txt"
        File file = new File(path)
        // 创建文件
        file.createNewFile()

        //写入
        file.withPrintWriter {printWriter ->
            printWriter.println("time --> " + System.currentTimeMillis())
        }



        //读取
        println("read file start")
        println(file.text)
        println("read file end")
    }
}
task studyIO { doLast{ String path = "D:/info.txt" File file = new File(path) // 创建文件 file.createNewFile() //写入 file.withPrintWriter {printWriter -> printWriter.println("time --> " + System.currentTimeMillis()) } //读取 println("read file start") println(file.text) println("read file end") } }

运行结果,在 D 盘根目录下创建 info.txt 文件同时写入时间戳信息,并输出以下结果:

> Task :studyIO
read file start
time --> 1684317062511
read file end
> Task :studyIO
read file start
time --> 1684317062511
read file end
> Task :studyIO read file start time --> 1684317062511 read file end

当然也可以一行一行读取文件:

file.eachLine {
println(it)
}
file.eachLine {
    println(it)
}
file.eachLine { println(it) }

实战:Task 输出 Android 项目依赖信息

结合前面小节的示例来实现一个稍微复杂点的 Task,在项目根目录的 build.gradle 文件中写入以下代码:

// 定义 Task
task writeDependencyTreeToFile {
group("CustomTask")
description("Query dependency information and write to a file")
doLast {
// 创建文件 位置在项目根目录
File file = new File("${rootProject.projectDir}/dependency-tree.txt")
file.createNewFile()
file.withPrintWriter { printWriter ->
// 遍历项目模块
rootProject.allprojects { Project project ->
// 获取项目配置信息
project.configurations.each { Configuration configuration ->
// 遍历依赖
configurations.named('implementation').get().allDependencies.each { dependency ->
String info = "${dependency.group}:${dependency.name}:${dependency.version}"
// 打印依赖信息
println(info)
// 将以来信息写入文件中
printWriter.println(info)
}
}
}
}
}
}
// 定义 Task
task writeDependencyTreeToFile {
    group("CustomTask")
    description("Query dependency information and write to a file")
    doLast {

        // 创建文件 位置在项目根目录
        File file = new File("${rootProject.projectDir}/dependency-tree.txt")
        file.createNewFile()
        file.withPrintWriter { printWriter ->
            // 遍历项目模块
            rootProject.allprojects { Project project ->
                // 获取项目配置信息
                project.configurations.each { Configuration configuration ->
                    // 遍历依赖
                    configurations.named('implementation').get().allDependencies.each { dependency ->
                        String info = "${dependency.group}:${dependency.name}:${dependency.version}"
                        // 打印依赖信息
                        println(info)
                        // 将以来信息写入文件中
                        printWriter.println(info)
                    }
                }
            }
        }
    }
}
// 定义 Task task writeDependencyTreeToFile { group("CustomTask") description("Query dependency information and write to a file") doLast { // 创建文件 位置在项目根目录 File file = new File("${rootProject.projectDir}/dependency-tree.txt") file.createNewFile() file.withPrintWriter { printWriter -> // 遍历项目模块 rootProject.allprojects { Project project -> // 获取项目配置信息 project.configurations.each { Configuration configuration -> // 遍历依赖 configurations.named('implementation').get().allDependencies.each { dependency -> String info = "${dependency.group}:${dependency.name}:${dependency.version}" // 打印依赖信息 println(info) // 将以来信息写入文件中 printWriter.println(info) } } } } } }

运行 Task 后会在项目的根目录下生成 dependency-tree.txt 文件并写入依赖信息,控制台输出如下:

> Task :writeDependencyTreeToFile
androidx.core:core-ktx:1.7.0
com.google.android.material:material:1.5.0
androidx.constraintlayout:constraintlayout:2.1.3
androidx.lifecycle:lifecycle-process:2.4.0
androidx.appcompat:appcompat:1.0.0
androidx.activity:activity-ktx:1.0.0
androidx.fragment:fragment-ktx:1.1.0
androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1
androidx.lifecycle:lifecycle-livedata-ktx:2.5.1
androidx.lifecycle:lifecycle-runtime-ktx:2.5.1
androidx.core:core-ktx:1.7.0
// 太多了就省略了 ...
> Task :writeDependencyTreeToFile
androidx.core:core-ktx:1.7.0
com.google.android.material:material:1.5.0
androidx.constraintlayout:constraintlayout:2.1.3
androidx.lifecycle:lifecycle-process:2.4.0
androidx.appcompat:appcompat:1.0.0
androidx.activity:activity-ktx:1.0.0
androidx.fragment:fragment-ktx:1.1.0
androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1
androidx.lifecycle:lifecycle-livedata-ktx:2.5.1
androidx.lifecycle:lifecycle-runtime-ktx:2.5.1
androidx.core:core-ktx:1.7.0
// 太多了就省略了 ...
> Task :writeDependencyTreeToFile androidx.core:core-ktx:1.7.0 com.google.android.material:material:1.5.0 androidx.constraintlayout:constraintlayout:2.1.3 androidx.lifecycle:lifecycle-process:2.4.0 androidx.appcompat:appcompat:1.0.0 androidx.activity:activity-ktx:1.0.0 androidx.fragment:fragment-ktx:1.1.0 androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1 androidx.lifecycle:lifecycle-livedata-ktx:2.5.1 androidx.lifecycle:lifecycle-runtime-ktx:2.5.1 androidx.core:core-ktx:1.7.0 // 太多了就省略了 ...

当然,这只是个简单实现,里面有很多重复依赖并且也没有依赖结构,只是巩固一下 Task 相关知识。自定义 Task 的需求比较常见,随便举个例子,比如之前适配 AndroidS 时要求清单文件中 Activity 等组件都加入 android:exported="false" 属性,对于页面较多的老项目来说手动添加不太现实(一些第三方库的清单文件也无法直接修改),如果利用自定义 Task,实现一个解析清单文件并且按照需求添加 android:exported 属性的 Task,并且利用 finalizedBy 将其挂载到构建流程之后就能轻松实现。

Gradle Plugin

熟悉完 Task 接着再来看看 Gradle 中的 Plugin(插件)。

手写 Plugin

在 Android 项目中,buildSrc 目录是 Gradle 的一个特殊目录,用于编写插件和构建脚本相关的自定义代码。当 Gradle 构建时,会自动检测并处理 buildSrc 目录,将其作为构建脚本的一部分来执行。

首先,在项目中新建一个 Java or Kotlin Library Moudle 命名为 buildSrc,并且在其目录下的 build.gradle 文件中写入以下代码:

//apply plugin: 'groovy' // 使用 groovy 编写需要引入该插件
apply plugin: 'java'
apply plugin: 'java-library'
dependencies {
// 使用 gradle 提供的 api
implementation gradleApi()
// implementation localGroovy() // 使用 groovy 编写需要引入该插件
// 在这里也可以引入一些其他库,比如需要插件进行网络请求可以引入 okhttp 等等
}
//apply plugin: 'groovy' // 使用 groovy 编写需要引入该插件
apply plugin: 'java'
apply plugin: 'java-library'



dependencies {
    // 使用 gradle 提供的 api
    implementation gradleApi()
    // implementation localGroovy() // 使用 groovy 编写需要引入该插件
    // 在这里也可以引入一些其他库,比如需要插件进行网络请求可以引入 okhttp 等等
}
//apply plugin: 'groovy' // 使用 groovy 编写需要引入该插件 apply plugin: 'java' apply plugin: 'java-library' dependencies { // 使用 gradle 提供的 api implementation gradleApi() // implementation localGroovy() // 使用 groovy 编写需要引入该插件 // 在这里也可以引入一些其他库,比如需要插件进行网络请求可以引入 okhttp 等等 }

定义插件

插件相关代码和普通模块一样就放在 java 目录下,如果使用 groovy 来写就需要新建 groovy 目录,当然,用 kotlin 编写也是可以的。后面的示例代码都用 java 编写方便理解。

新建 MyPlugin.java 文件:

public class MyPlugin implements Plugin<Project> {
@Override
public void apply(Project project) { // project 参数可以获取项目的一些信息
System.out.println("This is my first plugin");
}
}
public class MyPlugin implements Plugin<Project> {
    @Override

    public void apply(Project project) { // project 参数可以获取项目的一些信息
        System.out.println("This is my first plugin");
    }

}
public class MyPlugin implements Plugin<Project> { @Override public void apply(Project project) { // project 参数可以获取项目的一些信息 System.out.println("This is my first plugin"); } }

插件需要继承自 org.gradle.api.Plugin 不要搞错了,这就完成了一个插件,引用该插件时会输出一条日志。

引用插件

一般插件都是供其他模块使用,那么需要先能够被其他模块识别,在 main 目录下新建
META-INF/gradle-plugins/my-plugin.properties 文件,注意目录不要错:

image.png

在其中写入:

implementation-class=top.sunhy.app.plugin.MyPlugin // 插件具体实现类
implementation-class=top.sunhy.app.plugin.MyPlugin // 插件具体实现类
implementation-class=top.sunhy.app.plugin.MyPlugin // 插件具体实现类

接着就可以在其他模块中使用了,回到 app 模块的 build.gradle 中引用该插件:

apply plugin: 'my-plugin'
apply plugin: 'my-plugin'
apply plugin: 'my-plugin'

这时再次运行项目时注意控制台的输出:

image.png

可以看到插件正常运行了。

插件扩展

插件也支持扩展,如插件需要发起网络请求,请求的基地址想通过其他模块自定义配置可以这么写。

定义扩展信息,新建一个 Java 类:

public class MyExtension {
String baseUrl = "http://localhost:8080/api";
String user = "admin";
}
public class MyExtension {
    String baseUrl = "http://localhost:8080/api";
    String user = "admin";
}
public class MyExtension { String baseUrl = "http://localhost:8080/api"; String user = "admin"; }

回到 MyPlugin 中添加以下代码:

public class MyPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
// 创建
MyExtension ext = project.getExtensions().create("network", MyExtension.class);
// 配置阶段完成后获取
project.afterEvaluate(p -> {
// 打印
System.out.println("Plugin baseUrl: " + ext.baseUrl);
System.out.println("Plugin user: " + ext.user);
});
}
}
public class MyPlugin implements Plugin<Project> {

    @Override
    public void apply(Project project) {
        // 创建
        MyExtension ext = project.getExtensions().create("network", MyExtension.class);
        // 配置阶段完成后获取
        project.afterEvaluate(p -> {
            // 打印
            System.out.println("Plugin baseUrl: " + ext.baseUrl);
            System.out.println("Plugin user: " + ext.user);
        });
    }
}
public class MyPlugin implements Plugin<Project> { @Override public void apply(Project project) { // 创建 MyExtension ext = project.getExtensions().create("network", MyExtension.class); // 配置阶段完成后获取 project.afterEvaluate(p -> { // 打印 System.out.println("Plugin baseUrl: " + ext.baseUrl); System.out.println("Plugin user: " + ext.user); }); } }

先不进行自定义配置,直接运行看结果:

image.png

输出的是默认值,接着进行自定义配置,在 app 目录下的 build.gradle 中增加以下代码:

apply plugin: 'my-plugin'
// 自定义
network {
baseUrl = "http://sunhy.com/api"
user = "sunhy"
}
apply plugin: 'my-plugin'

// 自定义
network {
    baseUrl = "http://sunhy.com/api"
    user = "sunhy"
}
apply plugin: 'my-plugin' // 自定义 network { baseUrl = "http://sunhy.com/api" user = "sunhy" }

运行结果:

image.png

实战:扫描项目中所有的 Activity

先来捋一下思路,在构建 apk 的一系列任务中有一个任务肯定是合并所有模块的 AndroidManifest.xml 文件,那么我们就在这个任务之后获取到最终 AndroidManifest.xml 所在的目录,用 xml 解析器解析出里面的标签即可。

那么第一个问题就来了,哪个任务会得到最终的 AndroidManifest.xml 呢?点一下运行按钮控制台就会输出一系列的 Task:

image.png

看名字也知道这个任务肯定是处理 AndroidManifest 文件,那么我们就利用之前 Task 小节提到的 doLast 来给这个 Task 增加一项操作,注意自定义插件 apply 方法的 project 参数,可以获取一些项目信息,其中就包含了 Task,接下来随便找个 xml 解析库解析一下就完事了,代码如下:

public class MyPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
// 注意:要在 afterEvaluate 里进行操作,配置阶段完成后才会形成任务图
// 否则会找不到对应的 Task
project.afterEvaluate(p -> p.getTasks().forEach(task -> { // 遍历所有task
// 根据名称找到 task
if (task.getName().equals("processDebugMainManifest")) {
// doLast 增加解析操作
task.doLast(t -> {
// 获取 Task 输出的文件
t.getOutputs().getFiles().getFiles().forEach(file -> {
// 输出的不止有一个文件,根据文件名判断
if (file.getName().equals("AndroidManifest.xml")) {
// 解析的过程就不多说了
Document doc = Jsoup.parse(file, "UTF-8");
Elements activityElements = doc.select("manifest > application > activity");
activityElements.forEach(element -> {
// 打印结果
System.out.println("Activity: " + element.attr("android:name"));
});
}
});
});
}
}));
}
}
public class MyPlugin implements Plugin<Project> {
    @Override

    public void apply(Project project) {
            // 注意:要在 afterEvaluate 里进行操作,配置阶段完成后才会形成任务图
            // 否则会找不到对应的 Task
            project.afterEvaluate(p -> p.getTasks().forEach(task -> { // 遍历所有task
            // 根据名称找到 task
            if (task.getName().equals("processDebugMainManifest")) {
                // doLast 增加解析操作
                task.doLast(t -> {
                    // 获取 Task 输出的文件
                    t.getOutputs().getFiles().getFiles().forEach(file -> {
                        // 输出的不止有一个文件,根据文件名判断
                        if (file.getName().equals("AndroidManifest.xml")) {
                            // 解析的过程就不多说了
                            Document doc = Jsoup.parse(file, "UTF-8");
                            Elements activityElements = doc.select("manifest > application > activity");
                            activityElements.forEach(element -> {
                                // 打印结果
                                System.out.println("Activity: " + element.attr("android:name"));
                            });
                        }
                    });
                });
            }
        }));
    }
}
public class MyPlugin implements Plugin<Project> { @Override public void apply(Project project) { // 注意:要在 afterEvaluate 里进行操作,配置阶段完成后才会形成任务图 // 否则会找不到对应的 Task project.afterEvaluate(p -> p.getTasks().forEach(task -> { // 遍历所有task // 根据名称找到 task if (task.getName().equals("processDebugMainManifest")) { // doLast 增加解析操作 task.doLast(t -> { // 获取 Task 输出的文件 t.getOutputs().getFiles().getFiles().forEach(file -> { // 输出的不止有一个文件,根据文件名判断 if (file.getName().equals("AndroidManifest.xml")) { // 解析的过程就不多说了 Document doc = Jsoup.parse(file, "UTF-8"); Elements activityElements = doc.select("manifest > application > activity"); activityElements.forEach(element -> { // 打印结果 System.out.println("Activity: " + element.attr("android:name")); }); } }); }); } })); } }

运行看下效果:

image.png

可以看到包括其他模块(libs)中的 Activity 也都打印了出来。

参考资料

Gradle 官方文档

最后

对于 Gradle 的 Task 和 Plugin 熟悉了之后下一篇博客将分享下具体的字节码插桩操作。本篇博客的示例代码均以 Java 为主,对于 groovy 语法来说还是有必要去学习一下,因为不是所有人都会用 Java 来写 Gradle 脚本模块,相比于 Java,Groovy 和 Kotlin 对于 Gradle 脚本的编写提供了很多语法糖,能够大大提高开发效率。

???如果我的博客分享对你有点帮助,不妨点个赞支持下???

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYKIdKFd' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片