Android Gradle Plugin 3.0 以后依赖声明使用了 implementation
和 api
来替代原来的 compile 。提供了对依赖进行更细致的控制。
这一特性是由 JavaLibraryPlugin 中衍生过来的。 更准确的来说是 JavaBasePlugin 。Android 并不依赖 JavaLibraryPlugin。
好处:
加快编译速度。
相关链接: 迁移到 Android Plugin for Gradle 3.0.0 - 使用新依赖项配置
Gradle 是使用 Configuration 表示一组依赖。
如:
dependencies { compile 'com.google.code.gson:gson:2.8.5' }
为名称为 compile
的 Configuration
声明 gson 依赖,版本为 2.8.5。同样的可以为 api
, implementation
或自定义的 Configuration
声明依赖。
JavaLibrary 会生成主要的几个 Configuration: api
, compile
, compileOnly
, compileClasspath (JAVA_API)
, implementation
, runtime
, runtimeOnly
, runtimeClasspath (JAVA_RUNTIME)
, apiElements (JAVA_API)
, runtimeElements (JAVA_RUNTIME_JARS)
.
Configuration 存在继承关系:
Classpath 类型:
compileClasspath runtimeClasspath 总结如下
Elements 类型:
apiElements runtimeElements 总结如下
这里 Configuration 主要分为 3 种:
JavaLibrary 将生成的 jar 文件注册在 apiElements 和 runtimeElements 上。
将 javac 产生的 class 文件注册在 apiElements 上。注册就能其他工程通过对应的 Configuration 获取到 。
主工程通过 compileClasspath (JAVA_API) 可以获取到子工程的 apiElements(JAVA_API)。 同时能获取到子工程的 class 文件以及 apiElements 上的声明依赖( 来自 api 和 compile 声明)。
通过下面例子了解 api 和 implementation 更多的区别:
描述: app 用 implementation 方式依赖 lib ,lib 使用 api 方式依赖 libsub1 和 implement 方式依赖了libsub2。
以 app 工程 javac task 为例说明:
javac task 的实现类 JavaCompile。 该任务将 java 文件编译成 jvm 能执行的 class 。 在这个过程中有两个主要的输入 source 和 classpath。
source 是工程中的 java 文件集合。 classpath 是 javac 编译时需要的 class 路径。这里包括 jdk 和工程的依赖 。 依赖的通过 compileClasspath 获取。
获取链路如下。
可知。 app 的 compileClasspath 中只有 lib 和 libsub1 ,libsub2 因为使用 implement 而不出现 app 的编译路径中。
这样的好处就是对于 lib 来说,它最小化的对外提供了信息, 屏蔽了 libsub2 的存在。
加速主要是两点
一个任务的输出和输出没有变更。该任务不执行。直接使用上次的输出文件为结果。则该任务为 UP-TO-DATE 。
javac 的 classpath 被 @Classpath 和 @CompileClasspath 注解表示。
通过之前的文章 Gradle Task UP-TO-DATE 可知。在这两个注解下 Gradle 会对 classpath 的 class 文件进行重新排序和 ABI 化。
这样的好处在于
通过尽可能的让任务 UP-TO-DATE 让编译时长加。这里所有的优化的都是在增量编译的情况下生效。
注:
ABI 化: 删除了所有私有的方法和字段。 同时删除了所有方法的方法体。具体可查看 AbiExtractingClasspathResourceHasher
ABI 变更:修改,新增,删除了 非私有 方法签名。修改或新增了非私有属性 等都将引起 ABI 变更。
2 javac task classpath 的缩减。
由于出现的 编译的 classpath 路径减少 ,让 javac 编译时查找对应类减少几次查找从而加快编译速度。 这一块的加速个人感官上是很轻微的。
总结: implementation 和 api 并不会影响本工程的编译或运行。它只影响本工程对外提供的依赖列表。
Android 在原有的纬度加入了 Flavor 和 BuildType 的纬度。 使复杂层度上了一个台阶。变成了 xxCompile xxApi xxRuntime 。 对于 java 项目而言对外提供两种 Configuration:编译期 apiElements 和 运行期 runtimeElements 各一个。Android 因为存在 Flavor 和 BuildType 。 虽然它对外提供的也是两种 Configuration。 但每种 Configuration 又存在多个变种。默认情况下编译期 debugApiElements 和 releaseApiElements。 运行期 debugRuntimeElements 和 releaseRuntimeElements。加入 flavor 以后复杂度又翻了一倍。
在 AGP 2.x 的时候 主工程 BuildType 不管是 Debug 或 Release 默认都使用 library 的 Release。 3.0 以后开始对这种情况进行优化。debug 工程引用 library 的 Debug。Release 工程使用 library 的 Release 。
实现的原理在于为这些提供 apiElements / runtimeElements 的 Configuration 在原有的属性加入了 BuildtTpe 和 Flavor 属性信息。
Configuration 在查找的时候,如果只查到一个,检查双方的属性是否相等或相兼容。 检查成功则选择该 Configuration, 如果查询出现多个的时候, 会根据查找的属性的进行选择,找到一个匹配最全的, 如果没有最全的。 则进行择优匹配。对单个属性值根据规则逐一进行比较,丢弃相对较差的 Configuration。 ( 查找的属性+ 候选的属性)。这样如果选择出最合适的一个则选择该Configuration 。否则查找失败。
这里涉及到的兼容和择优的规则参考之前 Gradle Transform 初探 的文章 rule 相关信息。
对于Android compileClasspath 只参与编译本工程的 java 文件. 最终还需要将 runtimeClasspath 打入 apk 中。
Android Configuration 设置详情查看 VariantDependencies
Configuration 属性匹配详情查看 ComponentAttributeMatcher
Android dependency ‘com.android.support:support-support-v4’ has different version for the compile (25.2.0) and runtime (26.0.0-beta2) classpath. You should manually set the same version via DependencyResolution.
一个依赖存在 编译期 和运行期。 不可避免会发生同个依赖在两边的依赖版本不一致的问题。这可能导致 API 的不兼容或 ClassNotFound 等问题。 为此 Android 在 prebuild 会对两个 Configuration 的依赖进行版本比较。
详情查看 AppPreBuildTask
Gradle 的依赖分为本地和远程, 本地依赖有本地工程或者本地文件。 远程依赖的有 Maven 和 ivy 依赖。 Gradle 天生支持 Maven 依赖。Gradle 使用 GradlePomModuleDescriptorParser 对 pom 文件进行解析。 pom 文件是 maven 依赖的描述。 主要包括以下几个属性:
Gradle 为 Maven 依赖提供 10 种 Configuration 来管理。 default
, master
, compile
, provided
, runtime
, test
, system
, sources
, javadoc
, optional
。
详情查看 GradlePomModuleDescriptorBuilder.MAVEN2_CONFIGUR
Configuration 对应 Maven 的 scope ,这里会发现 scope 的种类只有5个 (不包含 import)。 但是Configuration 却有10个这么多。 追其原因这是为了兼容 ivy 格式的依赖。 Maven 依赖列表中不一定都是 Maven 依赖。 也可能是 ivy 依赖。ivy 相关可以查看链接 ivyfile-dependency
不同的 Configuration 获取的依赖是不同的。 这里不讨论 ivy 的兼容,根据 Maven 的特性介绍几个重要的 Configuration。
二级依赖解析规则如下。 compile 获取 scope 为的 compile 的依赖。其他均获取 scope 为的 compile 和 runtime 的依赖。 scope 非 compile /runtime 均会被忽略。或许设计便是如此。
详情查看 MavenDependencyDescriptor.selectLegacyConfigurations
注意 本小节的 Configuration 不等价于的 Gradle 的 Configuration。
1.默认 Configuration
默认 Configuration 为 default 。
compile "com.dim:lib:1.0"
等价
compile (group: 'com.dim', name: 'lib', version: '1.0',configuration:"defalut")
2.实现 implementation,api 的效果
Maven 的 scope 存在 rumtime 和 compile 。 应对到 Gradle 的 apiElements 和 runtimeElements 。
java 工程
api / implementation project(":dim")
替换
api / implementation (group: 'com.dim', name: 'lib', version: '1.0',configuration:"compile") runtime (group: 'com.dim', name: 'lib', version: '1.0',configuration:"runtime")
Android 工程
因为 Android 存在 debug 和 release ,所以较为复杂,debug / release在 maven 中以 classifier 的形式存在。
debugApi / debugImplementation (group: 'com.dim', name: 'lib', version: '1.0',classifier: 'debug', configuration:"compile") debugRuntime (group: 'com.dim', name: 'lib', version: '1.0',classifier: 'debug',configuration:"runtime") releaseApi / releaseImplementation (group: 'com.dim', name: 'lib', version: '1.0',classifier: 'release', configuration:"compile") releaseRuntime (group: 'com.dim', name: 'lib', version: '1.0',classifier: 'release',configuration:"runtime")
android 上的实现略显臃肿,有什么办法解决呢? Gradle Metadata ?
为了弥补 Maven 的局限, Gradle 引入 Gradle Metadata。
它在原有的基础上加入 .module 文件来扩展 Maven 的 pom 功能。
这里去掉了 Scope 的概念,转为 Variant 。对 pom 依赖进行重新组合。 一组依赖就是一个 Variant 。
{ "formatVersion": "0.4", "component": { "group": "com.dim", "module": "lib", "version": "1.0", "attributes": { "org.gradle.status": "release" } }, "createdBy": { "gradle": { "version": "4.10.2", "buildId": "priv3n7sd5bvbpnahf26lakzju" } }, "variants": [ { "name": "debugApiElements", "attributes": { "com.android.build.api.attributes.BuildTypeAttr": "debug", "com.android.build.api.attributes.VariantAttr": "debug", "com.android.build.gradle.internal.dependency.AndroidTypeAttr": "Aar", "org.gradle.usage": "java-api" }, "dependencies": [ { "group": "com.google.code.gson", "module": "gson", "version": { "requires": "2.8.5", "reject": "2.7.0" } } ], "files": [ { "name": "lib-debug.aar", "url": "lib-debug.aar", "size": 21590, "sha1": "afafefc0dccfcfb0246dc9201868e12e83df04ac", "md5": "7374a663e6ba72a82ba767f92a2bf810" } ] }, . ] }
这是一个 Variant 的描述。
.module 文件生成和解析查看
ModuleMetadataFileGenerator.generateTo()
ModuleMetadataParser.parse()
Variant 几乎是 Gradle Configuration 的翻译。它甚至可以指定版本约束,如拒绝某个依赖版本。Metadata 可以和 Gradle 的 Configuration 系统做完美的结合。 实现了依赖 Project 是什么样子,依赖 Project 生成的 Gradle Metadata 便是什么样子。
Gradle Metadata 只是一个 Gradle 的改进。 对于 Maven 发布的时候,不仅 .module 存在, pom 文件也会被保留。 这样的好处是当 Gradle Metadata 不兼容的情况下使用 pom 文件进行降级。同时不影响其他编译工具对 Maven 的支持。
enableFeaturePreview("GRADLE_METADATA")
全局启用了 GRADLE_METADATA 特性,该特性会为所有仓库会先检查是否存在 .module。 查询失败降级查询 pom 文件。java 工程原生支持 Gradle Metadata。Gradle 6.0 以下在该特性下 使用 maven-publish 插件发布的时候自动会带上 .module 信息。6.0 默认自动带上.module 文件。
由于并不不是所有仓库都支持 Gradle Metadata 。所有仓库都先查询一遍 module ,这或许过于浪费。可以为单一的仓库设置。
repositories { maven { url = "xxx" metadataSources({ it.gradleMetadata() }) } }
有了 Gradle Metadata 加持下。 可以很方便的实现 类似 implementation 和 api 的效果
api / implementation project(":dim") 等价 api / implementatio "com.dim:lib:1.0"
Gradle Metadata 在当前的环境下并非没有缺点
Gradle 觉得 Metadata 可以帮我们逃离依赖地狱。 或许可以或许通往另外一个地狱。
国内网络上对于 Gradle Metadata 这块的几乎没有涉及。一个 feature 存在 4.10.2 甚至更早。在 5.3 正式发布了 1.0。但是国内这块的涉及几乎没有,这是非常可惜的。
尽管有这么多个缺陷。但是这个特性确实让人兴奋。 Gradle 提供的功能或多或少都存在一些 Bug。 我们只希望 Gradle 6.0 尽快到来。