自定义Transform

news/2024/5/20 5:26:09 标签: gradle, android, Transform, plugin

Transform_0">自定义Transform

本文章研究所使用的示例代码:AndroidPluginDemo

基础概念

术语说明
TransformInput所谓Transform就是对输入的class文件转变成目标字节码文件,TransformInput就是这些输入文件的抽象。目前它包括两部分:DirectoryInput集合与JarInput集合。
DirectoryInput它代表着以源码方式参与项目编译的所有目录结构及其目录下的源码文件,可以借助于它来修改输出文件的目录结构、目标字节码文件
JarInput它代表着以jar包方式参与项目编译的所有本地jar包或远程jar包,可以借助于它来动态添加jar包
TransformOutputProvider它代表的是Transform的输出,例如可以通过它来获取输出路径。

Transform_API_12">Transform API

使用Transform API主要是写一个类继承Transform,并把该Transform注入到打包过程中。注入Transform很简单,先获取com.android.build.gradle.BaseExtension对象,然后调用它的registerTransform()方法。

public class CustomPlugin implements Plugin<Project> {
    @Override
    public void apply(@NotNull Project project) {
        project.getExtensions().findByType(BaseExtension.class)
                .registerTransform(new CustomTransform(project));
    }
}

TransformAPI_25">Transform常用API:

方法说明
String getName()用于指明本Transform的名字,也是代表该Transform的task的名字。
Set<QualifiedContent.ContentType> getInputTypes()用于指明Transform的输入类型,可以作为输入过滤的手段
Set<? super QualifiedContent.Scope> getScopes()用于指明Transform的作用域
boolean isIncremental()用于指明是否是增量构建
void transform(TransformInvocation invocation)执行Transform方法,Transform处理逻辑的地方。
boolean applyToVariant(@NonNull VariantInfo variant)是否应将此Transform应用于给定的variant,可以区分渠道使用Transform

ContentType

ContentType是一个接口,默认有一个枚举类型DefaultContentType实现了ContentType,包含有CLASSESRESOURCES类型。

类型说明
CLASSES表示的是在jar包或者文件夹中的.class文件。
RESOURCES表示的是标准的Java资源文件。

Android Plugin扩展的ContentType -> ExtendedContentType:

类型说明
DEXThe content is dex files.
NATIVE_LIBSContent is a native library.
CLASSES_ENHANCEDInstant Run ‘$override’ classes, which contain code of new method bodies.此流还包含用于应用HotSwap更改的AbstractPatchesLoaderImpl类。
DATA_BINDINGThe content is an artifact exported by the data binding compiler.
JAVA_SOURCESThe content is Java source file. @Deprecated don’t use!
DEX_ARCHIVEThe content is a dex archive. It contains a single DEX file per class.

Scope 作用范围

Scope类型说明
PROJECT只处理当前的项目(模块)
SUB_PROJECTS只处理子项目(模块)
EXTERNAL_LIBRARIES只处理外部的依赖库
TESTED_CODE只处理测试代码
PROVIDED_ONLY只处理provided-only的依赖库
PROJECT_LOCAL_DEPS只处理当前项目的本地依赖,例如jar, aar(过期,被EXTERNAL_LIBRARIES替代)
SUB_PROJECTS_LOCAL_DEPS只处理子项目的本地依赖,例如jar, aar(过期,被EXTERNAL_LIBRARIES替代)

Transform中的getInputTypes()方法和getScopes()方法返回的是Set集合,因此这些类型是可以进行组合的。在TransformManager中就包含了多种Set集合。

package com.android.build.gradle.internal.pipeline;

/**
 * Manages the transforms for a variant.
 *
 * <p>The actual execution is handled by Gradle through the tasks.
 * Instead it's a means to more easily configure a series of transforms that consume each other's
 * inputs when several of these transform are optional.
 */
public class TransformManager extends FilterableStreamCollection {

    private static final boolean DEBUG = true;

    private static final String FD_TRANSFORMS = "transforms";

    public static final Set<ScopeType> EMPTY_SCOPES = ImmutableSet.of();

    public static final Set<ContentType> CONTENT_CLASS = ImmutableSet.of(CLASSES);
    public static final Set<ContentType> CONTENT_JARS = ImmutableSet.of(CLASSES, RESOURCES);
    public static final Set<ContentType> CONTENT_RESOURCES = ImmutableSet.of(RESOURCES);
    public static final Set<ContentType> CONTENT_NATIVE_LIBS =
            ImmutableSet.of(NATIVE_LIBS);
    public static final Set<ContentType> CONTENT_DEX = ImmutableSet.of(ExtendedContentType.DEX);
    public static final Set<ContentType> CONTENT_DEX_WITH_RESOURCES =
            ImmutableSet.of(ExtendedContentType.DEX, RESOURCES);
    public static final Set<ScopeType> PROJECT_ONLY = ImmutableSet.of(Scope.PROJECT);
    public static final Set<ScopeType> SCOPE_FULL_PROJECT =
            ImmutableSet.of(Scope.PROJECT, Scope.SUB_PROJECTS, Scope.EXTERNAL_LIBRARIES);
    public static final Set<ScopeType> SCOPE_FULL_WITH_FEATURES =
            new ImmutableSet.Builder<ScopeType>()
                    .addAll(SCOPE_FULL_PROJECT)
                    .add(InternalScope.FEATURES)
                    .build();
    public static final Set<ScopeType> SCOPE_FEATURES = ImmutableSet.of(InternalScope.FEATURES);
    public static final Set<ScopeType> SCOPE_FULL_LIBRARY_WITH_LOCAL_JARS =
            ImmutableSet.of(Scope.PROJECT, InternalScope.LOCAL_DEPS);
    public static final Set<ScopeType> SCOPE_FULL_PROJECT_WITH_LOCAL_JARS =
            new ImmutableSet.Builder<ScopeType>()
                    .addAll(SCOPE_FULL_PROJECT)
                    .add(InternalScope.LOCAL_DEPS)
                    .build();

isIncremental

TransformisIncremental()方法表示是否支持增量编译,返回true的话表示支持,这个时候可以根据TransformInput来获得更改、移除或者添加的文件目录或者jar包。

package com.android.build.api.transform;

import com.android.annotations.NonNull;
import java.util.Collection;

/**
 * The input to a Transform.
 * <p>
 * It is mostly composed of a list of {@link JarInput} and a list of {@link DirectoryInput}.
 */
public interface TransformInput {

    /**
     * Returns a collection of {@link JarInput}.
     */
    @NonNull
    Collection<JarInput> getJarInputs();

    /**
     * Returns a collection of {@link DirectoryInput}.
     */
    @NonNull
    Collection<DirectoryInput> getDirectoryInputs();
}

JarInput

JarInput有一个方法是getStatus()来获取Status

package com.android.build.api.transform;

import com.android.annotations.NonNull;
import java.util.Collection;

/**
 * A {@link QualifiedContent} of type jar.
 * <p>
 * This means the {@link #getFile()} is the jar file containing the content.
 * <p>
 * This also contains the incremental state of the jar file, if the transform is in incremental
 * mode through {@link #getStatus()}.
 * <p>
 * For a transform to run in incremental mode:
 * <ul>
 *     <li>{@link Transform#isIncremental()} must return <code>true</code></li>
 *     <li>The parameter <var>isIncremental</var> of
 *     {@link Transform#transform(Context, Collection, Collection, TransformOutputProvider, boolean)}
 *     must be <code>true</code>.</li>
 * </ul>
 *
 * If the transform is not in incremental mode, {@link #getStatus()} always returns
 * {@link Status#NOTCHANGED}.
 */
public interface JarInput extends QualifiedContent {

    @NonNull
    Status getStatus();
}

Status是一个枚举类,包含了NOTCHANGEDADDEDCHANGEDREMOVED,所以可以根据JarInputstatus来对它进行相应的处理,比如添加或者移除。

package com.android.build.api.transform;

/**
 * The file changed status for incremental execution.
 */
public enum Status {
    /**
     * The file was not changed since the last build.
     */
    NOTCHANGED,
    /**
     * The file was added since the last build.
     */
    ADDED,
    /**
     * The file was modified since the last build.
     */
    CHANGED,
    /**
     * The file was removed since the last build.
     */
    REMOVED;
}

DirectoryInput

DirectoryInput有一个方法getChangedFiles()开获取一个Map<File, Status>集合,所以可以遍历这个Map集合,然后根据File对应的Status来对File进行处理。

如果不支持增量编译,就在处理.class之前把之前的输出目录中的文件删除。

获取TransformInput对象是根据TransformInvocation:

package com.android.build.api.transform;

import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import java.util.Collection;

/**
 * An invocation object used to pass of pertinent information for a
 * {@link Transform#transform(TransformInvocation)} call.
 */
public interface TransformInvocation {

    /**
     * Returns the context in which the transform is run.
     * @return the context in which the transform is run.
     */
    @NonNull
    Context getContext();

    /**
     * Returns the inputs/outputs of the transform.
     * @return the inputs/outputs of the transform.
     */
    @NonNull
    Collection<TransformInput> getInputs();

    /**
     * Returns the referenced-only inputs which are not consumed by this transformation.
     * @return the referenced-only inputs.
     */
    @NonNull Collection<TransformInput> getReferencedInputs();
    /**
     * Returns the list of secondary file changes since last. Only secondary files that this
     * transform can handle incrementally will be part of this change set.
     * @return the list of changes impacting a {@link SecondaryInput}
     */
    @NonNull Collection<SecondaryInput> getSecondaryInputs();

    /**
     * Returns the output provider allowing to create content.
     * @return he output provider allowing to create content.
     */
    @Nullable
    TransformOutputProvider getOutputProvider();


    /**
     * Indicates whether the transform execution is incremental.
     * @return true for an incremental invocation, false otherwise.
     */
    boolean isIncremental();
}

TransformInvocation_266">TransformInvocation

TransformInvocation包含了输入、输出相关信息。其输出相关内容是由TransformOutputProvider来做处理。TransformOutputProvidergetContentLocation()方法可以获取文件的输出目录,如果目录存在的话直接返回,如果不存在就会重新创建一个。例如:

// getContentLocation方法相当于创建一个对应名称表示的目录
// 是从0 、1、2开始递增。如果是目录,名称就是对应的数字,如果是jar包就类似0.jar
File outputDir = transformInvocation.outputProvider.getContentLocation("include", 
         dirInput.contentTypes, dirInput.scopes, Format.DIRECTORY)

File outputJar = transformInvocation.outputProvider.getContentLocation(jarInput.name
        , jarInput.contentTypes
        , jarInput.scopes
        , Format.JAR)

在执行编译过程中会生成对应的目录,例如在app/build/intermediates/transforms目录下生成了一个名为CustomPlugin的目录,这个名称就是根据自定义的TransformgetName()方法返回的字符串来的。

transforms
    > CustomPlugin
        > debug
            > 0.jar
            > 1.jar
            ...
            > 39
            > __content__.json

CustomPlugin目录下还会有一个名为__content__的.json文件。该文件中展示了CustomPlugin中文件目录下的内容。

[
  {
    "name": "androidx.localbroadcastmanager:localbroadcastmanager:1.0.0",
    "index": 37,
    "scopes": [
      "EXTERNAL_LIBRARIES"
    ],
    "types": [
      "CLASSES"
    ],
    "format": "JAR",
    "present": true
  },
  {
    "name": "cae395e225fd7e1a29b7e372dfac40c8d0d8f1ee",
    "index": 38,
    "scopes": [
      "PROJECT"
    ],
    "types": [
      "CLASSES"
    ],
    "format": "JAR",
    "present": true
  },
  {
    "name": "66d46f518ab0f2d4aa1a29cadd54ee980bbb1cb6",
    "index": 40,
    "scopes": [
      "PROJECT"
    ],
    "types": [
      "CLASSES"
    ],
    "format": "DIRECTORY",
    "present": true
  },
  {
    "name": "CustomPlugin",
    "index": 42,
    "scopes": [
      "PROJECT"
    ],
    "types": [
      "CLASSES"
    ],
    "format": "DIRECTORY",
    "present": false
  }
]

http://www.niftyadmin.cn/n/1268699.html

相关文章

Leetcode 29:两数相除(超详细的解法!!!)

给定两个整数&#xff0c;被除数 dividend 和除数 divisor。将两数相除&#xff0c;要求不使用乘法、除法和 mod 运算符。 返回被除数 dividend 除以除数 divisor 得到的商。 示例 1: 输入: dividend 10, divisor 3 输出: 3示例 2: 输入: dividend 7, divisor -3 输出:…

ASM 6 Developer Guide

主要数据结构 对象模型 核心程序包由28个类和接口组成。如果我们排除Opcodes接口&#xff0c;5个抽象访问者类&#xff08;AnnotationVisitor&#xff0c; ClassVisitor&#xff0c;FieldVisitor&#xff0c; MethodVisitor 和 ModuleVisitor&#xff09;和6实用工具类&#…

Leetcode 38:报数(超详细的解法!!!)

报数序列是一个整数序列&#xff0c;按照其中的整数的顺序进行报数&#xff0c;得到下一个数。其前五项如下&#xff1a; 1. 1 2. 11 3. 21 4. 1211 5. 1112211 被读作 "one 1" ("一个一") , 即 11。 11 被读作 "two 1s" (…

‘buildSrc‘ cannot be used as a project name as it is a reserved name.

‘buildSrc’ cannot be used as a project name as it is a reserved name. 可以尝试使用以下方法解决&#xff1a; 打开你的 settings.gradle / settings.gradle.kts 文件 将 “buildSrc” 从 included modules 移除 重新编译

Leetcode 36:有效的数独(超详细的解法!!!)

判断一个 9x9 的数独是否有效。只需要根据以下规则&#xff0c;验证已经填入的数字是否有效即可。 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。 上图是一个部分填充的有效的数独。 数独部分空格…

groovy 替换文件指定内容

groovy 替换文件指定内容 替换指定文件的部分内容脚本 static def replaceFileText(file, oldText, newText) {String text file.text.replaceAll(oldText, newText)file.withPrintWriter { printWriter ->printWriter.print(text)} }使用范例 gradle.startParameter.ge…

Leetcode 30:与所有单词相关联的字串(超详细的解法!!!)

给定一个字符串 s 和一些长度相同的单词 **words。**在 s 中找出可以恰好串联 words 中所有单词的子串的起始位置。 注意子串要与 words 中的单词完全匹配&#xff0c;中间不能有其他字符&#xff0c;但不需要考虑 words 中单词串联的顺序。 示例 1: 输入:s "barfooth…

google bundleTool 源码里 com.android.bundle.* 相关的代码爆红

google bundleTool 源码里 com.android.bundle.* 相关的代码爆红 解决在builde.gradle里添加依赖: // The repackaging rules are defined in the "shadowJar" task below. dependencies {compileOnly files(project.buildDir.path "/classes/java/main"…