欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Transform 的基本认识

程序员文章站 2022-04-12 10:35:13
...

前言

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

方法名 功能 备注
getName Transform Task 命名,该返回名不是作为最后的命名,任务会自动补全该名字  
getInputType() Transform 需要输入的内容类型  
getScopes Transform 搜索范围类型  
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等