Transform 三步走
第一步
获取上一个 Transform 的输入 invocation.getInputs()
,其中输入包括:
- jar 文件
- class 文件
第二部
分别处理 jar 部分
和 class 部分
-
获取所有 jar 输入:transformInput.getJarInputs()
-
获取所有 class 输入:transformInput.getDirectoryInputs()
所有的 class 输入都放置在一个目录下面,所以我们要遍历该目录下的所有 class 文件,为下一步处理做准备
第三部
遍历 directoryInput
目录可直接获得 class 文件,通常修改 class 文件我们用 ASM框架 处理字节码文件。
ASM 处理字节码输入的是字节码文件的字节数组byte[]
,处理完毕得到新的字节数组,重新写入文件,覆盖已有文件即可;
同样的对于 jar 部分,我们通过 JarFile
遍历压缩包获取 class 文件得到字节数组使用 ASM 操作即可。
详细部分参考下文的:processJarFile
注意:
至于要处理那些 class 文件,要如何处理?根据需求实现 processClassFileByAsm
方法
ASM 如何使用参考官网文档。
完整代码
package com.vimedia.plugin.transform
import com.android.build.api.transform.*
import com.android.build.gradle.internal.pipeline.TransformManager
import com.**.plugin.common.utils.IOUtils
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardOpenOption
import java.util.function.Consumer
import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
@SuppressWarnings("sunapi")
class CheckPluginTransform extends Transform {
CheckPluginTransform() {
}
@Override
String getName() {
return CheckPluginTransform.class.getSimpleName()
}
@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 invocation) throws IOException {
TransformOutputProvider outputProvider = invocation.getOutputProvider()
//处理上一个 transform 所有的输入
invocation.getInputs().forEach(new Consumer<TransformInput>() {
@Override
void accept(TransformInput transformInput) {
//处理所有 jar
transformInput.getJarInputs().forEach(new Consumer<JarInput>() {
@Override
void accept(JarInput jarInput) {
File destFile = outputProvider.getContentLocation(jarInput.getName(),
jarInput.getContentTypes(), jarInput.getScopes(), Format.JAR)
try {
processJarFile(jarInput, destFile)
} catch (Exception e) {
println("check plugin transform getInputs error = " + e)
}
}
})
//处理所有目录下的 class 文件(transform 的输入不全部都是 jar)
transformInput.getDirectoryInputs().forEach(new Consumer<DirectoryInput>() {
@Override
void accept(DirectoryInput directoryInput) {
File destDir = outputProvider.getContentLocation(directoryInput.getName(),
directoryInput.getContentTypes(), directoryInput.getScopes(), Format.DIRECTORY)
println("directoryInput destDir = " + destDir.getAbsolutePath() + " inFile = " + directoryInput.getFile().getAbsolutePath())
Path sourcePath = directoryInput.getFile().toPath()
Path destPath = destDir.toPath()
try {
Files.walk(sourcePath)
.sorted(Comparator.naturalOrder())
.forEach {
Path relativePath = sourcePath.relativize(it)
Path targetPath = destPath.resolve(relativePath)
println("relativePath = " + relativePath.toFile().getAbsolutePath()
+ "\ttargetPath = " + targetPath.toFile().getAbsolutePath()
+ "\tit = " + it.toFile().getAbsolutePath())
if (Files.isDirectory(it)) {
Files.createDirectories(targetPath)
} else if (Files.isRegularFile(it)) {
processDirClassFile(it.toFile(), targetPath.toFile())
}
}
} catch (IOException e) {
println("check plugin transform getDirectoryInputs error = " + e)
}
}
})
}
})
}
/**
*
* @param rawClassFile 目录下的 class 文件
* @param outputFile 下一个 transform 的输出文件
*/
private void processDirClassFile(File rawClassFile, File outputFile) {
println("处理目录下的 class 文件\n\t输入 =" + rawClassFile.getAbsolutePath() + "\n\t输出 =" + outputFile.getAbsolutePath())
//创建父文件,否则容易目录不存在导致下面文件创建失败
def parentFile = outputFile.getParentFile()
if (parentFile.isDirectory() && !parentFile.exists()) {
parentFile.mkdirs()
}
def inputStream = new FileInputStream(rawClassFile)
//byte[] -> asm -> byte[],通过 asm 操作最终获得新的字节数组便是修改后的 classes 文件
//byte[] originBytes = IOUtils.toByteArray(inputStream)
//byte[] newBytes = processClassFileByAsm(originBytes,rawClassFile.getName())
//使用 StandardOpenOption.CREATE:如果文件不存在则创建
//使用 StandardOpenOption.TRUNCATE_EXISTING:如果文件存在则覆盖
//无需自己创建文件
Files.write(Paths.get(outputFile.getPath()), IOUtils.toByteArray(inputStream),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)
}
private void processJarFile(JarInput jarInput, File destFile) throws Exception {
def inFile = jarInput.getFile()
if (!inFile.getName().endsWith(".jar")) {
return
}
JarOutputStream outputStream = new JarOutputStream(new FileOutputStream(destFile))
JarFile inJarFile = new JarFile(jarInput.getFile())
def rawEntries = inJarFile.entries()
//遍历每一个 class
while (rawEntries.hasMoreElements()) {
def element = rawEntries.nextElement()
outputStream.putNextEntry(new JarEntry(element.getName()))
def elementInStream = inJarFile.getInputStream(element)
//转成 byte 数组输入到 asm
byte[] originBytes = IOUtils.toByteArray(elementInStream)
//byte[] -> asm -> byte[],通过 asm 操作最终获得新的字节数组便是修改后的 classes 文件
//byte[] newBytes = processClassFileByAsm(originBytes,element.getName())
outputStream.write(originBytes)
outputStream.closeEntry()
}
outputStream.close()
inJarFile.close()
//使用 StandardOpenOption.CREATE:如果文件不存在则创建
//使用 StandardOpenOption.TRUNCATE_EXISTING:如果文件存在则覆盖
//无需自己创建文件
Files.write(Paths.get(inFile.getPath()), IOUtils.toByteArray(new FileInputStream(destFile)),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)
}
/**
* 使用 asm 框架根据需求处理原 class 文件,符合要求时输出新的 class 字节数组
* @param originClassBytes 原 class 字节输出
* @param filename 原 class 文件名
* @return
*/
private byte[] processClassFileByAsm(byte[] originClassBytes, String filename) {
return null
}
}