Gradle进阶计划(二)Gradle Plugin原理分析

安稳与你 提交于 2021-02-02 10:39:37

        通过 Gradle进阶计划(一)Gradle初探 的介绍,我们已经对Gradle有了初步的了解。这篇文章我们更深入研究一下 Gradle Plugin 的原理。

 

一、Gradle 和 Gradle Plugin

        首先,我们需要先明确一个概念,就是 Gradle 和 Gradle Plugin 是不同的。 

(一)Gradle

        结合上一篇文章,官方已经对Gradle已经有了很详细的定义,这里在重点解释一下。

        Gradle 是一个构建项目的工具,将它用来编译Android App能够简化你的编译、打包、测试过程。它其实不仅仅是用在Android Studio上。如何去理解构建工具呢?构建工具就是对你的项目进行编译、运行、签名、打包、依赖管理等一系列功能的合集。例如 Eclipse 最初是用来做 Java 开发的,Google 为了能在 Eclipse 上进行Android 开发,开发了ADT 插件(Android Developer Tools),正是因为有了 ADT ,我们才可以在 Eclipse 上进行编译、运行、签名、打包等一系列流程。而这背后的工作都是 ADT 的功劳,ADT 就是我们的构建工具。一般来说,构建工具除了以上提到的编译、运行、签名、打包等,还具备依赖管理的功能。什么是依赖管理呢?例如我们以前在 Eclipse 上开发 Android 引用第三方库,一般都是先下载 jar 文件,然后把 jar 文件添加到 libs 目录,依赖管理就是将这个过程自动化。

        在 Gradle 之前也是 Android 也有其他构建工具的,这就是大名鼎鼎的 Maven。Java 世界中主要有三大构建工具:Ant、Maven 和 Gradle,经过几年的发展,Ant 几乎销声匿迹,所以在早期 Android 开发中,是使用 Maven 作为构建工具的。但是 Gradle 相较于 Maven 有着较大的优势,所以 AS 时代,Google 用 Gradle 替代了 Maven 作为 Android 开发的构建工具。

  • Gradle 对于依赖的管理更加的简洁。不需要像 Maven 定义 groupId、artifactId、version 等繁琐的属性值;
  • Gradle 支持动态的版本依赖。在版本号后面使用+号的方式可以实现动态的版本管理;
  • Gradle 在解决依赖冲突方面实现机制更加明确。使用 Maven 和 Gradle 进行依赖管理时都采用的是传递性依赖;而如果多个依赖项指向同一个依赖项的不同版本时就会引起依赖冲突。而 Maven 处理这种依赖关系往往是噩梦一般的存在;
  • Gradle 多模块构建方面具有优势。相较于 Maven 聚合 POM 的形式,Gradle allprojects 和 subprojects 的定义方法更加的方便和清晰;
  • Gradle 在插件机制的支持上做的更好。Maven 配置语法太受限于XML,Gradle中则一切变得非常简单。

(二)Gradle Plugin

        为了支持 Gradle 能在 AS 上使用,Google 开发了一个 AS 的插件叫 Android Gradle Plugin ,所以我们能在 AS 上使用 Gradle 完全是因为这个插件的原因。它一边调用 Gradle 本身的代码和批处理工具来构建项目,一边调用 Android SDK 的编译、打包功能,从而让我们能够顺畅地在AS上进行开发。

        Gradle Plugin 是独立于Android Studio 运行的,它的更新也是与Android Studio分开的。Gradle Plugin 会有版本号,每个版本号又对应有一个或一些 Gradle发行版本(一般是限定一个最低版本)。这也是为什么如果这两个版本对应不上了,你的工程构建的时候会报错。

插件版本 Gradle版本
1.0.0 - 1.1.3 2.2.1 - 2.3
1.2.0 - 1.3.1 2.2.1 - 2.9
1.5.0 2.2.1 - 2.13
2.0.0 - 2.1.2 2.10 - 2.13
2.1.3 - 2.2.3 2.14.1+
2.3.0+ 3.3+
3.0.0+ 4.1+
3.1.0+ 4.4+

        Android Studio 3.0 之后自动将插件版本升级到3.0.0,所以我们也需要对应地把Gradle升级到4.1才行。

        其实前面我们还提到了Gradle Wrapper,这里再简单说一下。

        Gradle Wrapper:意为 Gradle 的包装,它的作用是简化Gradle本身的安装、部署。不同版本的项目可能需要不同版本的Gradle,手工部署的话比较麻烦,而且可能产生冲突,所以需要Gradle Wrapper帮你搞定这些事情。Gradle Wrapper是Gradle项目的一部分。

        假设我们本地有多个项目,一个是比较老的项目,还用着 Gradle 1.0 的版本,一个是比较新的项目用了 Gradle 2.0 的版本,但是你两个项目肯定都想要同时运行的,如果你只装了 Gradle 1.0 的话那肯定不行,所以为了解决这个问题,Google 推出了 Gradle Wrapper 的概念,它在你每个项目都配置了一个指定版本的 Gradle ,你可以理解为每个 Android 项目本地都有一个小型的 Gradle ,通过这个每个项目你可以支持用不同的 Gradle 版本来构建项目。   

       总结:

       Gradle:是一个构建工具,版本定义在 gradle-wrapper.properties中的distributionUrl=https/://services.gradle.org/distributions/gradle-x.xx-all.zip
       Gradle Plugin:是谷歌为使用Gradle而自行开发的工具,版本定义在 build.gradle中依赖的classpath 'com.android.tools.build:gradle:x.x.x'

 

二、Gradle Plugin 主要流程

        对于源码的分析这里就不展开讲了,详情可参照 android gradle plugin 源码地址,我只介绍一下涉及到的主要流程。

         Android gradle plugin 的入口类 com.android.build.gradle.AppPlugin,这个可以查看 properties 文件,里面声明了对应插件的入口类。

         AppPlugin 继承自 BasePlugin。AppPlugin 里没有做太多的操作,主要是重写了 createTaskManager 和 createExtension,剩下的大部分工作还是在 BasePlugin 里做的。

        有几点需要注意:

  • build.gradle 里见到的 android {} dsl 是在 BasePlugin.configureExtension() 里声明的
  • 主要的 task 是在 BasePlugin.createAndroidTasks() 里生成的
  • 主要 task 的实现可以在 TaskManager 中找到
  • transform 会转化成 TransformTask

 

三、主要 Task 分析

        我们先看一下,生成一个 APK 所需的构建流程是怎样的。官方流程图如下:

        下面是详细的打包流程图:

(1)打包资源文件。使用aapt来打包res资源文件,生成R.java、resources.arsc和res文件。R.java文件是所有res资源的id列表。resources.arsc里面会对所有的资源id进行组装,在apk运行时获取资源的时候会根据设备的情况获得不同的资源。
(2)处理aidl文件,生成相应java 文件。这个阶段aidl会处理.aidl文件,生成对应的Java接口文件。对于没有使用到aidl的android工程,可以跳过此步骤。
(3)编译工程源代码,生成相应class 文件。通过javac编译R.java、Java接口文件、Java源文件,生成.class文件。如果有配置混淆的话,会编译成混淆的class文件。
(4)转换所有class文件,生成classes.dex文件。Android系统的Dalvik虚拟机的可执行文件为DEX格式,程序运行所需的class.dex就是在这一步生成的,使用到的工具为dx,主要的工作是将java字节码转换为Dalvik字节码、压缩常量池、消除冗余信息等。
(5)打包生成apk。apkbuilder将classes.dex、resources.arsc、res文件夹(res/raw资源被原装不动地打包进APK之外,其它的资源都会被编译或者处理)、Other Resources(assets文件夹)、AndroidManifest.xml打包成apk文件。
(6)对apk文件进行签名。通过jarsigner对apk进行签名。
(7)对签名后的apk文件进行对其处理。通过zipalign对签名后的apk进行对齐处理,它能够对打包后的app进行优化。





        那么以 Task 的维度来看 apk 的打包,是什么流程呢?通过执行下面的命令,可以打印出打包一个 apk 需要哪些 task 。

./gradlew android-gradle-plugin-source:assembleDebug --console=plain

        输出结果如下:

:android-gradle-plugin-source:preBuild UP-TO-DATE
:android-gradle-plugin-source:preDebugBuild
:android-gradle-plugin-source:compileDebugAidl
:android-gradle-plugin-source:compileDebugRenderscript
:android-gradle-plugin-source:checkDebugManifest
:android-gradle-plugin-source:generateDebugBuildConfig
:android-gradle-plugin-source:prepareLintJar UP-TO-DATE
:android-gradle-plugin-source:generateDebugResValues
:android-gradle-plugin-source:generateDebugResources
:android-gradle-plugin-source:mergeDebugResources
:android-gradle-plugin-source:createDebugCompatibleScreenManifests
:android-gradle-plugin-source:processDebugManifest
:android-gradle-plugin-source:splitsDiscoveryTaskDebug
:android-gradle-plugin-source:processDebugResources
:android-gradle-plugin-source:generateDebugSources
:android-gradle-plugin-source:javaPreCompileDebug
:android-gradle-plugin-source:compileDebugJavaWithJavac
:android-gradle-plugin-source:compileDebugNdk NO-SOURCE
:android-gradle-plugin-source:compileDebugSources
:android-gradle-plugin-source:mergeDebugShaders
:android-gradle-plugin-source:compileDebugShaders
:android-gradle-plugin-source:generateDebugAssets
:android-gradle-plugin-source:mergeDebugAssets
:android-gradle-plugin-source:transformClassesWithDexBuilderForDebug
:android-gradle-plugin-source:transformDexArchiveWithExternalLibsDexMergerForDebug
:android-gradle-plugin-source:transformDexArchiveWithDexMergerForDebug
:android-gradle-plugin-source:mergeDebugJniLibFolders
:android-gradle-plugin-source:transformNativeLibsWithMergeJniLibsForDebug
:android-gradle-plugin-source:transformNativeLibsWithStripDebugSymbolForDebug
:android-gradle-plugin-source:processDebugJavaRes NO-SOURCE
:android-gradle-plugin-source:transformResourcesWithMergeJavaResForDebug
:android-gradle-plugin-source:validateSigningDebug
:android-gradle-plugin-source:packageDebug
:android-gradle-plugin-source:assembleDebug

        下面列出了各个 task 的实现类及作用:

Task 对应实现类 作用
preBuild   空 task,只做锚点使用
preDebugBuild   空 task,只做锚点使用,与 preBuild 区别是这个 task 是 variant 的锚点
compileDebugAidl AidlCompile 处理 aidl
compileDebugRenderscript RenderscriptCompile 处理 renderscript
checkDebugManifest CheckManifest 检测 manifest 是否存在
generateDebugBuildConfig GenerateBuildConfig 生成 BuildConfig.java
prepareLintJar PrepareLintJar 拷贝 lint jar 包到指定位置
generateDebugResValues GenerateResValues 生成 resvalues,generated.xml
generateDebugResources   空 task,锚点
mergeDebugResources MergeResources 合并资源文件
createDebugCompatibleScreenManifests CompatibleScreensManifest manifest 文件中生成 compatible-screens,指定屏幕适配
processDebugManifest MergeManifests 合并 manifest 文件
splitsDiscoveryTaskDebug SplitsDiscovery 生成 split-list.json,用于 apk 分包
processDebugResources ProcessAndroidResources aapt 打包资源
generateDebugSources   空 task,锚点
javaPreCompileDebug JavaPreCompileTask 生成 annotationProcessors.json 文件
compileDebugJavaWithJavac AndroidJavaCompile 编译 java 文件
compileDebugNdk NdkCompile 编译 ndk
compileDebugSources   空 task,锚点使用
mergeDebugShaders MergeSourceSetFolders 合并 shader 文件
compileDebugShaders ShaderCompile 编译 shaders
generateDebugAssets   空 task,锚点
mergeDebugAssets MergeSourceSetFolders 合并 assets 文件
transformClassesWithDexBuilderForDebug DexArchiveBuilderTransform class 打包 dex
transformDexArchiveWithExternalLibsDexMergerForDebug ExternalLibsMergerTransform 打包三方库的 dex,在 dex 增量的时候就不需要再 merge 了,节省时间
transformDexArchiveWithDexMergerForDebug DexMergerTransform 打包最终的 dex
mergeDebugJniLibFolders MergeSouceSetFolders 合并 jni lib 文件
transformNativeLibsWithMergeJniLibsForDebug MergeJavaResourcesTransform 合并 jnilibs
transformNativeLibsWithStripDebugSymbolForDebug StripDebugSymbolTransform 去掉 native lib 里的 debug 符号
processDebugJavaRes ProcessJavaResConfigAction 处理 java res
transformResourcesWithMergeJavaResForDebug MergeJavaResourcesTransform 合并 java res
validateSigningDebug ValidateSigningTask 验证签名
packageDebug PackageApplication 打包 apk
assembleDebug   空 task,锚点

        以上就是打包一个 APK 所需要的 Task,在分析主要Task之前,我们先了解一下 Task 的分类。

        在 Gradle Plugin 中的 Task 主要有三种:

(1)普通 Task

  • 一般继承 DefaultTask;
  •  看 @TaskAction 注解的方法,此方法就是这个 Task 做的事情

(2)增量 Task:相对于全量来说的,全量我们可以理解为调用 clean 以后第一次编译的过程,这个就是全量编译,之后修改了代码或者资源文件,再次编译,就是增量编译。

  •  首先这个 Task 要继承 IncrementalTask;
  • 其次看 isIncremental 方法,如果返回 true,说明支持增量,返回 false 则不支持;
  • 然后看 doFullTaskAction 方法,是全量的时候执行的操作;
  • 最后看 doIncrementalTaskAction 方法,这里是增量的时候执行的操作。

(3)Transform:这个后面我会专门出一篇文章来讲。

  • 继承自 Transform;
  • 重点关注 transform 方法实现。

1. generateDebugBuildConfig

(1)实现类:GenerateBuildConfig

(2)整体实现:

(3)代码调用:

GenerateBuildConfig.generate -> BuildConfigGenerator.generate -> JavaWriter

(4)分析实现:

        在 GenerateBuildConfig 中,主要生成代码的步骤如下:

  • 生成 BuildConfigGenerator
  • 添加默认的属性,包括 DEBUG,APPLICATION_ID,FLAVOR,VERSION_CODE,VERSION_NAME
  • 添加自定义属性
  • 调用 JavaWriter 生成 BuildConfig.java 文件

2. mergeDebugResources

(1)实现类:MergeResources

(2)整体实现:

(3)代码调用:

MergeResources.doFullTaskAction -> ResourceMerger.mergeData -> MergedResourceWriter.end 
-> QueueableAapt2.compile -> Aapt2QueuedResourceProcessor.compile -> AaptProcess.compile 
-> AaptV2CommandBuilder.makeCompile

 (4)分析实现:

         MergeResources 这个类,继承自 IncrementalTask,我们重点关注 doFullTaskAction 这个全量方法。

  • 通过 getConfiguredResourceSets() 获取 resourceSets,包括了自己的 res/ 和 依赖库的 res/ 以及 build/generated/res/rs
  • 创建 ResourceMerger
  • 创建 QueueableResourceCompiler,因为 gradle3.x 以后支持了 aapt2,所以这里有两种选择 aapt 和 aapt2。其中 aapt2 有三种模式,OutOfProcessAaptV2,AaptV2Jni,QueueableAapt2,这里默认创建了 QueueableAapt2,resourceCompiler = QueueableAapt2
  • 将第一步获取的 resourceSet 加入 ResourceMerger 中
  • 创建 MergedResourceWriter
  • 调用 ResourceMerger.mergeData 合并资源
  • 调用 MergedResourceWriter 的 start(),addItem(),end() 方法
  • 调用 QueueableAapt2 -> Aapt2QueuedResourceProcessor -> AaptProcess 处理资源,这一步调用 aapt2 命令去处理资源,处理完以后 xxx.xml.flat 格式

3. processDebugResources

(1)实现类:ProcessAndroidResources

(2)整体实现:

(3)代码调用:

ProcessAndroidResources.doFullTaskAction -> ProcessAndroidResources.invokeAaptForSplit 
-> AndroidBuilder.processResources -> QueueAapt2.link -> Aapt2QueuedResourceProcessor.link 
-> AaptProcess.link -> AaptV2CommandBuilder.makeLink

(4)分析实现:

  • 获取 split 数据,返回的是一个 ApkData 列表,ApkData 有三个子类,分别是 Main,Universal,FullSplit。这里的 ApkData 会返回一个 Universal 和多个 FullSplit,Universal 代表的是主 apk,FullSplit 就是根据屏幕密度拆分的 apk。如果我们没有配置 splits apk,那么这里只会返回一个 Main 的实例,标识完整的 apk。
  • 处理 main 和 不依赖 density 的 ApkData 资源
  • 调用 invokeAaptForSplit 处理资源
  • 调用 AndroidBuilder.processResources -> QueueAapt2.link -> Aapt2QueuedResourceProcessor.link -> AaptProcess.link -> AaptV2CommandBuilder.makeLink 处理资源,生成资源包以及 R.java 文件
  • 处理其他 ApkData 资源,这里只会生成资源包而不会生成 R.java 文件

4. processDebugManifest

(1)实现类:MergeManifests

(2)整体实现:

(3)代码调用:

MergeManifests.dofFullTaskAction -> AndroidBuilder.mergeManifestsForApplication 
-> Invoker.merge -> ManifestMerge2.merge

(4)分析实现:

        这个 task 功能主要是合并 mainfest,包括 module 和 flavor 里的,整个过程通过 MergingReport,ManifestMerger2 和 XmlDocument 进行。
这里直接看 ManifestMerger2.merge() 的 merge 过程 。 主要有几个步骤:

  • 获取依赖库的 manifest 信息,用 LoadedManifestInfo 标识
  • 获取主 module 的 manifest 信息
  • 替换主 module 的 Manifest 中定义的某些属性,替换成 gradle 中定义的属性 例如: package, version_code, version_name, min_sdk_versin 等
  • 合并 flavor,buildType 中的 manifest
  • 合并依赖库的 manifest
  • 处理 manifest 的 placeholders
  • 之后对最终合并后的 manifest 中的一些属性重新进行一次替换,类似步骤 4
  • 保存 manifest 到 build/intermediates/manifest/fullxxx/AndroidManifest.xml 这就生成了最终的 Manifest 文件

5. transformClassesWithDexBuilderForDebug

(1)实现类:DexArchiveBuilderTransform

(2)整体实现:

(3)代码调用:

DexArchiveBuilderTransform.transform -> DexArchiveBuilderTransform.convertJarToDexArchive 
-> DexArchiveBuilderTransform.convertToDexArchive -> DexArchiveBuilderTransform.launchProcessing 
-> DxDexArchiveBuilder.convert

(4)分析实现:

        在 DexArchiveBuilderTransform 中,对 class 的处理分为两种方式,一种是对 目录下的 class 进行处理,一种是对 .jar 里的 class 进行处理。
为什么要分为这两种方式呢?.jar 中的 class 一般来说都是依赖库,基本上不会改变,gradle 在这里做了一个缓存,但是两种方式最终都会调用到 convertToDexArchive。

  • convertJarToDexArchive 处理 jar。处理 .jar 时,会对 jar 包中的每一个 class 都单独打成一个 .dex 文件,之后还是放在 .jar 包中
  • convertToDexArchive 处理 dir 以及 jar 的后续处理。 对 dir 处理使用 convertToDexArchive,其中会调用 launchProcessing。在 launchProcessing 中,有下面几个步骤:a)判断目录下的 class 是否新增或者修改过     b)调用 DexArchiveBuilder.build 去处理修改过的 class     c)DexArchiveBuilder 有两个子类,D8DexArchiveBuilder 和 DxDexArchiveBuilder,分别是调用 d8 和 dx 去打 dex

6. transformDexArchiveWithExternalLibsDexMergerForDebug / transformDexArchiveWithDexMergerForDebug

(1)实现类:ExternalLibsMergerTransform / DexMergerTransform

(2)整体实现:

 

(3)代码调用:

// dx 
ExternalLibsMergerTransform / DexMergerTransform.transform -> DexMergerTransformCallable.call 
-> DxDexArchiveMerger.mergeDexArchives -> DxDexArchiveMerger.mergeMonoDex 
-> DexArchiveMergerCallable.call -> DexMerger.merge

// d8
ExternalLibsMergerTransform / DexMergerTransform.transform -> DexMergerTransformCallable.call 
-> D8DexArchiveMerger.mergeDexArchives -> 调用 D8 命令

(4)分析实现:

        主要是处理依赖库的 dex,把上一步生成的依赖库的 dex merge 成一个 dex。

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!