dim.red
环境: gradle 4.1 , Android Plugin 2.3.0 , Android Plugin 3.0.0

#0x00 背景
去年的时候写个 Android Plugin Transform 初探 . 现在我们接着之前的脚步来学习 Gradle 下的 Transform . 同时熟悉一下 Android Plugin 在该规则下的应用.

#0x01 历史
Android Plugin 是在1.5.0-beta1 版本加入的. 1.5.0 的 Release 是在2015 年的 11 月.
Gradle 的 Transform api 是在 3.5 版本引入的. 3.5.0 的 Release 是在 2017 年的 4 月.
相似的命名, 相似的功能,
[^_^]: Android plugin 1.5.0
Gradle 3.5

#0x02 使用
Gradle 中 Configuration 代表一组依赖关系.而 Transform 是作用在依赖的产物上, 将产物 A 根据一定规则转换成产物 B.
依赖的产物默认有一个属性ArtifactAttributes.ARTIFACT_FORMAT, 远程依赖默认为文件的后缀名. 本地依赖的情况比较复杂,具体看应用的插件.

先看一个比较全的应用

Lib 工程提供 Configuration. 供主工程消费.

1 Lib 工程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Configuration apLib = project.configurations.create("apLib");
apLib.setDescription("ap elements " );
// 能被其他 project 的消费
apLib.setCanBeConsumed(true);
// 设置当前的 Configuration 的属性. 供请求 Configuration 的属性匹配
apLib.getAttributes().attribute(Attribute.of("Type", String.class), "ap")
// 声明一个variants apVar , 设置 apVar 属性为 ap, 同时为apVar 设置最终产物 /lib/demo.ap
apLib.getOutgoing().variants(new Action<NamedDomainObjectContainer<ConfigurationVariant>>() {
@Override
void execute(NamedDomainObjectContainer<ConfigurationVariant> configurationVariants) {

configurationVariants.create("apVar", { variant ->
variant.artifact(new File("/lib/demo.ap"), { artifact ->
artifact.setType("ap");
})
variant.getAttributes().attribute(Attribute.of("artifactType", String.class), "ap")
})
}
});

这里声明了一个 apLib 的 Configuration . 并且为这个apLib 设置了一些属性,这些属性将在后面查找提供作用, 同时为 apLib 设置它的产物 . 也可以为这个 artifact 设置依赖任务 (artifact.builtBy(Task)) , 获取这个产物会先执行依赖任务.

2 主工程

定义一个 apC . 获取 apC 依赖上所有 ap 类型的文件.

1
2
3
4
5
6
7
8
9
10
11
12
13
Configuration apC = project.configurations.create("apC");
//主动依赖 Lib 工程
project.getDependencies().add("apC", project.project(':lib'));

//定义 apC 属性, 主要用来匹配 lib 生成的 apLib 的属性
apC.getAttributes().attribute(Attribute.of("Type", String.class), "ap")
apC.getIncoming().artifactView({
config ->
config.attributes {
// 定义属性, 用于匹配 apLib apVar 的属性
container -> container.attribute(Attribute.of("artifactType", String.class), "ap")
}
}).getArtifacts().getArtifactFiles().getFiles(); // /lib/demo.ap

数据流向: 根据 apC 的属性匹配到 lib 工程的 apLib 的属性. 根据 container 的属性匹配到了 apVar 的属性. 获取到产物.
属性的匹配默认是调用两个值的 equals 方法. 当然你可以自己定义一个匹配策略和一个解决匹配冲突的的策略.

1
2
3
AttributeMatchingStrategy attributeMatchingStrategy = project.getDependencies().getAttributesSchema().attribute(Attribute.of("artifactType", String.class)); // 获取规则
attributeMatchingStrategy.compatibilityRules.add(TypeCompatibilityRules.class); // 定义新的匹配策
attributeMatchingStrategy.disambiguationRules.add(TypeDisambiguationRules.class); // 定义解决冲突的策

到这里. 我们还没有讲到 Transform 的应用. 属性匹配是 Transform 的基础. 当匹配不成功的时候, 会根据 Transform 定义的 FromTo 尝试组成转化规则. 下面是一个简单的转换

1
2
3
4
5
6
7
8
9
10
11
12
DependencyHandler dependencies = project.getDependencies();
dependencies.registerTransform({
it.getFrom().attribute(ArtifactAttributes.ARTIFACT_FORMAT, "ap");
it.getTo().attribute(Attribute.of("artifactType", String.class), "ap-info");
it.artifactTransform(ApInfoTransform.class);
});
apC.getIncoming().artifactView({
config ->
config.attributes {
container -> container.attribute(Attribute.of("artifactType", String.class), "ap-info")
}
}).getArtifacts().getArtifactFiles().getFiles();

定义了一个转换规则: 由 ap 转化成 ap-info .具体转换在 ApInfoTransform

数据的流向是: 根据 apC 的属性匹配到 lib 工程的 apLib 的属性. container 的属性没有找到能够匹配的属性. 但是匹配到了 ApInfoTransform 的To, ApInfoTransform 的 From 匹配到了 apVar 的属性. 至此组成了一个转化规则, 由 apVar 获取到的产物,需经过 ApInfoTransform 转化后返回.

#0x03 Android Plugin 的应用

Android Plugin 正式使用 Gradle Transform 是在 3.0 上.

1. Android Plugin 2.3.0 实现

2.3.0

处理的是 AAR

  1. AAR 的来源有两个: 一种是 AAR 依赖 通过网络下载或本地获取, 一种是 Android Library Module 通过 bundleXX 任务打包出来的 AAR.
  2. Application Plugin 通过依赖关系收集所有的AAR, 再通过 PrepareLibraryTask 将所有 AAR 文件的解压, 用 AndroidDependency 来管理这些 AAR . 通过自己管理这些目录. 组织出不同的资源目录, 如 jar ,Jni,Aidl,Proguard …

    2. Android Plugin 3.0.0 实现

    3.0
    处理的是 attribute 产物
    这里的 attribute 产物来源主要是两种,
    一种是 AAR 依赖, 通过网络下载AAR, 通过 ExtractAarTransform 将 ARTIFACT_FORMAT 为 aar 转化成 android-exploded-aar 的产物. 再通过不不同的 AarTransform 将 ARTIFACT_FORMAT 为 android-exploded-aar 转化成不同的属性的产物.
    一种是通过使用 Android Library Module . 通过往 Configuration 输出不同属性的产物.

3. 对比:

对比 2.3.0 来说 通过将 Transform 的功能将 AAR 文件的处理的时机和缓存全部移交给了 Gradle,
好处

  1. Transform 的处理是只有一次, 可以节省多余重复的解压.
  2. 缓存是全局的. 所有的工程都能复用同一个缓存.

同时由于 Gradle 并没有对 AAR 依赖做特别的适配, 导致在2.3.0 上处理依赖的时候,需要对依赖进行二次管理, Android Plugin 3.0.0 不需要这些额外的处理,只需要通过定义不同的 Transform 获取到对应的产物就好了.

0x03 Android Plugin Transform 和 Gradle Transform

  • 维度不同
    Android Plugin Transform 有两个维度一个是 ContentType ,一个是 Scope, 一个产物只有一个ContentType 和 Scope , Gradle 维度是 Attribute ,一个产物有多个 Attribute.
  • 扩展能力不同
    Android Plugin Transform 不支持自定义 ContentType , Gradle 支持自定义 Attribute .
  • 缓存不同
    Android Plugin Transform 将结果记录是在一个大的文件目录下, 通过不同的文件名称来表示不同 Scope 下的 ContentType.
    Gradle Transform 是将 Transform 输出的结果记录在 Gradle 的文件缓存中,
    由于记录的方式不同, 导致的结果是 Android plugin Transform 的一个输入对应一个输出,即使你并不对输入做任何操作, 也需要将输入的文件 copy 到输出的文件夹下.才能被下一个 Transform 正确识别. 而 Gradle Transform 不同, 记录是结果, 你完全可以不对输出目录做任何的事情, 直接将原有的输出地址返回回去.
  • 缓存范围不同
    Android Plugin Transform 缓存是 Application 级别的. 在 build/intermediates/transforms/目录下以transforms名称命名的.
    Gradle Transform 是全局的, 在.gradle/caches/transforms-1/files-1.1/下, 文件名称是由所有关键信息进行 hash 算出来的.
  • 应用不同
    Gradle Transform 应用的是依赖的产物。 将产物A 转化成 产物B, 不支持同属性的转化.
    Android Plugin Transform 应用的是打包过程中的产物如 so, classs, res,dex。 这些信息大部分是由依赖产物加工而来的。支持相同ContentType 的转化.

0x04 总结

通过上述的对比, 我们可以知道 两个 Transform 应用在不同场景, Gradle Transform 是对依赖进行处理. 这种处理是全局性的.而 Android Plugin Transform 是对打包过程中中间产物的再处理, 是针对 Application 的.

0x05 尾巴

这一块的代码看蛮久的, 因为涉及的代码比较多, 面比较广, 需要有一个比较好的耐心和比较久的时间. 通过这一块的梳理, 对 Android plugin 3.0 和 Gradle 4.1 的依赖的管理有更好的理解.