!AGP8+ 的 Transform + ASM 模板

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
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值