Gradle构建周期及其应用

>>强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!

1.1 构建周期初探1.2 构建周期的运用    1.2.1 hook Gradle构建过程    1.2.2 多渠道打包相关问题        解决思路        解决方案

1.1 构建周期初探

在build.gradle中写一个task

task gotoAuthLifeCycle{
   println "gradle's lifecycle"
}

在terminal终端上运行 ./gradlew gotoAuthLifeCycle ,执行过程会出现以下内容:

Gradle构建周期及其应用


从上图中可以看到一个CONFIGURING(Configuration),这表示的构建周期中的配置阶段,因为上述task内容简单,所以构建速度快,也就截到了配置阶段,实际上还有两个阶段(InitializationExecution),简单来说总共三个构建阶段,下面是官网的介绍:

  • Initialization

Gradle supports single and multi-project builds. During the initialization phase, Gradle determines which projects are going to take part in the build, and creates a Projectinstance for each of these projects.

  • Configuration

During this phase the project objects are configured. The build scripts of all projects which are part of the build are executed.

  • Execution

Gradle determines the subset of the tasks, created and configured during the configuration phase, to be executed. The subset is determined by the task name arguments passed to the gradle command and the current directory. Gradle then executes each of the selected tasks.

Initialization:Gradle支持单个和多项目构建,在该阶段将会确定哪些项目参与构建,并且为每个项目创建一个Project对象实例。在Android项目中为执行setting.gradle文件。如果一个项目中除app以外还有一个module,那么setting.gradle如下:

include ':app', ':jackieplugin'

Configuation:配置项目对象(也可称为解析build.gradle文件),所有项目的构建脚本将会被‘执行’,‘执行‘每个项目build.gradle文件然后才能知道所有task之间的依赖关系,但并不是task的所有内容都会被执行,如下图所示:

task one{

}

task goTestLifeCycle{
   dependsOn('one')
   println 'the lifecycle of gradle:我会在Configuration阶段执行'
   doFirst{
       println "doFirst:我会在Execution阶段执行"
   }
   doLast{
       println "doLast:我会在Execution阶段执行"
   }
}

可以看出,task闭包中的内容是在Configuration阶段执行的,但是doFirst,doLast则是在下个阶段执行(类似监听器)。对于 Android 项目来说即为执行各个 module 下的 build.gradle文件,这样各个 build.gradle 文件中的 task 的依赖关系就被确认下来了(例如 assembleDebug task 的执行依赖于其他 tasks 先执行,而这个依赖关系的确定就是在 Configuration 阶段)

Execution:task的执行阶段。首先执行doFirst{}闭包中的内容,然后是doLast{}闭包中的内容。

1.2 构建周期的运用

弄清楚了构建周期,那么它的作用在哪里呢.有如下作用:

1.2.1 hook Gradle构建过程

在命令输入 ./gradlew xxx 回车之后的执行过程,流程如下:

Gradle构建周期及其应用

Project 在 Configuration 阶段结束的 hook,前面也提到Configuration 阶段将会执行每一个 Project 的 build.gradle 文件,那么如何监听这每一个 Project 的引入后的点呢?通过 Project.afterEvaluate

task goTestLifeCycle{
   dependsOn('one')
   println 'the lifecycle of gradle:我会在Configuration阶段执行'
   doFirst{
       println "doFirst:我会在Execution阶段执行"
   }
   doLast{
       println "doLast:我会在Execution阶段执行"
   }
}
task TestHook{
   afterEvaluate {
       println 'hook afterEvaluate'
   }
}

通过执行 ./gradlew goTestLiftCycle,可以看到

xmly@xmlydeMacBook-Pro Test5 $ ./gradlew goTestLifeCycle

> Configure project :app
the lifecycle of gradle:我会在Configuration阶段执行
hook afterEvaluate

> Task :app:goTestLifeCycle
doFirst:我会在Execution阶段执行
doLast:我会在Execution阶段执行

在afterEvaluate阶段,当前Project内的task信息才能被掌握,如果想在assembleDebug task 前输出一段信息,那么在 app/build.gradle 中编写以下代码

task TestInsertHook{
   afterEvaluate {
       tasks.findByName("assembleDebug").doFirst {
           println 'jackie is on afterEvaluzte before assembleDebug.'
       }
   }
}

如果添加以下代码则出错:

 task TestErrorHook {
  tasks.findByName("assembleDebug").doFirst {
    println 'hook afterEvaluate from TestErrorHook'
  }
}

出错内容为:

Project evaluation failed including an error in afterEvaluate {}. Run with --stacktrace for details of the afterEvaluate {} error.


FAILURE: Build failed with an exception.

* Where:
Build file '/Users/bill/Desktop/androidDemo/Test/app/build.gradle' line: 56

* What went wrong:
A problem occurred evaluating project ':app'.
> Cannot invoke method doFirst() on null object

此时的appProject并没有获取全部的task信息,导致找不到assembleDebug。

应用:从上面的内容我们可以做到在app打包的某个流程(task)前插入一些操作

1.2.2 多渠道打包相关问题

我们都知道,Android中时常需要发布渠道包,需要将渠道信息附加到apk中,然后在程序启动的时候读取渠道信息。

动态指定一个渠道号(比如1001),那么构建的apk中,请在它的AndroidManifest.xml文件里面的application节点下面添加如下meta-data,请写一段Gradle脚本来自动完成:

<application android:allowBackup="false" android:supportsRtl="true">
<meta-data android:name=“channel" android:value=“1001" />
</application>

要求:当通过如下命令来构建渠道包的时候,将渠道号自动添加到apk的manifest中。./gradlew clean aR -P channel=1001PS:禁止使用manifestPlaceholders

解决思路

./gradlew clean aR -P channel=1001

首先解释一下这个命令的意思:

执行clean和assembleRelease(aR是缩写)这两个task, -P 指的是Property属性,

-P channel=1001 整体的意思是输入属性channel值为1001的属性值。

从上面的1.2.1中我们可以知道解决这个问题在核心在 project afterEvaluate以后,找到处理的那个task,同时如果是通过执行 ./gradlew clean assembleRelease -P channel=1001 来修改的,那么我们可以执行 ./gradlew assembleRelease来查看相关Task的执行代码:

:app:preBuild UP-TO-DATE
:app:preReleaseBuild UP-TO-DATE
:app:compileReleaseAidl UP-TO-DATE
:app:compileReleaseRenderscript UP-TO-DATE
:app:checkReleaseManifest UP-TO-DATE
:app:generateReleaseBuildConfig UP-TO-DATE
:app:prepareLintJar UP-TO-DATE
:app:mainApkListPersistenceRelease UP-TO-DATE
:app:generateReleaseResValues UP-TO-DATE
:app:generateReleaseResources UP-TO-DATE
:app:mergeReleaseResources UP-TO-DATE
:app:createReleaseCompatibleScreenManifests UP-TO-DATE
:app:processReleaseManifest UP-TO-DATE   //关键Task
:app:splitsDiscoveryTaskRelease UP-TO-DATE
:app:processReleaseResources UP-TO-DATE
:app:generateReleaseSources UP-TO-DATE
:app:javaPreCompileRelease UP-TO-DATE
:app:compileReleaseJavaWithJavac UP-TO-DATE
:app:compileReleaseNdk NO-SOURCE
:app:compileReleaseSources UP-TO-DATE
:app:lintVitalRelease
:app:mergeReleaseShaders UP-TO-DATE
:app:compileReleaseShaders UP-TO-DATE
:app:generateReleaseAssets UP-TO-DATE
:app:mergeReleaseAssets UP-TO-DATE
:app:transformClassesWithDexBuilderForRelease UP-TO-DATE
:app:transformDexArchiveWithExternalLibsDexMergerForRelease UP-TO-DATE
:app:transformDexArchiveWithDexMergerForRelease UP-TO-DATE
:app:mergeReleaseJniLibFolders UP-TO-DATE
:app:transformNativeLibsWithMergeJniLibsForRelease UP-TO-DATE
:app:processReleaseJavaRes NO-SOURCE
:app:transformResourcesWithMergeJavaResForRelease UP-TO-DATE
:app:packageRelease UP-TO-DATE
:app:assembleRelease

从里面的task processReleaseManifest 这个命名可以看到这个是处理manifest的那个task,然后再在它的doLast后面通过Groovy xml API来直接修改构建生成的xml文件

解决方案

在build.gradle中添加如下:

project.afterEvaluate {
   android.applicationVariants.each { ApplicationVariant variant ->
       String variantName = variant.name.capitalize()

       def mergeManifestTask = project.tasks.getByName("process${variantName}Manifest")
       mergeManifestTask.doLast{ mm ->
           String manifestPath = "$mm.manifestOutputDirectory/AndroidManifest.xml"
           def manifest = file(manifestPath).getText()
           if (project.hasProperty("channel")){
               def channelNo = project.property("channel")
               def xml = new XmlParser().parseText(manifest)
               xml.application[0].appendNode("meta-data",['android:name':'channel','android:value':channelNo])
               def serialize = XmlUtil.serialize(xml)
               file(manifestPath).write(serialize)
           }
       }
   }

}




原文始发于微信公众号(九局下半大逆转):Gradle构建周期及其应用