Gradle 2.0 开始:
implementation ‘com.android.tools.build:gradle-api:3.0.1’
每个 Transform 其实都是一个 Gradle task,他们链式组合,前一个的输出作为下一个的输入,而我们自定义的 Transform 是作为第一个 task 最先执行的。
本文是基于 buildSrc 的方式定义 Gradle 插件的,因为只在 Demo 项目中应用,所以 buildSrc 的方式就够了。需要注意一点的是,buildSrc 方式要求 library module 的名称必须为 buildSrc,在实现中注意一下。
废话少说,直接上图:
buildSrc module:
在 buildSrc 中自定义一个基于 Groovy 的插件
在主项目 App 的 build.gradle 中引入自定义的 AsmPlugin
apply plugin: AsmPlugin
最后,在 settings.gradle 中加入 buildSrc module
include ‘:app’, ‘:buildSrc’
至此,我们就完成了一个自定义的插件,功能十分简陋,只是在控制台输出 “hello gradle plugin",让我们编译一下看看这个插件到底有没有生效。
好了,看到控制台的输出表明我们自定义的插件生效了,“作案地方”就此埋伏完毕。
后面会定义一个 AsmTransform,注册到 AsmPlugin 中,具体代码会在介绍 ASM 的时候贴出来。
ASM
有了搞事情的时机,怎么去修改字节码呢?此时神器 ASM 就出场了。
ASM 是一个功能比较齐全的 Java 字节码操作与分析框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接 产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类的行为。
更多细节可以去 [ASM 官网]( )
看看。
笔者写 Demo 的时候最新的版本是 7.0。
ASM 提供一种基于 Visitor 的 API,通过接口的方式,分离读 class 和写 class 的逻辑,提供一个 ClassReader 负责读取class字节码,然后传递给 Class Visitor 接口,Class Visitor 接口提供了很多 visitor 方法,比如 visit class,visit method 等,这个过程就像 ClassReader 带着 ClassVisitor 游览了 class 字节码的每一个指令。
光有读还不够,如果我们要修改字节码,ClassWriter 就出场了。ClassWriter 其实也是继承自 ClassVisitor 的,所做的就是保存字节码信息并最终可以导出,那么如果我们可以代理 ClassWriter 的接口,就可以干预最终生成的字节码了。
好,还是废话少说,直接上代码。
先看一下插件目录的结构
这里新建了 AsmTransform 插件,以及 class visitor 的 adapter(TestMethodClassAdapter),使得在 visit method 的时候可以调用自定义的 TestMethodVisitor。
同时,buildSrc 的 build.gradle 中也要引入 ASM 依赖
// ASM 相关
implementation ‘org.ow2.asm:asm:7.1’
implementation ‘org.ow2.asm:asm-util:7.1’
implementation ‘org.ow2.asm:asm-commons:7.1’
下面先来看一下 AsmTransform
import com.android.build.api.transform.DirectoryInput
import com.android.build.api.transform.Format
import com.android.build.api.transform.JarInput
import com.android.build.api.transform.QualifiedContent
import com.android.build.api.transform.Transform
import com.android.build.api.transform.TransformException
import com.android.build.api.transform.TransformInput
import com.android.build.api.transform.TransformInvocation
import com.android.build.api.transform.TransformOutputProvider
import com.android.build.gradle.internal.pipeline.TransformManager
import me.sure.asm.TestMethodClassAdapter
import org.apache.commons.io.FileUtils
import org.gradle.api.Project
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
class AsmTransform extends Transform {
Project project
AsmTransform(Project project) {
this.project = project
}
@Override
void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation)
println(“===== ASM Transform =====”)
println(“
t
r
a
n
s
f
o
r
m
I
n
v
o
c
a
t
i
o
n
.
i
n
p
u
t
s
"
)
p
r
i
n
t
l
n
(
"
{transformInvocation.inputs}") println("
transformInvocation.inputs")println("{transformInvocation.referencedInputs}”)
println(“
t
r
a
n
s
f
o
r
m
I
n
v
o
c
a
t
i
o
n
.
o
u
t
p
u
t
P
r
o
v
i
d
e
r
"
)
p
r
i
n
t
l
n
(
"
{transformInvocation.outputProvider}") println("
transformInvocation.outputProvider")println("{transformInvocation.incremental}”)
//当前是否是增量编译
boolean isIncremental = transformInvocation.isIncremental()
//消费型输入,可以从中获取jar包和class文件夹路径。需要输出给下一个任务
Collection inputs = transformInvocation.getInputs()
//引用型输入,无需输出。
Collection referencedInputs = transformInvocation.getReferencedInputs()
//OutputProvider管理输出路径,如果消费型输入为空,你会发现OutputProvider == null
TransformOutputProvider outputProvider = transformInvocation.getOutputProvider()
for (TransformInput input : inputs) {
for (JarInput jarInput : input.getJarInputs()) {
File dest = outputProvider.getContentLocation(
jarInput.getFile().getAbsolutePath(),
jarInput.getContentTypes(),
jarInput.getScopes(),
Format.JAR)
//将修改过的字节码copy到dest,就可以实现编译期间干预字节码的目的了
transformJar(jarInput.getFile(), dest)
}
for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
println("== DI = " + directoryInput.file.listFiles().toArrayString())
File dest = outputProvider.getContentLocation(directoryInput.getName(),
directoryInput.getContentTypes(), directoryInput.getScopes(),
Format.DIRECTORY)
//将修改过的字节码copy到dest,就可以实现编译期间干预字节码的目的了
//FileUtils.copyDirectory(directoryInput.getFile(), dest)
transformDir(directoryInput.getFile(), dest)
}
}
}
@Override
String getName() {
return AsmTransform.simpleName
}
@Override
Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS
}
@Override
Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT
}
@Override
boolean isIncremental() {
return true
}
private static void transformJar(File input, File dest) {
println(“=== transformJar ===”)
FileUtils.copyFile(input, dest)
}
private static void transformDir(File input, File dest) {
if (dest.exists()) {
FileUtils.forceDelete(dest)
}
FileUtils.forceMkdir(dest)
String srcDirPath = input.getAbsolutePath()
String destDirPath = dest.getAbsolutePath()
println("=== transform dir = " + srcDirPath + ", " + destDirPath)
for (File file : input.listFiles()) {
String destFilePath = file.absolutePath.replace(srcDirPath, destDirPath)
File destFile = new File(destFilePath)
if (file.isDirectory()) {
transformDir(file, destFile)
} else if (file.isFile()) {
FileUtils.touch(destFile)
transformSingleFile(file, destFile)
}
}
}
private static void transformSingleFile(File input, File dest) {
println(“=== transformSingleFile ===”)
weave(input.getAbsolutePath(), dest.getAbsolutePath())
}
private static void weave(String inputPath, String outputPath) {
try {
FileInputStream is = new FileInputStream(inputPath)
ClassReader cr = new ClassReader(is)
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES)
TestMethodClassAdapter adapter = new TestMethodClassAdapter(cw)
cr.accept(adapter, 0)
FileOutputStream fos = new FileOutputStream(outputPath)
fos.write(cw.toByteArray())
fos.close()
} catch (IOException e) {
e.printStackTrace()
}
}
}
我们的 InputTypes 是 CONTENT_CLASS, 表明是 class 文件,Scope 先无脑选择 SCOPE_FULL_PROJECT 在 transform 方法中主要做的事情就是把 Inputs 保存到 outProvider 提供的位置去。生成的位置见下图:
对照代码,主要有两个 transform 方法,一个 transformJar 就是简单的拷贝,另一个 transformSingleFile,我们就是在这里用 ASM 对字节码进行修改的。 关注一下 weave 方法,可以看到我们借助 ClassReader 从 inputPath 中读取输入流,在 ClassWriter 之前用一个 adapter 进行了封装,接下来就让我们看看 adapter 做了什么。
public class TestMethodClassAdapter extends ClassVisitor implements Opcodes {
public TestMethodClassAdapter(ClassVisitor classVisitor) {
super(ASM7, classVisitor);
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
840)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!