一、前言回顾
首先我们依然回顾一下basePlugin里的三个回调:
//plugin的基础设置、初始化工作
threadRecorder.record(
ExecutionType.BASE_PLUGIN_PROJECT_CONFIGURE,
project.getPath(),
null,
this::configureProject);
//EXTENSION的初始化工作
threadRecorder.record(
ExecutionType.BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION,
project.getPath(),
null,
this::configureExtension);
//plugin的task创建
threadRecorder.record(
ExecutionType.BASE_PLUGIN_PROJECT_TASKS_CREATION,
project.getPath(),
null,
this::createTasks);
上一篇文中我们已经详细介绍了第二步extension的用法和源码了,今天就来说说最后一步,也是gradle里最重要内容之一的Task。
二、task介绍
task,如其名:任务,gradle就是由一个一个任务来完成的。他其实也是一个类,有自己的属性,也可以”继承”,甚至他还有自己的生命周期。
他的定义方式有很多,下面我们来看一个最简单的实现:
task myTask {
println "myTask invoked!"
}
gradle就是一个一个task组成的,我们平时遇到莫名其妙的报错,最常用的就是clean(斜眼笑),其实也是一个task而已,包括我们debug运行、打包签名等等等等,都是Android studio给我们视图化了而已,本质也是执行task。所以我们执行一下clean命令:gradle clean
myTask invoked!还是被打出来了,为啥?其实上面我也提到了,task有自己的生命周期。
初始化—配置期—执行期,我从实战gradle里偷了一张图:
其实上面代码就是在配置阶段而已,配置阶段的代码只要在执行任何task都会跟着执行,如果我们希望不被执行的话,就只能放到执行阶段了,最直接的方法就是加到doLast、doFirst里,当然实现方式也挺多的,我就列两种吧:
project.task('printPerson') {
group 'atom'
//定义时
doLast {
println "this is doLast1"
}
}
Task printPerson= project.tasks["printPerson"]
//后来加
printPerson.doFirst {
println "this is doFirst1"
}
printPerson.doFirst {
println "this is doFirst2"
}
printPerson.doLast {
println "this is doLast2"
}
刚开始可能不好理解这种方式,其实可以理解为task里有一个队列,队列中是task将会执行的action,当doFirst 时,就会在队列头部插入一个action,而doLast则在队列尾部添加,当执行该任务时就会从队列中取出action依次执行,就如同我们上述代码,执行gradle printPerson时,打印结果如下:
> Task :app:printPerson
this is doFirst2
this is doFirst1
this is doLast1
this is doLast2
注意,此时必须要执行gradle printPerson时才会打印了,clean之流就没用了。
刚刚提到过,task其实也是一个类,没错,就如同object一样,task的基类是DefaultTask ,我们也可以自定义一个task,必须继承DefaultTask,如下:
class MyTask extends DefaultTask {
String message = 'This is MyTask'
// @TaskAction 表示该Task要执行的动作,即在调用该Task时,hello()方法将被执行
@TaskAction
def hello(){
println "Hello gradle. $message"
}
}
其实task还有许多内容,比如输入输出文件outputFile、Input
but,对于android开发目前来说,这就够了,但是了解一下也是很有好处的,比如我们构建速度就和输入输出有关,是不是被这个坑爹的构建速度郁闷到很多次~我推荐大家去看看《Android+Gradle权威指南》之类的书,目前网上资料不全不够系统,当然,官方文档还是值得好好看的~
闲话少叙,继续主题。task还有一个比较重要的内容,就是“继承”。
没错,task之间也是可以‘继承’的,不过此继承非彼继承,而是通过dependsOn关键字实现的,我们先来看看实现:
task task1 << {
println "this is task1"
}
task task2 << {
println "this is task2"
}
task task3 << {
println "this is task3"
}
task task4 << {
println "this is task4"
}
task1.dependsOn('task2')
task2.dependsOn('task3')
task1.dependsOn('task4')
‘继承’关系为:task1–>task2/task4和task2–>task3
我们先打印出来:
> Task :app:task3
this is task3
> Task :app:task2
this is task2
> Task :app:task4
this is task4
> Task :app:task1
this is task1
可以看到,task3是最先执行的,这是因为dependsOn的逻辑就是首先执行‘最高’辈分的,最后执行‘最低’辈分的。什么意思呢,拿代码来说就是task1‘继承’了task2,task4,而task2‘继承’了task3,意思就是task3是task1的爷爷辈,所以最先执行,这样相信大家能够理解了吧。
task基础部分大概就讲这么多了吧,接下来我们终于可以分析源码了。
三、Android的assemble源码
assemble是一个task,用于构建、打包项目,平时我们打包签名APK就是调用了该方法,由于我们有不同buildTypes,以及不同productFlavors,所以我们还需要生成各种不同的assemble系列方法:assemble{productFlavor}{BuildVariant},比如
assembleRelease:打所有的渠道Release包
assemblexiaomiRelease:打小米Release包
assemblehuaweiRelease:打华为Release包
AndroidDSL负责生成我们在build.gradle里配置的多渠道等各种assemble系列方法。
然后assemble方法会依赖很多方法,就如同我们上文所叙述的,依次执行assemble依赖的方法完成构建,好了,我们还是来看源码理解吧!
文章开头已经放出来源码,第三个注释就是Android的创建task部分,我们直接看该方法:
private void createTasks() {
//创建一些卸载APK、检查设备等方法
threadRecorder.record(
ExecutionType.TASK_MANAGER_CREATE_TASKS,
project.getPath(),
null,
() -> taskManager.createTasksBeforeEvaluate());
//创建Android相关重要方法
project.afterEvaluate(
project ->
threadRecorder.record(
ExecutionType.BASE_PLUGIN_CREATE_ANDROID_TASKS,
project.getPath(),
null,
() -> createAndroidTasks(false)));
}
其实就是调用了createTasksBeforeEvaluate和createAndroidTasks两个方法,注释写的很明白了,createAndroidTasks才是重点,该方法中又调用了variantManager的createAndroidTasks方法,跳过与本文无关的细节,看下面重要的地方:
/**
* Variant/Task creation entry point.
*/
public void createAndroidTasks() {
//省略部分代码...
for (final VariantScope variantScope : variantScopes) {
recorder.record(
ExecutionType.VARIANT_MANAGER_CREATE_TASKS_FOR_VARIANT,
project.getPath(),
variantScope.getFullVariantName(),
() -> createTasksForVariantData(variantScope));
}
}
循环调用createTasksForVariantData方法,该方法就是为所有的渠道创建相关方法了,而variantScopes则存放了各种渠道、buildType信息,继续查看该方法:
/** Create tasks for the specified variant. */
public void createTasksForVariantData(final VariantScope variantScope) {
//1======
final BaseVariantData variantData = variantScope.getVariantData();
final VariantType variantType = variantData.getType();
final GradleVariantConfiguration variantConfig = variantScope.getVariantConfiguration();
final BuildTypeData buildTypeData = buildTypes.get(variantConfig.getBuildType().getName());
if (buildTypeData.getAssembleTask() == null) {
//2======
buildTypeData.setAssembleTask(taskManager.createAssembleTask(buildTypeData));
}
// Add dependency of assemble task on assemble build type task.
//3======
taskManager
.getTaskFactory()
.configure(
"assemble",
task -> {
assert buildTypeData.getAssembleTask() != null;
task.dependsOn(buildTypeData.getAssembleTask().getName());
});
//4======
createAssembleTaskForVariantData(variantData);
if (variantType.isForTesting()) {
//省略测试相关代码...
} else {
//5======
taskManager.createTasksForVariantScope(variantScope);
}
}
1、解析variant渠道等信息
2、创建AssembleTask存入data里
3、给assemble添加依赖
4、创建该variant的专属AssembleTask
5、给AssembleTask添加构建项目所需task依赖(dependsOn)
看一下4、5步骤详细代码,首先是第四步,给每个渠道和buildtype创建对应的方法:
/** Create assemble task for VariantData. */
private void createAssembleTaskForVariantData(final BaseVariantData variantData) {
final VariantScope variantScope = variantData.getScope();
if (variantData.getType().isForTesting()) {
//测试
} else {
BuildTypeData buildTypeData =
buildTypes.get(variantData.getVariantConfiguration().getBuildType().getName());
Preconditions.checkNotNull(buildTypeData.getAssembleTask());
if (productFlavors.isEmpty()) {
//如果没有设置渠道
} else {
//省略部分代码...
// assembleTask for this flavor(dimension), created on demand if needed.
if (variantConfig.getProductFlavors().size() > 1) {
//获取渠道名
final String name = StringHelper.capitalize(variantConfig.getFlavorName());
final String variantAssembleTaskName =
//组装名字
StringHelper.appendCapitalized("assemble", name);
if (!taskManager.getTaskFactory().containsKey(variantAssembleTaskName)) {
//创建相应渠道方法
Task task = taskManager.getTaskFactory().create(variantAssembleTaskName);
task.setDescription("Assembles all builds for flavor combination: " + name);
task.setGroup("Build");
//渠道方法依赖AssembleTask
task.dependsOn(variantScope.getAssembleTask().getName());
}
taskManager
.getTaskFactory()
.configure(
"assemble", task1 -> task1.dependsOn(variantAssembleTaskName));
}
}
}
}
注释已经很清晰了,最重要的就是组装名字,创建相应的渠道打包方法。这里我们又学到一种定义task的方式:TaskFactory.create
这是AndroidDSL自定义的类,他的实现类是TaskFactoryImpl,由kotlin语言实现:
class TaskFactoryImpl(private val taskContainer: TaskContainer): TaskFactory {
//....
override fun configure(name: String, configAction: Action<in Task>) {
val task = taskContainer.getByName(name)
configAction.execute(task)
}
override fun findByName(name: String): Task? {
return taskContainer.findByName(name)
}
override fun <T : Task> create(
taskName: String, taskClass: Class<T>, configAction: Action<T>): T {
return taskContainer.create(taskName, taskClass, configAction)
}
}
省略了大部分方法,但也很简单了,使用代理模式代理了taskContainer,而这个taskContainer就是gradle的类了,查看官方文档:
<T extends Task> T create(String name,
Class<T> type,
Action<? super T> configuration)
throws InvalidUserDataException
Creates a Task with the given name and type, configures it with the given action, and adds it to this container.
After the task is added, it is made available as a property of the project, so that you can reference the task by name in your build file. See here for more details.
Specified by:
create in interface PolymorphicDomainObjectContainer<Task>
Type Parameters:
T - the type of the domain object to be created
Parameters:
name - The name of the task to be created.
type - The type of task to create.
configuration - The action to configure the task with.
Returns:
The newly created task object.
Throws:
InvalidUserDataException - If a task with the given name already exists in this project.
就是创建一个task并放入容器里
参数只有第三个比较难猜一点点,看了文档也就很清楚:给task设置一个action而已。当然,这里并没有调用这个重载方法,不过我这里是为了第5步介绍,好的,让我们回到第5步操作:
taskManager.createTasksForVariantScope(variantScope);
这里taskManager由BasePlugin的子类实现,实现类为ApplicationTaskManager,我们看一下他的createTasksForVariantScope方法:
@Override
public void createTasksForVariantScope(@NonNull final VariantScope variantScope) {
BaseVariantData variantData = variantScope.getVariantData();
assert variantData instanceof ApplicationVariantData;
createAnchorTasks(variantScope);
createCheckManifestTask(variantScope);
handleMicroApp(variantScope);
// Create all current streams (dependencies mostly at this point)
createDependencyStreams(variantScope);
// Add a task to publish the applicationId.
createApplicationIdWriterTask(variantScope);
taskFactory.create(new MainApkListPersistence.ConfigAction(variantScope));
taskFactory.create(new BuildArtifactReportTask.ConfigAction(variantScope));
// Add a task to process the manifest(s)
recorder.record(
ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_MANIFEST_TASK,
project.getPath(),
variantScope.getFullVariantName(),
() -> createMergeApkManifestsTask(variantScope));
// Add a task to create the res values
recorder.record(
ExecutionType.APP_TASK_MANAGER_CREATE_GENERATE_RES_VALUES_TASK,
project.getPath(),
variantScope.getFullVariantName(),
() -> createGenerateResValuesTask(variantScope));
// Add a task to merge the resource folders
recorder.record(
ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_RESOURCES_TASK,
project.getPath(),
variantScope.getFullVariantName(),
(Recorder.VoidBlock) () -> createMergeResourcesTask(variantScope, true));
//省略类似方法
}
这个方法就是构建精髓所在,他创建了我们构建项目所需要的大部分task,比如创建manifest文件,合并manifest文件,处理resource文件…等等task,这些task就是构建项目的基石,这里我就放出任玉刚大佬总结的主要构建方法:
具体每个方法做了什么,就是需要大家阅读源码参透了,这里我只负责梳理大致流程,嘿嘿…
下面我们就看看创建的第一个方法createAnchorTasks,在这个方法里面调用了createCompileAnchorTask,他的实现是:
private void createCompileAnchorTask(@NonNull final VariantScope scope) {
final BaseVariantData variantData = scope.getVariantData();
scope.setCompileTask(
taskFactory.create(
new TaskConfigAction<Task>() {
@NonNull
@Override
public String getName() {
return scope.getTaskName("compile", "Sources");
}
@NonNull
@Override
public Class<Task> getType() {
return Task.class;
}
@Override
public void execute(@NonNull Task task) {
variantData.compileTask = task;
variantData.compileTask.setGroup(BUILD_GROUP);
}
}));
scope.getAssembleTask().dependsOn(scope.getCompileTask());
}
为什么我要专门说一下这个task,就是因为最后一句代码,AssembleTask依赖的该task,也就是说当我们执行AssembleTask的时候,该task会提前执行,而构建原理也在于此,该task也会依赖其他task,就这样一层层依赖,构建时就会调用所有的相关task,这样就完成了我们Android项目的构建。
四、结语
好了,终于梳理完成整个过程了,其实结合源码看文章,整个过程还是比较清晰的,不像Android源码那样晦涩难懂,主要就是task的理解。
到这里相信gradle再大家眼里也不那么神秘了,也有一定自己的理解了,接下来大家可以自行阅读源码,梳理清晰构建的主要task都做了什么,彻底掌握Android构建,这样就可以为所欲为了哈哈哈哈,我就不再出相应文章了,是时候实战一波了,下一篇文章将给大家带来一篇比较实用的gradle插件实现,顺便也可以测试一下我们的学习成果了~