背景

现在已经要步入AGP8.0时代了,Transform过时并被删除已经是板上钉钉的事儿了,网上传言TransformAction是Transform的代替品。事实究竟怎么呢,咱们要了解TransformAction不是什么,首要咱们要了解它是什么。

让我想起了刚开始学Service的时分,老外写文档会说Service是一个专门在后台处理长期使命的Android组件。

  1. Service不是一个独自的进程;
  2. Service也不是一个独自的线程;
    并且也会说Service不是什么,这种写注释的思路仍是值得咱们学习的。

好了回到正题,咱们先了解一下TransformAction是什么

ps:

  • 本文主要讲了TransformAction中产品转化部分,至于装备依靠部分有爱好的能够点击官方链接Transforming artifacts

什么是TransformAction

最直接的办法,看源码和官方文档TransformAction

//Interface for artifact transform actions.
public interface TransformAction<T extends TransformParameters> {  
    @Inject  
    T getParameters();  
    void transform(TransformOutputs outputs);  
}

API非常简洁,只要一个transform办法,getParameters办法还不需要完成,官方对它的介绍也比较简单依靠产品转化的接口

其实一个TransformAction主要便是输入,输出,改换逻辑三个部分

完成一个TransformAction

UnzipTransform的效果呢便是解压缩,很简单了解

abstract class UnzipTransform : TransformAction<TransformParameters.None> {
    @get:InputArtifact                                                      
    abstract val inputArtifact: Provider<FileSystemLocation>
    override
    fun transform(outputs: TransformOutputs) {
        val input = inputArtifact.get().asFile
        val unzipDir = outputs.dir(input.name)                              
        unzipTo(input, unzipDir)                                            
    }
    private fun unzipTo(zipFile: File, unzipDir: File) {
        // implementation...
    }
}

注册TransformAction

注册是在dependencies闭包下进行注册

val artifactType = Attribute.of("artifactType", String::class.java)
dependencies {
    registerTransform(UnzipTransform::class) {
        from.attribute(artifactType, "jar")
        to.attribute(artifactType, "java-classes-directory")
    }
}

注册它的效果是将jar类型的产品转化为java-classes-directory类型的产品 光看这个仍是有点懵,究竟怎么用呢,从咱们熟悉的场景入手,最简单了解

AGP中TransformAction的应用

Gradle官方也举了一些比如,可是不太好了解,了解之后想不到实践运用场景,或许我比较菜

Jetifier依靠转化

[TransformAction] to convert a third-party library that uses old support libraries into an equivalent library that uses AndroidX.

将Support依靠替换为AndroidX依靠,不管项目是否包括support依靠,只需咱们在gradle.properties中开启了android.enableJetifier=true都会进行转化操作

@CacheableTransform
abstract class JetifyTransform : TransformAction<JetifyTransform.Parameters> {
    @get:PathSensitive(PathSensitivity.NAME_ONLY)
    @get:InputArtifact
    abstract val inputArtifact: Provider<FileSystemLocation>
    override fun transform(transformOutputs: TransformOutputs) {
        val inputFile = inputArtifact.get().asFile
        // Case 1: If this is an AndroidX library, no need to jetify it
        if (jetifierProcessor.isNewDependencyFile(inputFile)) {
            transformOutputs.file(inputFile)
            return
        }
        // Case 2: If this is an old support library, it means that it was not replaced during
        // dependency substitution earlier, either because it does not yet have an AndroidX version,
        // or because its AndroidX version is not yet available on remote repositories. Again, no
        // need to jetify it.
        if (jetifierProcessor.isOldDependencyFile(inputFile)) {
            transformOutputs.file(inputFile)
            return
        }
        val jetifierIgnoreList: List<Regex> = getJetifierIgnoreList(parameters.ignoreListOption.get())
        // Case 3: If the library is ignored, do not jetify it
        if (jetifierIgnoreList.any { it.containsMatchIn(inputFile.absolutePath) }) {
            transformOutputs.file(inputFile)
            return
        }
        // Case 4: For the remaining libraries, let's jetify them
        val outputFile = transformOutputs.file("jetified-${inputFile.name}")
        val result = try {
            jetifierProcessor.transform2(
                input = setOf(FileMapping(inputFile, outputFile)),
                copyUnmodifiedLibsAlso = true,
                skipLibsWithAndroidXReferences = true
            )
        } catch (exception: Exception) {
            ....
            throw RuntimeException(message, exception)
        }
    }
}

完成原理

jetifier将要处理的依靠分为4类

1.androidx的依靠库
2.废弃的support依靠库
3.被装备为疏忽的依靠库
4.不符合上述条件的其他依靠库

transform完毕后会将处理过的资源从头压缩,比如.class文件、.java文件、xml文件、proguard.txt等等,并且会带上jetified的前缀, 然后供下流的TransformAction消费。

注册机遇

if (projectOptions.get(BooleanOption.ENABLE_JETIFIER)) {
    registerTransform(  
        JetifyTransform::class.java,  
        AndroidArtifacts.ArtifactType.AAR,  
        jetifiedAarOutputType  
    ) { params ->  
        params.ignoreListOption.setDisallowChanges(jetifierIgnoreList)  
    }  
    registerTransform(  
        JetifyTransform::class.java,  
        AndroidArtifacts.ArtifactType.JAR,  
        AndroidArtifacts.ArtifactType.PROCESSED_JAR  
    ) { params ->  
        params.ignoreListOption.setDisallowChanges(jetifierIgnoreList)  
    }  
}

是在DependencyConfigurator中进行注册,这儿咱们只需记住一个点,是在依靠装备中进行注册

Android工程aar转化成jar

Transform that returns the content of an extracted AAR folder

咱们知道Java工程是无法运用AAR的,可是Android工程能够,AGP是怎么完成的呢

@DisableCachingByDefault
public abstract class AarTransform implements TransformAction<AarTransform.Parameters> {
    @PathSensitive(PathSensitivity.ABSOLUTE)
    @InputArtifact
    public abstract Provider<FileSystemLocation> getInputArtifact();
    @NonNull
    public static ArtifactType[] getTransformTargets() {
        return new ArtifactType[] {
            // For CLASSES, this transform is ues for runtime, and AarCompileClassesTransform is
            // used for compile
            ArtifactType.SHARED_CLASSES,
            ArtifactType.JAVA_RES,
            ArtifactType.SHARED_JAVA_RES,
            ArtifactType.PROCESSED_JAR,
            ArtifactType.MANIFEST,
            ArtifactType.ANDROID_RES,
            ArtifactType.ASSETS,
            ArtifactType.SHARED_ASSETS,
            ArtifactType.JNI,
            ArtifactType.SHARED_JNI,
            ArtifactType.AIDL,
            ArtifactType.RENDERSCRIPT,
            ArtifactType.UNFILTERED_PROGUARD_RULES,
            ArtifactType.LINT,
            ArtifactType.ANNOTATIONS,
            ArtifactType.PUBLIC_RES,
            ArtifactType.COMPILE_SYMBOL_LIST,
            ArtifactType.DATA_BINDING_ARTIFACT,
            ArtifactType.DATA_BINDING_BASE_CLASS_LOG_ARTIFACT,
            ArtifactType.RES_STATIC_LIBRARY,
            ArtifactType.RES_SHARED_STATIC_LIBRARY,
            ArtifactType.PREFAB_PACKAGE,
            ArtifactType.AAR_METADATA,
            ArtifactType.ART_PROFILE,
            ArtifactType.NAVIGATION_JSON,
        };
    }
    @Override
    public void transform(@NonNull TransformOutputs transformOutputs) {
        File input = getInputArtifact().get().getAsFile();
        ArtifactType targetType = getParameters().getTargetType().get();
        switch (targetType) {
            case CLASSES_JAR:
            case JAVA_RES:
            case PROCESSED_JAR:
                // even though resources are supposed to only be in the main jar of the AAR, this
                // is not necessarily enforced by all build systems generating AAR so it's safer to
                // read all jars from the manifest.
                // For shared libraries, these are provided via SHARED_CLASSES and SHARED_JAVA_RES.
                if (!isShared(input)) {
                    AarTransformUtil.getJars(input).forEach(transformOutputs::file);
                }
                break;
            case SHARED_CLASSES:
            case SHARED_JAVA_RES:
                if (isShared(input)) {
                    AarTransformUtil.getJars(input).forEach(transformOutputs::file);
                }
                break;
            case LINT:
                outputIfExists(FileUtils.join(input, FD_JARS, FN_LINT_JAR), transformOutputs);
                break;
            case MANIFEST:
                // Return both the manifest and the extra snippet for the shared library.
                outputIfExists(new File(input, FN_ANDROID_MANIFEST_XML), transformOutputs);
                if (isShared(input)) {
                    outputIfExists(
                            new File(input, FN_SHARED_LIBRARY_ANDROID_MANIFEST_XML),
                            transformOutputs);
                }
                break;
            //代码太长省略多个case
            case PREFAB_PACKAGE:
                outputIfExists(new File(input, FD_PREFAB_PACKAGE), transformOutputs);
                break;
            case AAR_METADATA:
                outputIfExists(
                        FileUtils.join(input, AarMetadataTask.AAR_METADATA_ENTRY_PATH.split("/")),
                        transformOutputs);
                break;
            case ART_PROFILE:
                outputIfExists(
                        FileUtils.join(
                                input,
                                SdkConstants.FN_ART_PROFILE),
                        transformOutputs);
                break;
            case NAVIGATION_JSON:
                outputIfExists(new File(input, FN_NAVIGATION_JSON), transformOutputs);
                break;
            default:
                throw new RuntimeException("Unsupported type in AarTransform: " + targetType);
        }
    }
}

getTransformTargets()声明晰一切AAR包中或许呈现的资源类型,在transform()中根据类型将aar包中的文件解压到输出目录,这便是AarTransform的效果

注册机遇

for (transformTarget in AarTransform.getTransformTargets()) {
            registerTransform(
                AarTransform::class.java,
                AndroidArtifacts.ArtifactType.EXPLODED_AAR,
                transformTarget
            ) { params ->
                params.targetType.setDisallowChanges(transformTarget)
                params.sharedLibSupport.setDisallowChanges(sharedLibSupport)
            }
        }

AarTransform相同是在DependencyConfigurator中进行注册,留意它是用一个for循环进行注册,也便是为getTransformTargets()这个数组每一种产品类型都注册一个AarTransform

当某个Task的输入装备依靠中的artifactType包括这个数组中其中一项时,都有或许履行到AarTransform

Gradle中的Configuration是什么

咱们在build.gradle中写的compile,implementation,provided,compileOnly等等,都是Configuration。

承担项目或许aar依靠仅仅configuration表面的使命,关于项目依靠来说,它真实的效果,其实是当project A依靠project B时,它就拥有了关于project B中产品的一切权,即它能够获取到project B中的产品

什么是artifacts

artifacts的意思便是产品,有的地方也翻译成构件或工件,说的是同一个东西,这儿我就称号为产品,关于大部分task来说,履行完之后都会有产品输出,输出的就叫artifacts,相同artifacts也能够作为Task的输入。

与artifacts密切关联的有2个类:

  • ArtifactCollection

  • FileCollection

其实基本上一切的AndroidTask的输出都是文件。

/**
 * 装备的一组已解析的产品调集。当查询调集时,将按需解析装备
 */
public interface ArtifactCollection extends Iterable<ResolvedArtifactResult> {
    /**
     * 用于回来包括有一切产品的文件调集,这个办法的回来值可作为一个task的input,
     利用这个input构建起task之间的依靠联系
     */
    FileCollection getArtifactFiles();
    /**
     * 用于回来解析好的artifacts,假如没有解析,则会进行解析。在这个过程中,
     假如需要的话,或许会下载artifact的metadata和artifact文件
     */
    Set<ResolvedArtifactResult> getArtifacts();
    /**
     * 以ResolvedArtifactResult实例的Provider方法回来已解析的产品。
      回来的Provider是动态的,会盯梢此产品调集的生产者Tasks。
      这个Provider将根据需要解析产品的metadata并下载产品文件。
     */
    @Incubating
    Provider<Set<ResolvedArtifactResult>> getResolvedArtifacts();
    Collection<Throwable> getFailures();
}

能够看到几个办法都是跟获取artifacts相关的,咱们先不要陷入这些api怎么运用,了解它的详细功用即可

FileCollection的定义如下

public interface FileCollection extends Iterable<File>, AntBuilderAware, Buildable

这儿只需关注一点,一个Buildable对象代表一个或多个artifact,这些artifacts也是由一个或多个task发生的,而FileCollection继承了Buildable接口

Task和artifacts、ArtifactCollection是密切相关的,那么讲这些有什么效果呢,它关于了解TransformAction的产品转化有很重要的效果

Task中的ArtifactView

A view over the artifacts resolved for this set of dependencies. By default, the view returns all files and artifacts, but this can be restricted by component identifier or by attributes.

这是一组解分出的依靠项的产品视图。默认情况下,该视图回来一切文件和产品,但能够经过组件标识符或特点来进行约束
之所以又介绍ArtifactView这个类,是因为Task中artifactType是由ArtifactView来操控的

public interface ArtifactView extends HasAttributes {
    /**
     * Returns the collection of artifacts matching the requested attributes that are sourced from Components matching the specified filter.
     */
    ArtifactCollection getArtifacts();
    /**
     * Returns the collection of artifact files matching the requested attributes that are sourced from Components matching the specified filter.
     */
    FileCollection getFiles();
 }

留意ArtifactView接口中2个办法的回来值,便是咱们前面说过的ArtifactCollection和FileCollection

Task怎么增加输入artifactType

然后调用咱们的TransformAction呢,学习了网上大佬的代码

tasks.register("consumerTask", ConsumerTask::class.java) {
            it.artifactCollection = myConfiguration.incoming
            .artifactView {viewConfiguration ->
            //将咱们自定义的或许需要依靠的artifactType加入到Task的输入产品调集
                viewConfiguration.attributes.attribute(artifactType, "java-classes-directory")
            }.artifacts
            it.outputFile.set(File("build/consumerTask/output/output.txt"))
            }

咱们把artifactType=”java-classes-directory”加入到ConsumerTask装备的输入依靠中,那么当咱们履行ConsumerTask的时分

恳求java-classes-directory类型的产品时,假如找不到,就会查找一系列能够将其他类型的产品转为java-classes-directory类型的TransformAction(例如文章最初的UnzipTransform),并依照一定的规矩进行产品转化(为了偷闲和便于了解,我对官方的demo做了些修改,勿喷)

AGP中的ArtifactView运用

这是VariantDependencies.kt中的一段代码,能够看到运用办法基本相同,只不过AGP中包装了一个attributesAction

configuration
            .incoming
            .artifactView { config: ArtifactView.ViewConfiguration ->
                config.attributes(attributesAction)
                filter?.let { config.componentFilter(it) }
                // TODO somehow read the unresolved dependencies?
                config.lenient(lenientMode)
            }
            .artifacts

TransformAction调用机遇

当Gradle解析装备并且装备中的依靠联系不具有带有所恳求特点的变体时,Gradle会测验查找一系列TransformAction进行转化,下面是它的转化规矩

  1. 假如只要一个转化链,则挑选它。
  2. 假如有两个改换链,并且一个是另一个的后缀,则将其选中。
  3. 假如存在最短的改换链,则将其选中。
  4. 在一切其他情况下,挑选将失利并陈述错误。

一起还有两个特殊情况:

  • 当已经存在与恳求特点匹配的依靠项变体时,Gradle不会测验挑选产品转化。
  • artifactType特点是特殊的,因为它仅存在于解析的产品上,而不存在于依靠项上。因而任何只改换artifactTypeTransformAction,只要在运用ArtifactView时才会考虑运用

上面是Gradle官方文档翻译过来的,不太好消化。

说一下我的了解 它的履行机遇是在Gradle履行依靠项解析和转化的过程中,分为2类

  1. 装备依靠
  2. 产品转化

假如有装备缓存,则不会进行转化;假如有多个TransformAction,会依照最优的办法挑选TransformAction进行转化。

TransformAction在运用依靠前才会履行,并且第一次履行后会在.gradle/caches下生成缓存,实践上是在Task履行之前履行

上面举得2个比如,JetifyTransform和AarTransform都归于产品转化,并且AGP中运用最多的也是产品转化,也便是上面特殊情况的第2条。

至于装备依靠,尽管Gradle官方举了一个比如,可是我想不到这个比如究竟有什么用处,了解这个比如也费了一段时间,并且也没有碰到运用的场景,这儿就不介绍了。

有爱好的能够点击官方链接Transforming artifacts

对比

回到主题,AGP(Android Gradle Plugin)中的Transform是一个用于在Android构建过程中进行字节码操作的API

  1. 供给者是AGP
  2. 效果是操作字节码
  3. 以task的方法履行

TransformAction是Gradle供给的依靠产品转化的API

  1. 供给者是Gradle
  2. 效果是依靠产品转化
  3. 在Task履行之前履行

Artifact transform 做的主要是依靠产品的转化,不同于AGP对中间产品部分的Transform, Gradle全局的产品转化能够保证同步后能够运用到转化过的产品, 能够在一次 transform过程中一起处理class, layout, manifest中的support依靠,例如上面的JetifyTransform,这在AGP的Transform中是无法完成的

TransformAction的注册API

是在project的dependencies模块进行注册,这也从侧面反响了TransformAction是为工程的依靠而服务的

project.dependencies.registerTransform(
            transformClass
        ) { spec: TransformSpec<T> ->
            spec.from.attribute(ArtifactAttributes.ARTIFACT_FORMAT, fromArtifactType)
            spec.to.attribute(ArtifactAttributes.ARTIFACT_FORMAT, toArtifactType)
            spec.parameters.projectName.setDisallowChanges(project.name)
            parametersSetter?.let { it(spec.parameters) }
        }

Google官方说明

Google已经明确说明Transform API 没有单一的代替 API,每个case都会有新的针对性 API。一切代替 API 都坐落androidComponents {}代码块中

为什么说TransformAction不是Transform的替代品

总结

TransformAction和Transform两者的履行机遇以及效果,是大不相同的,并且官方也明确表明晰Transform代替api的代码块,所以transformAction并不是Transform的代替品。

当然TransformAction还有一些细节内容没有介绍,比如支撑增量编译、输出写入规矩等等

TransformAction目前主要应用在AGP中,实践开发中我还没有找到适合运用的场景

以上观念如有讹夺,欢迎批评指正,另外关于TransformAction的实践开发中的运用场景也欢迎留言交流

参阅文章

本文学习了以下大佬的文章,非常感谢

哔哩哔哩 Android 同步优化•Jetifier
傻傻分不清楚:Gradle TransformAction和AGP Transform Transform 被废弃,TransformAction 了解一下~
连载 | 深化了解gradle框架之三:artifacts的发布