美文网首页
Android Plugin源码与Gradle构建(三)

Android Plugin源码与Gradle构建(三)

作者: AndroidHint | 来源:发表于2018-08-26 17:16 被阅读0次

    一、前言

    上一篇文章中我们讲到了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,这为后面自定义插件的创建打下了基础。

    相关文章

      网友评论

          本文标题:Android Plugin源码与Gradle构建(三)

          本文链接:https://www.haomeiwen.com/subject/ctaniftx.html