Android Gradle Transform 学习

本文介绍了如何在Gradle配置中使用Kotlin插件,并且详细阐述了一个自定义的XXXPlugin,它定义了一个BaseTransform基类和XXXTransform实现,用于处理特定类文件的编译和转换过程。
摘要由CSDN通过智能技术生成

build.gradle:

plugins {
    id 'java'
    //id 'groovy'
    id 'org.jetbrains.kotlin.jvm' version '1.5.31'
    id 'java-gradle-plugin'
}

repositories {
    google()
    mavenCentral()
}

java {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}

compileKotlin {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}

dependencies {
    //implementation localGroovy()
    //implementation gradleApi()
    implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31"
    implementation "com.android.tools.build:gradle:7.0.4"
    implementation "com.android.tools.build:gradle-api:7.0.4"
    implementation "commons-io:commons-io:2.9.0"
    //implementation "commons-codec:commons-codec:1.15"
    implementation "org.ow2.asm:asm:9.2"
    implementation "org.ow2.asm:asm-tree:9.2"
}

XXXPlugin:

import com.android.build.gradle.BaseExtension
import org.gradle.api.Plugin
import org.gradle.api.Project

/**
 * @author ganmin.he
 * @date 2022/1/5
 */
class XXXPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        println("Welcome to xxx plugin...")
        val ext = project.extensions.getByType(BaseExtension::class.java)
        ext.registerTransform(XXXTransform(project))
    }
}

BaseTransform:

import com.android.build.api.transform.*
import com.android.build.gradle.internal.pipeline.TransformManager
import org.apache.commons.io.FileUtils
import org.gradle.api.Project
import java.io.File

/**
 * @author ganmin.he
 * @date 2022/1/5
 */
abstract class BaseTransform(protected val project: Project) : Transform() {
    protected val tag = javaClass.simpleName

    protected fun log(msg: String) {
        project.logger.lifecycle("$tag: $msg")
    }

    abstract override fun getName(): String

    override fun getInputTypes(): MutableSet<QualifiedContent.ContentType> {
        return TransformManager.CONTENT_CLASS
    }

    override fun getScopes(): MutableSet<in QualifiedContent.Scope> {
        return TransformManager.SCOPE_FULL_PROJECT
    }

    final override fun isIncremental(): Boolean = true

    /**
     * @return true if transformed jar has been written to destOutput, else false.
     */
    abstract fun doTransformJar(jarInput: JarInput, destOutput: File): Boolean

    /**
     * @return true if transformed file has been written to destOutput, else false.
     */
    abstract fun doTransformFile(fileInput: File, destOutput: File): Boolean

    final override fun transform(transformInvocation: TransformInvocation) {
        log("start transform")

        val isIncremental = transformInvocation.isIncremental

        // Consumer input, the jar and class directory path can be obtained from it,
        // and it needs to be output to the next transformer.
        val transformInputs = transformInvocation.inputs

        // Reference input, no output required.
        val referenceInputs = transformInvocation.referencedInputs

        // outputProvider manage output paths.
        // If transformInputs is empty, outputProvider will be null.
        val outputProvider = transformInvocation.outputProvider

        // The source file of the sub-module will also generate a jar during the compilation process
        // and then be compiled into the main project.
        transformInputs.forEach { input ->
            input.jarInputs.forEach jarInputs@{ jarInput ->
                //log("jarInput $jarInput")

                val destJar = outputProvider.getContentLocation(
                    jarInput.name,
                    jarInput.contentTypes,
                    jarInput.scopes,
                    Format.JAR
                )

                if (isIncremental) {
                    when (jarInput.status) {
                        Status.REMOVED -> {
                            log("delete $destJar")
                            FileUtils.deleteQuietly(destJar)
                            return@jarInputs
                        }
                        Status.NOTCHANGED -> return@jarInputs
                        Status.ADDED, Status.CHANGED -> {}
                    }
                }

                if (!doTransformJar(jarInput, destJar)) {
                    // Copy the unmodified bytecode to dest.
                    FileUtils.copyFile(jarInput.file, destJar)
                }
            }

            input.directoryInputs.forEach { directoryInput ->
                //log("directoryInput ${directoryInput.file}")

                val destDir = outputProvider.getContentLocation(
                    directoryInput.name,
                    directoryInput.contentTypes,
                    directoryInput.scopes,
                    Format.DIRECTORY
                )

                if (isIncremental) {
                    directoryInput.changedFiles.forEach changedFiles@{ (file, status) ->
                        val destFile = File(destDir, file.relativeTo(directoryInput.file).path)
                        when (status) {
                            Status.REMOVED -> {
                                log("delete $destFile")
                                FileUtils.deleteQuietly(destFile)
                                return@changedFiles
                            }
                            Status.NOTCHANGED -> return@changedFiles
                            Status.ADDED, Status.CHANGED -> {
                                if (!doTransformFile(file, destFile)) {
                                    // Copy the unmodified bytecode to dest.
                                    log("copy to $destFile")
                                    FileUtils.copyFile(file, destFile)
                                }
                            }
                        }
                    }
                } else {
                    FileUtils.deleteQuietly(destDir)
                    FileUtils.iterateFiles(directoryInput.file, null, true).forEach { file ->
                        val destFile = File(destDir, file.relativeTo(directoryInput.file).path)
                        if (!doTransformFile(file, destFile)) {
                            // Copy the unmodified bytecode to dest.
                            //log("copy to $destFile")
                            FileUtils.copyFile(file, destFile)
                        }
                    }
                }
            }
        }

        log("end transform")
    }
}

XXXTransform:

import com.android.build.api.transform.*
import org.gradle.api.Project
import java.io.File
import java.io.FileOutputStream
import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream

/**
 * @author ganmin.he
 * @date 2022/1/5
 */
class TrackTransform(project: Project) : BaseTransform(project) {
    companion object {
        private const val TARGET = "a/b/c/Target.class"
        private val TARGETS = arrayOf(TARGET)
    }

    override fun getName(): String = "xxx"

    override fun doTransformJar(jarInput: JarInput, destOutput: File): Boolean {
        JarFile(jarInput.file).use { jarFile ->
            val entries = jarFile.entries().toList()
            val filter = entries.filter {
                TARGETS.contains(it.name)
            }
            if (filter.isEmpty()) return false
            // Write the modified bytecode directly to destOutput.
            JarOutputStream(FileOutputStream(destOutput)).use { jos ->
                entries.forEach { other ->
                    if (filter.contains(other)) return@forEach
                    jos.putNextEntry(JarEntry(other.name))
                    jarFile.getInputStream(other).use {
                        jos.write(it.readAllBytes())
                    }
                    jos.closeEntry()
                }

                filter.forEach { targetEntry ->
                    log("found ${targetEntry.name} in ${jarInput.file}")
                    jos.putNextEntry(JarEntry(targetEntry.name))
                    jarFile.getInputStream(targetEntry).use {
                        when (targetEntry.name) {
                            TARGET -> ModifyTarget.modifyByteCode(it)
                            else -> it.readAllBytes()
                        }
                    }.let {
                        jos.write(it)
                    }
                    jos.closeEntry()
                }

                log("write to $destOutput")
            }
            return true
        }
    }

    override fun doTransformFile(fileInput: File, destOutput: File): Boolean = false
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值