从Android Plugin源码开始完全理解gradle构建:Task(三)

*本篇文章已受权微信公众号 guolin_blog (郭霖)独家发布
系列文章: java

从Android Plugin源码开始完全理解gradle构建:初识AndroidDSL(一)android

从Android Plugin源码开始完全理解gradle构建:Extension(二)web

1、前言回顾

首先咱们依然回顾一下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。api

2、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里,固然实现方式也挺多的,我就列两种吧:app

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时,打印结果以下:dom

> Task :app:printPerson 
this is doFirst2
this is doFirst1
this is doLast1
this is doLast2

注意,此时必需要执行gradle printPerson时才会打印了,clean之流就没用了。ide

刚刚提到过,task其实也是一个类,没错,就如同object同样,task的基类是DefaultTask ,咱们也能够自定义一个task,必须继承DefaultTask,以下:svg

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基础部分大概就讲这么多了吧,接下来咱们终于能够分析源码了。

3、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);
        }
    }

一、解析variant渠道等信息
二、建立AssembleTask存入data里
三、给assemble添加依赖
四、建立该variant的专属AssembleTask
五、给AssembleTask添加构建项目所需task依赖(dependsOn)

看一下四、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就是构建项目的基石,这里我就放出任玉刚大佬总结的主要构建方法:

经常使用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项目的构建。

4、结语

好了,终于梳理完成整个过程了,其实结合源码看文章,整个过程仍是比较清晰的,不像Android源码那样晦涩难懂,主要就是task的理解。 到这里相信gradle再你们眼里也不那么神秘了,也有必定本身的理解了,接下来你们能够自行阅读源码,梳理清晰构建的主要task都作了什么,完全掌握Android构建,这样就能够随心所欲了哈哈哈哈,我就再也不出相应文章了,是时候实战一波了,下一篇文章将给你们带来一篇比较实用的gradle插件实现,顺便也能够测试一下咱们的学习成果了~