自定义Transform
本文章研究所使用的示例代码:AndroidPluginDemo
基础概念
术语 | 说明 |
---|
TransformInput | 所谓Transform就是对输入的class文件转变成目标字节码文件,TransformInput 就是这些输入文件的抽象。目前它包括两部分:DirectoryInput 集合与JarInput 集合。 | DirectoryInput | 它代表着以源码方式参与项目编译的所有目录结构及其目录下的源码文件,可以借助于它来修改输出文件的目录结构、目标字节码文件。 | JarInput | 它代表着以jar包方式参与项目编译的所有本地jar包或远程jar包,可以借助于它来动态添加jar包。 | TransformOutputProvider | 它代表的是Transform的输出,例如可以通过它来获取输出路径。 |
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));
}
}
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,包含有CLASSES 和RESOURCES 类型。
类型 | 说明 |
---|
CLASSES | 表示的是在jar包或者文件夹中的.class文件。 | RESOURCES | 表示的是标准的Java资源文件。 |
Android Plugin扩展的ContentType -> ExtendedContentType :
类型 | 说明 |
---|
DEX | The content is dex files. | NATIVE_LIBS | Content is a native library. | CLASSES_ENHANCED | Instant Run ‘$override’ classes, which contain code of new method bodies.此流还包含用于应用HotSwap更改的AbstractPatchesLoaderImpl类。 | DATA_BINDING | The content is an artifact exported by the data binding compiler. | JAVA_SOURCES | The content is Java source file. @Deprecated don’t use! | DEX_ARCHIVE | The 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;
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
Transform的isIncremental() 方法表示是否支持增量编译,返回true的话表示支持,这个时候可以根据TransformInput 来获得更改、移除或者添加的文件目录或者jar包。
package com.android.build.api.transform;
import com.android.annotations.NonNull;
import java.util.Collection;
public interface TransformInput {
@NonNull
Collection<JarInput> getJarInputs();
@NonNull
Collection<DirectoryInput> getDirectoryInputs();
}
JarInput
JarInput 有一个方法是getStatus() 来获取Status
package com.android.build.api.transform;
import com.android.annotations.NonNull;
import java.util.Collection;
public interface JarInput extends QualifiedContent {
@NonNull
Status getStatus();
}
Status是一个枚举类,包含了NOTCHANGED 、ADDED 、CHANGED 、REMOVED ,所以可以根据JarInput 的status 来对它进行相应的处理,比如添加或者移除。
package com.android.build.api.transform;
public enum Status {
NOTCHANGED,
ADDED,
CHANGED,
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;
public interface TransformInvocation {
@NonNull
Context getContext();
@NonNull
Collection<TransformInput> getInputs();
@NonNull Collection<TransformInput> getReferencedInputs();
@NonNull Collection<SecondaryInput> getSecondaryInputs();
@Nullable
TransformOutputProvider getOutputProvider();
boolean isIncremental();
}
TransformInvocation
TransformInvocation 包含了输入、输出相关信息。其输出相关内容是由TransformOutputProvider 来做处理。TransformOutputProvider 的getContentLocation() 方法可以获取文件的输出目录,如果目录存在的话直接返回,如果不存在就会重新创建一个。例如:
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 的目录,这个名称就是根据自定义的Transform类getName() 方法返回的字符串来的。
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
}
]
|