All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sunyuan.click.debounce.DebounceTransform.kt Maven / Gradle / Ivy

package com.sunyuan.click.debounce

import CollectNeedHookMethodInformationVisitor
import com.android.build.api.transform.*
import com.android.build.api.variant.VariantInfo
import com.android.build.gradle.internal.pipeline.TransformManager
import com.android.utils.FileUtils
import com.sunyuan.click.debounce.extension.DebounceExtension
import com.sunyuan.click.debounce.utils.*
import com.sunyuan.click.debounce.visitor.ClickClassVisitor
import org.apache.commons.codec.digest.DigestUtils
import org.gradle.api.Project
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import java.io.File
import java.io.IOException
import java.net.URLClassLoader


/**
 * author : Sy007
 * date   : 2020/11/28
 * desc   :
 * version: 1.0
 */
private const val TRANSFORM_NAME = "DebounceTransform"

open class DebounceTransform(private val project: Project) : Transform() {
    private lateinit var mContext: Context
    private lateinit var mDebounceExtension: DebounceExtension
    private val mSpecifiedInterfaceImplChecked = SpecifiedInterfaceImplChecked()


    init {
        project.afterEvaluate {
            val debounceExtension: DebounceExtension =
                project.extensions.findByName(EXTENSION_NAME) as DebounceExtension
            ConfigUtil.sDebug = debounceExtension.isDebug
            ConfigUtil.sDebounceCheckTime = debounceExtension.debounceCheckTime
            LogUtil.sLogger = project.logger
            debounceExtension.init()
            debounceExtension.printlnConfigInfo()
            mDebounceExtension = debounceExtension
        }
    }


    override fun getName(): String {
        return TRANSFORM_NAME
    }


    override fun getInputTypes(): MutableSet {
        return TransformManager.CONTENT_CLASS
    }


    override fun isIncremental(): Boolean {
        return true
    }

    override fun getScopes(): MutableSet {
        return TransformManager.SCOPE_FULL_PROJECT
    }

    override fun transform(transformInvocation: TransformInvocation?) {
        super.transform(transformInvocation)
        transformInvocation ?: return
        mContext = transformInvocation.context
        val inputs = transformInvocation.inputs
        val outputProvider = transformInvocation.outputProvider
        val isIncremental = transformInvocation.isIncremental
        if (!isIncremental) {
            //如果不支持增量更新。
            outputProvider.deleteAll()
        }
        val urlClassLoader: URLClassLoader =
            ClassLoaderHelper.getClassLoader(inputs, transformInvocation.referencedInputs, project)
        mSpecifiedInterfaceImplChecked.setUrlClassLoader(urlClassLoader)
        inputs.forEach { transformInput ->
            transformInput.jarInputs.forEach { jarInput ->
                val inputJar = jarInput.file
                val outputJar = getOutPutJar(jarInput, outputProvider)
                if (isIncremental) {
                    when (jarInput.status) {
                        Status.ADDED, Status.CHANGED -> transformJar(inputJar, outputJar)
                        Status.REMOVED -> FileUtils.delete(outputJar)
                        else -> {//null or  Status.NOTCHANGED

                        }
                    }
                } else {
                    transformJar(inputJar, outputJar)
                }
            }
            transformInput.directoryInputs.forEach { di ->
                val inputDir: File = di.file
                val outputDir = outputProvider.getContentLocation(
                    di.name,
                    di.contentTypes,
                    di.scopes,
                    Format.DIRECTORY
                )
                if (isIncremental) {
                    for ((inputFile, value) in di.changedFiles) {
                        when (value) {
                            Status.ADDED, Status.CHANGED -> {
                                transformSingleFile(inputFile, inputDir, outputDir)
                            }
                            Status.REMOVED -> {
                                val outputFile = toOutputFile(outputDir, inputDir, inputFile)
                                FileUtils.deleteIfExists(outputFile)
                            }
                            else -> {//null or  Status.NOTCHANGED

                            }
                        }
                    }
                } else {
                    if (inputDir.isDirectory) {
                        inputDir.copyRecursively(outputDir, true)
                        inputDir.walk()
                            .maxDepth(Int.MAX_VALUE)
                            .filter {
                                it.isFile
                            }.forEach { inputFile ->
                                transformSingleFile(inputFile, inputDir, outputDir)
                            }

                    }
                }
            }
        }
    }


    private fun transformSingleFile(inputFile: File, inputDir: File, outputDir: File) {
        if (ClassUtil.checkClassName(inputFile.name)) {
            val relativePath: String =
                inputFile.absolutePath.replace(inputDir.absolutePath + File.separator, "")
            var modifiedBytes: ByteArray? = null
            if (mDebounceExtension.match(relativePath)) {
                modifiedBytes = modifyClass(inputFile.readBytes())
            }
            if (modifiedBytes != null) {
                val outputFile = toOutputFile(outputDir, inputDir, inputFile)
                outputFile.writeBytes(modifiedBytes)
            }
        }
    }

    private fun toOutputFile(outputDir: File, inputDir: File, inputFile: File): File {
        val filePath: String = inputFile.absolutePath
        return File(filePath.replace(inputDir.absolutePath, outputDir.absolutePath))
    }


    private fun transformJar(
        inputJar: File,
        outputJar: File
    ) {
        val modifiedJar: File =
            JarUtil.modifyJarFile(
                inputJar,
                mContext.temporaryDir
            ) { relativePath, sourceBytes ->
                //relativePath->com/xxx/yyy/zzz.class
                if (mDebounceExtension.match(relativePath)) {
                    return@modifyJarFile modifyClass(sourceBytes)
                } else {
                    return@modifyJarFile null
                }
            }
        modifiedJar.copyTo(outputJar, true)
    }

    private fun getOutPutJar(jarInput: JarInput, outputProvider: TransformOutputProvider): File {
        var destName = jarInput.file.name
        val hexName = DigestUtils.md5Hex(
            jarInput.file.absolutePath
        ).substring(0, 8)
        if (destName.endsWith(".jar")) {
            destName = destName.substring(0, destName.length - 4)
        }
        return outputProvider.getContentLocation(
            destName + "_" + hexName,
            jarInput.contentTypes,
            jarInput.scopes,
            Format.JAR
        )
    }


    @Throws(IOException::class)
    fun modifyClass(srcClass: ByteArray): ByteArray? {
        val cw = ClassWriter(ClassWriter.COMPUTE_MAXS)
        val cv = ClickClassVisitor(cw)
        val needHookMethodInformationVisitor =
            CollectNeedHookMethodInformationVisitor(cv, mSpecifiedInterfaceImplChecked)
        val cr = ClassReader(srcClass)
        cr.accept(needHookMethodInformationVisitor, ClassReader.EXPAND_FRAMES)
        return cw.toByteArray()
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy