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

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

从Android Plugin源码开始彻底理解gradle构建:初识AndroidDSL(一)

从Android Plugin源码开始彻底理解gradle构建:Extension(二)

一、前言回顾

首先我们依然回顾一下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就是构建项目的基石,这里我就放出任玉刚大佬总结的主要构建方法:

常用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插件实现,顺便也可以测试一下我们的学习成果了~

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值