Android Plugin源码与Gradle构建(三)

1,078 阅读7分钟

一、前言

上一篇文章中我们讲到了Android Plugin中的apply方法中的回调方法configureExtension,详见Android Plugin源码与Gradle构建(二)。今天我们继续分析Android Plugin的源码,主要分析最后一个回调方法createTasks的逻辑。

二、初识Task

task就是一个任务,gradle构建都是由一个个任务组成的。我们可以这样定义一个task:

task myTask {
  println "this is a task"
}

task有自己的属性,也有自己的生命周期,它的生命周期分为三个阶段,分别为初始化阶段、配置阶段和执行阶段,如下图所示(图片来源:掌握构建生命周期):

上面我们定义的myTask任务只是在配置阶段,配置阶段的代码只要执行任何task,它都会跟着执行。所以,我们执行一下gradle clean任务,this is a task也被打印出来了。 如果我们希望定义的任务只能在执行阶段才执行的话,那么我们可以将实现逻辑放到doFirst、doLast中。例如看下面一个例子:

task myTask {
    doLast {
       println 'myTask do Last1'
    }
}

myTask.doFirst {
    println 'myTask do First2'
}

myTask.doFirst {
    println 'myTask do First1'
}

myTask.doLast {
    println 'myTask do Last2'
}

当我们执行gradle myTask时,就会打印如下内容:

> Task :app:myTask
myTask do First1
myTask do First2
myTask do Last1
myTask do Last2

我们可以理解为task中包含一个队列,队列中包含的是要执行的Action,当使用doFirst时,即往队列首加入了一个Action;当使用doLast时,即往队列尾加入了一个Action。所以myTask使用了两次doFirst时,最后一次被加入了队列首;myTask使用了两次doLast时,最后一次被加入了队列尾。

task不仅仅有生命周期的概念,同时也具有“继承”的概念。这个“继承”是通过关键字dependsOn来实现的,它和类的继承不是一回事,我们先看下面的这个例子:

task task1 << {
    println 'task1'
}

task task2 << {
    println 'task2'
}

task task3 << {
    println 'task3'
}

task task4 << {
    println 'task4'
}

task1.dependsOn('task2')
task2.dependsOn('task3')
task1.dependsOn('task4')

我们定义了四个task,并且确定了“继承”关系:task1->task2/task4->task3。 执行gradle task1,打印的内容是:

> Task :app:task3
task3

> Task :app:task2
task2

> Task :app:task4
task4

> Task :app:task1
task1

可以看到是task3先执行,然后是task2,接着是task4,最后才是task1。这是因为dependensOn的逻辑是执行“高”辈分的,然后再执行“低”辈分的。 由于“继承”关系是task1->task2/task4->task3。所以task3是最高级的,task2task4平级,task1是最低级的。所以会先执行task3,然后执行task3的上一级,即task2,然后执行和task3没有联系的task4,最后才执行task1

task的基础知识先介绍到这里,由于本文篇幅重点在于介绍Android Plugin源码的Task任务,如果对task感兴趣可以去看一下《Android+Gradle权威指南》或者官方文档。

三、Android插件的Task

上一篇文章我们说到了createExtension回调方法,现在我们来看createTasks的回调方法:

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

主要是调用了两个方法,一个是taskManager. createTasksBeforeEvaluate,一个是createAndroidTasks,其中和Android相关的Task的创建都在createAndroidTasks方法中。

@VisibleForTesting
final void createAndroidTasks(boolean force) {
    // Make sure unit tests set the required fields.
    checkState(extension.getBuildToolsRevision() != null,
            "buildToolsVersion is not specified.");
    checkState(extension.getCompileSdkVersion() != null, "compileSdkVersion is not specified.");

    ndkHandler.setCompileSdkVersion(extension.getCompileSdkVersion());

    // get current plugins and look for the default Java plugin.
    if (project.getPlugins().hasPlugin(JavaPlugin.class)) {
        throw new BadPluginException(
                "The 'java' plugin has been applied, but it is not compatible with the Android plugins.");
    }

    boolean targetSetupSuccess = ensureTargetSetup();
    sdkHandler.ensurePlatformToolsIsInstalledWarnOnFailure(
            extraModelInfo.getSyncIssueHandler());
    // Stop trying to configure the project if the SDK is not ready.
    // Sync issues will already have been collected at this point in sync.
    if (!targetSetupSuccess) {
        project.getLogger()
                .warn("Aborting configuration as SDK is missing components in sync mode.");
        return;
    }

    // don't do anything if the project was not initialized.
    // Unless TEST_SDK_DIR is set in which case this is unit tests and we don't return.
    // This is because project don't get evaluated in the unit test setup.
    // See AppPluginDslTest
    if (!force
            && (!project.getState().getExecuted() || project.getState().getFailure() != null)
            && SdkHandler.sTestSdkFolder == null) {
        return;
    }

    if (hasCreatedTasks) {
        return;
    }
    hasCreatedTasks = true;

    extension.disableWrite();

    taskManager.configureCustomLintChecks();

    ProcessProfileWriter.getProject(project.getPath())
            .setCompileSdk(extension.getCompileSdkVersion())
            .setBuildToolsVersion(extension.getBuildToolsRevision().toString())
            .setSplits(AnalyticsUtil.toProto(extension.getSplits()));

    String kotlinPluginVersion = getKotlinPluginVersion();
    if (kotlinPluginVersion != null) {
        ProcessProfileWriter.getProject(project.getPath())
                .setKotlinPluginVersion(kotlinPluginVersion);
    }

    // setup SDK repositories.
    sdkHandler.addLocalRepositories(project);

    threadRecorder.record(
            ExecutionType.VARIANT_MANAGER_CREATE_ANDROID_TASKS,
            project.getPath(),
            null,
            () -> {
                variantManager.createAndroidTasks();
                ApiObjectFactory apiObjectFactory =
                        new ApiObjectFactory(
                                androidBuilder,
                                extension,
                                variantFactory,
                                project.getObjects());
                for (VariantScope variantScope : variantManager.getVariantScopes()) {
                    BaseVariantData variantData = variantScope.getVariantData();
                    apiObjectFactory.create(variantData);
                }

                // Make sure no SourceSets were added through the DSL without being properly configured
                sourceSetManager.checkForUnconfiguredSourceSets();

                // must run this after scopes are created so that we can configure kotlin
                // kapt tasks
                taskManager.addDataBindingDependenciesIfNecessary(
                        extension.getDataBinding(), variantManager.getVariantScopes());
            });

    // create the global lint task that depends on all the variants
    taskManager.configureGlobalLintTask(variantManager.getVariantScopes());

    // Create and read external native build JSON files depending on what's happening right
    // now.
    //
    // CREATE PHASE:
    // Creates JSONs by shelling out to external build system when:
    //   - Any one of AndroidProject.PROPERTY_INVOKED_FROM_IDE,
    //      AndroidProject.PROPERTY_BUILD_MODEL_ONLY_ADVANCED,
    //      AndroidProject.PROPERTY_BUILD_MODEL_ONLY,
    //      AndroidProject.PROPERTY_REFRESH_EXTERNAL_NATIVE_MODEL are set.
    //   - *and* AndroidProject.PROPERTY_REFRESH_EXTERNAL_NATIVE_MODEL is set
    //      or JSON files don't exist or are out-of-date.
    // Create phase may cause ProcessException (from cmake.exe for example)
    //
    // READ PHASE:
    // Reads and deserializes JSONs when:
    //   - Any one of AndroidProject.PROPERTY_INVOKED_FROM_IDE,
    //      AndroidProject.PROPERTY_BUILD_MODEL_ONLY_ADVANCED,
    //      AndroidProject.PROPERTY_BUILD_MODEL_ONLY,
    //      AndroidProject.PROPERTY_REFRESH_EXTERNAL_NATIVE_MODEL are set.
    // Read phase may produce IOException if the file can't be read for standard IO reasons.
    // Read phase may produce JsonSyntaxException in the case that the content of the file is
    // corrupt.
    boolean forceRegeneration =
            projectOptions.get(BooleanOption.IDE_REFRESH_EXTERNAL_NATIVE_MODEL);

    checkSplitConfiguration();
    if (ExternalNativeBuildTaskUtils.shouldRegenerateOutOfDateJsons(projectOptions)) {
        threadRecorder.record(
                ExecutionType.VARIANT_MANAGER_EXTERNAL_NATIVE_CONFIG_VALUES,
                project.getPath(),
                null,
                () -> {
                    for (VariantScope variantScope : variantManager.getVariantScopes()) {
                        ExternalNativeJsonGenerator generator =
                                variantScope.getExternalNativeJsonGenerator();
                        if (generator != null) {
                            // This will generate any out-of-date or non-existent JSONs.
                            // When refreshExternalNativeModel() is true it will also
                            // force update all JSONs.
                            generator.build(forceRegeneration);
                        }
                    }
                });
    }
    BuildableArtifactImpl.Companion.enableResolution();
}

代码有点长,我们挑重点的看,其实上面的代码最主要是执行了回调方法:

variantManager.createAndroidTasks();
ApiObjectFactory apiObjectFactory =
        new ApiObjectFactory(
                androidBuilder,
                extension,
                variantFactory,
                project.getObjects());
for (VariantScope variantScope : variantManager.getVariantScopes()) {
    BaseVariantData variantData = variantScope.getVariantData();
    apiObjectFactory.create(variantData);
}

// Make sure no SourceSets were added through the DSL without being properly configured
sourceSetManager.checkForUnconfiguredSourceSets();

// must run this after scopes are created so that we can configure kotlin
// kapt tasks
taskManager.addDataBindingDependenciesIfNecessary(
        extension.getDataBinding(), variantManager.getVariantScopes());

其中执行了

public void createAndroidTasks() {
    variantFactory.validateModel(this);
    variantFactory.preVariantWork(project);
    //当variantScopes为空,则创建variantScopes
    if (variantScopes.isEmpty()) {
        recorder.record(
                ExecutionType.VARIANT_MANAGER_CREATE_VARIANTS,
                project.getPath(),
                null /*variantName*/,
                this::populateVariantDataList);
    }

    // Create top level test tasks.
    recorder.record(
            ExecutionType.VARIANT_MANAGER_CREATE_TESTS_TASKS,
            project.getPath(),
            null /*variantName*/,
            () -> taskManager.createTopLevelTestTasks(!productFlavors.isEmpty()));


    //为所有定义的渠道创建相关的构建任务
    for (final VariantScope variantScope : variantScopes) {
        recorder.record(
                ExecutionType.VARIANT_MANAGER_CREATE_TASKS_FOR_VARIANT,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> createTasksForVariantData(variantScope));
    }

    taskManager.createReportTasks(variantScopes);
}

我们直接看为所有的定义的渠道创建相关的构建任务的逻辑,即调用了variantScopesforeach方法,对于每一个渠道,相应的执行了createTasksForVariantData方法:

public void createTasksForVariantData(final VariantScope variantScope) {
    //1、解析variant渠道等信息
    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、创建构建类型(有可能是自定义的buildType)的assembleTask
        buildTypeData.setAssembleTask(taskManager.createAssembleTask(buildTypeData));
    }

    // 3、给assemble添加依赖,即assemble任务需要依赖于上面创建的构建类型的assembleTask
    taskManager
            .getTaskFactory()
            .configure(
                    "assemble",
                    task -> {
                        assert buildTypeData.getAssembleTask() != null;
                        task.dependsOn(buildTypeData.getAssembleTask().getName());
                    });
    //4、创建该variant专属的assembleTask
    createAssembleTaskForVariantData(variantData);
    if (variantType.isForTesting()) {
        //省略...
    } else {
        //5、给assembleTask添加构建项目所需task依赖
        taskManager.createTasksForVariantScope(variantScope);
    }
}

createTasksForVariantData方法主要执行了以下逻辑: 1、解析variant渠道等信息 2、创建构建类型(在buildTypes标签下定义的构建类型)的assembleTask 3、给assemble添加依赖 4、创建该variant专属的assembleTask 5、给assembleTask添加构建项目所需task依赖

首先看一下第四步的详细逻辑:

private void createAssembleTaskForVariantData(final BaseVariantData variantData) {
    final VariantScope variantScope = variantData.getScope();
    if (variantData.getType().isForTesting()) {
        variantScope.setAssembleTask(taskManager.createAssembleTask(variantData));
    } 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");
                    task.dependsOn(variantScope.getAssembleTask().getName());
                }
                //assemble依赖该渠道任务
                taskManager
                        .getTaskFactory()
                        .configure(
                                "assemble", task1 -> task1.dependsOn(variantAssembleTaskName));
            }
        }
    }
}

这里主要组装了任务的名字,然后创建了相应的渠道任务。该渠道任务现在已经创建出来了,但是当我们执行该渠道任务时,如果最终能构建出一个apk,则必须需要依赖其他一些task,下面我们讲到第五步:

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));

    省略类似方法...
}

在这个方法中创建了大量构建时的task,例如创建Manifest文件,合并Manifest文件、处理resource文件等task。所以当我们执行了assemble任务时,其实上面创建的task也一并被执行,所有这些task构成了项目的一次构建。

四、总结

Android Plugin的源码分析到这里暂告一段落了。从Android Plugin源码中我们知道了Android项目的整个构建过程,并且了解了如何创建Extension和Task,这为后面自定义插件的创建打下了基础。