Transform 的基本认识

前言

Android Transform Api 是 Android Gradle Plugin 中提供的一个hook api,该api提供了在 class 转换为 dex 前的加工处理入口,通过该api,我们可以对执行class内容进行更改调整。

Transform 的基本认识

Transform 实际是一个普通的回调入口,该回调将根据 Transform 所返回的各个配置,在 class 转换为 dex 前,根据配置内容进行调用,并将结果通过 transform(Context, Collection<TransformInput>, Collection<TransformInput>, TransformOutputProvider, boolean)【过期】 或 transform(TransformInvocation) 进行返回。

配置

在 Transform 中有几个必须实现的内容

class ClassCollectorTransform extends Transform{

    @Override
    String getName() {
        return null
    }

    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return null
    }

    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return null
    }
    
    @Override
    boolean isIncremental() {
        return false
    }
}

方法名功能备注
getNameTransform Task 命名,该返回名不是作为最后的命名,任务会自动补全该名字 
getInputType()Transform 需要输入的内容类型 
getScopesTransform 搜索范围类型 
isIncemental是否支持增量编译 

主要参数说明:

InputType

该参数指名需要操作的内容,主要:

  • CLASSES Java代码
  • RESOURCES Java Resource 资源

除此之外官方提供了一些其他操作内容,但是受限于前置任务的产出物,并不能完全适用于当前阶段的 transform(指定操作内容必须是已产生的产出物)

  • DEX
  • NATIVE_LIBS
  • CLASSES_ENHANCED
  • DATA_BINDING
  • JAVA_SOURCES
  • DEX_ARCHIVE

Scopes

该参数指明需要搜索的目标范围,主要提供以下几种内容:

  • PROJECT 当前项目内容
  • SUB_PROJECTS 子项目内容
  • EXTERNAL_LIBRARIES 外部依赖库
  • TESTED_CODE 测试代码
  • PROVIDED_ONLY provider 方式的本地或者远程依赖
  • PROJECT_LOCAL_DEPS 项目本地依赖(local jars)
  • SUB_PROJECTS_LOCAL_DEPS 子项目的本地依赖(local jars)
根据以上参数,基本上我们通过使用 InputType + Scope 能实现对应目标内容的输入源的控制。

使用

配置完所需要的配置后,接下来就是对所输出的内容进行转换的过程。

在旧版中,内容是被输出到:

transform(Context contenxt, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental)方法中

这里我们以新版为例:

transform(TransformInvocation transformInvocation)

TransformInvocation 只是对旧版的封装,及其增加一些补充内容,我们只对目前比较常用的一些参数进行描述。

Context getContext()

返回任务上下文,实质该Context为 TransformTask 的父类,TransformTask 是继承自 DefaultTask 的任务子类

Collection<TransformInput> getInputs()

返回InputType Scopes所要求的内容,TransformInput 提供了两种结果输出,一种是jar,一种是 File(被DirectoryInput包装了) 输出,这两种内容我们都需要进行处理。

TransformOutputProvider getOutputProvider()

TransformOutputProvider 是官方提供的输出代理类,通过该工具类,我们可以将需要指定输出的内容输出到所要求的目录下。

下面用一个例子来简单操作下:


class ExampleTransform extends Transform {

    @Override
    String getName() {
        return "Example"
    }

    @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 transformInvocation) throws TransformException, InterruptedException, IOException {
        super.transform(transformInvocation)
        println "begin transform"

        transformInvocation.getInputs().each { TransformInput input ->
            input.directoryInputs.each { DirectoryInput directoryInput->
                File outputFile = transformInvocation.getOutputProvider().getContentLocation(directoryInput.name,
                        directoryInput.contentTypes, directoryInput.scopes,
                        Format.DIRECTORY)
                FileUtils.copyDirectory(directoryInput.file, outputFile)

                println "directory input  >>> ${directoryInput.file.getAbsolutePath()}"
                println "directory output >>> ${outputFile.getAbsolutePath()} \n"
            }

            input.jarInputs.each { JarInput jarInput->
                String jarName = jarInput.name
                File outputFile = transformInvocation.getOutputProvider().getContentLocation(jarName,
                        jarInput.contentTypes, jarInput.scopes, Format.JAR)

                FileUtils.copyFile(jarInput.file, outputFile)

                println "jar input  >>> ${jarInput.file.getAbsolutePath()}"
                println "jar output >>> ${outputFile.getAbsolutePath()}\n"


            }
        }

        println "end transform"
    }
}

输出内容:

...
:app:mergeDebugAssets
:app:transformClassesWithExampleForDebug
begin transform
jar input  >>> /Users/XX/.gradle/caches/transforms-1/files-1.1/appcompat-v7-27.1.1.aar/7228f687c4d3d50971fb2c4144b818e5/jars/classes.jar
jar output >>> /Users/XX/Desktop/Git/transformExample/app/build/intermediates/transforms/Example/debug/0.jar

jar input  >>> /Users/XX/.gradle/caches/transforms-1/files-1.1/constraint-layout-1.1.2.aar/1ffd866ac236943344a2db8900ec9ff0/jars/classes.jar
jar output >>> /Users/XX/Desktop/Git/transformExample/app/build/intermediates/transforms/Example/debug/1.jar

jar input  >>> /Users/XX/.gradle/caches/transforms-1/files-1.1/support-fragment-27.1.1.aar/366533887dab6e0bdc7b5f9565c8df3f/jars/classes.jar
jar output >>> /Users/XX/Desktop/Git/transformExample/app/build/intermediates/transforms/Example/debug/2.jar

jar input  >>> /Users/XX/.gradle/caches/transforms-1/files-1.1/animated-vector-drawable-27.1.1.aar/9aa09e1b40578aaf51e0fe3c26b1cf2b/jars/classes.jar
jar output >>> /Users/XX/Desktop/Git/transformExample/app/build/intermediates/transforms/Example/debug/3.jar

jar input  >>> /Users/XX/.gradle/caches/transforms-1/files-1.1/support-core-ui-27.1.1.aar/fbf121a4aa433efd0847730e6a71dfa6/jars/classes.jar
jar output >>> /Users/XX/Desktop/Git/transformExample/app/build/intermediates/transforms/Example/debug/4.jar

jar input  >>> /Users/XX/.gradle/caches/transforms-1/files-1.1/support-core-utils-27.1.1.aar/249c37a99952d8b3446a0f10707ef631/jars/classes.jar
jar output >>> /Users/XX/Desktop/Git/transformExample/app/build/intermediates/transforms/Example/debug/5.jar

jar input  >>> /Users/XX/.gradle/caches/transforms-1/files-1.1/support-vector-drawable-27.1.1.aar/0e11925044cb0cd7243a520ef57b6b65/jars/classes.jar
jar output >>> /Users/XX/Desktop/Git/transformExample/app/build/intermediates/transforms/Example/debug/6.jar

jar input  >>> /Users/XX/.gradle/caches/transforms-1/files-1.1/support-compat-27.1.1.aar/89e06865e27940d83a4ef0b84e7a40ec/jars/classes.jar
jar output >>> /Users/XX/Desktop/Git/transformExample/app/build/intermediates/transforms/Example/debug/7.jar

jar input  >>> /Users/XX/.gradle/caches/modules-2/files-2.1/com.android.support/support-annotations/27.1.1/39ded76b5e1ce1c5b2688e1d25cdc20ecee32007/support-annotations-27.1.1.jar
jar output >>> /Users/XX/Desktop/Git/transformExample/app/build/intermediates/transforms/Example/debug/8.jar

jar input  >>> /Users/XX/.gradle/caches/modules-2/files-2.1/com.android.support.constraint/constraint-layout-solver/1.1.2/bfc967828daffc35ba01c9ee204d98b664930a0f/constraint-layout-solver-1.1.2.jar
jar output >>> /Users/XX/Desktop/Git/transformExample/app/build/intermediates/transforms/Example/debug/9.jar

jar input  >>> /Users/XX/.gradle/caches/transforms-1/files-1.1/livedata-core-1.1.0.aar/63c28b9d84c74954069ca32d59a8948a/jars/classes.jar
jar output >>> /Users/XX/Desktop/Git/transformExample/app/build/intermediates/transforms/Example/debug/10.jar

jar input  >>> /Users/XX/.gradle/caches/transforms-1/files-1.1/viewmodel-1.1.0.aar/e7d22623b4477da4befa3300011bae7f/jars/classes.jar
jar output >>> /Users/XX/Desktop/Git/transformExample/app/build/intermediates/transforms/Example/debug/11.jar

jar input  >>> /Users/XX/.gradle/caches/transforms-1/files-1.1/runtime-1.1.0.aar/ddae57800275e6f4fbfcddbc1689ac1c/jars/classes.jar
jar output >>> /Users/XX/Desktop/Git/transformExample/app/build/intermediates/transforms/Example/debug/12.jar

jar input  >>> /Users/XX/.gradle/caches/modules-2/files-2.1/android.arch.lifecycle/common/1.1.0/edf3f7bfb84a7521d0599efa3b0113a0ee90f85/common-1.1.0.jar
jar output >>> /Users/XX/Desktop/Git/transformExample/app/build/intermediates/transforms/Example/debug/13.jar

jar input  >>> /Users/XX/.gradle/caches/transforms-1/files-1.1/runtime-1.1.0.aar/6a2bd34a44e70a5ec1df7445a52ad01e/jars/classes.jar
jar output >>> /Users/XX/Desktop/Git/transformExample/app/build/intermediates/transforms/Example/debug/14.jar

jar input  >>> /Users/XX/.gradle/caches/modules-2/files-2.1/android.arch.core/common/1.1.0/8007981f7d7540d89cd18471b8e5dcd2b4f99167/common-1.1.0.jar
jar output >>> /Users/XX/Desktop/Git/transformExample/app/build/intermediates/transforms/Example/debug/15.jar

directory input  >>> /Users/XX/Desktop/Git/transformExample/app/build/intermediates/classes/debug
directory output >>> /Users/XX/Desktop/Git/transformExample/app/build/intermediates/transforms/Example/debug/16 

end transform
...

在以上例子中我们并未对拦截到的class内容进行任何操作,仅仅只是将它们进行转移,这里需要注意的是,我们对于拦截的内容,需要手动转移到指定的 getContentLocation 地址下,不能随意进行存储,否则将不能正常处理转换后的内容。

另外根据所指定的产物内容的不同,getContentLocation所存放的地址也不一样。

这里值得注意的是,如果我们忽略了部分代码的转移,那么在最后的包中,将不再有该代码文件的存在。

TransformManager 提供了较多常用的封装工具,可以参考这里面的API进行调用

总结

结合以上的内容,已经可以初步获取到所需要产物的输入内容,并能将所输出的内容转移到对应的目录下。在这个流程中,我们可以任意对输入内容进行修改后再进行转移,以达到所需要的代码调整的目的。

当然对应晦涩难懂的Class 字节码,我们不会直接对字节流进行读取后操作,而是借助于字节码工具,在进行加工处理。

比较出名的字节码操作库有javassist、asm、aspectJ等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值