“This is the 17th day of my participation in the First Challenge 2022.

The introduction of the source code

First of all, how to view gradle source, we rely on in the project com. Android. View the build: gradle can, as follows:

compile gradleApi()
compile 'com. Android. Tools. Build: gradle: 2.3.3'
Copy the code

Sync Gradle to see the source code

Analysis of the

Apk is a gradle plugin application

apply plugin: 'com.android.application'
Copy the code

So we find under the source of the gradle AppPligin, some of its source code is as follows:

public class AppPlugin extends BasePlugin implements Plugin<Project> {
    @Inject
    public AppPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
        super(instantiator, registry); }...@NonNull
    @Override
    protected TaskManager createTaskManager(
            @NonNull Project project,
            @NonNull AndroidBuilder androidBuilder,
            @NonNull DataBindingBuilder dataBindingBuilder,
            @NonNull AndroidConfig androidConfig,
            @NonNull SdkHandler sdkHandler,
            @NonNull NdkHandler ndkHandler,
            @NonNull DependencyManager dependencyManager,
            @NonNull ToolingModelBuilderRegistry toolingRegistry,
            @NonNull Recorder recorder) {
        return new ApplicationTaskManager(
                project,
                androidBuilder,
                dataBindingBuilder,
                androidConfig,
                sdkHandler,
                ndkHandler,
                dependencyManager,
                toolingRegistry,
                recorder);
    }

    @Override
    public void apply(@NonNull Project project) {
        super.apply(project); }... }Copy the code

The createTaskManager function returns an ApplicationTaskManager object. The source code for this class is as follows:

public class ApplicationTaskManager extends TaskManager {...@Override
    public void createTasksForVariantData(
            @NonNull final TaskFactory tasks,
            @NonNull final BaseVariantData<? extends BaseVariantOutputData> variantData) {
        assert variantData instanceof ApplicationVariantData;

        final VariantScope variantScope = variantData.getScope();

        createAnchorTasks(tasks, variantScope);
        createCheckManifestTask(tasks, variantScope);

        handleMicroApp(tasks, variantScope);

        // Create all current streams (dependencies mostly at this point)
        createDependencyStreams(tasks, variantScope);

        // Add a task to process the manifest(s)
        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_MANIFEST_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> createMergeAppManifestsTask(tasks, 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(tasks, variantScope));

        // Add a task to compile renderscript files.
        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_CREATE_RENDERSCRIPT_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> createRenderscriptTask(tasks, 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(tasks, variantScope));

        // Add a task to merge the asset folders
        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_ASSETS_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> createMergeAssetsTask(tasks, variantScope));

        // Add a task to create the BuildConfig class
        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_BUILD_CONFIG_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> createBuildConfigTask(tasks, variantScope));

        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_PROCESS_RES_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> {
                    // Add a task to process the Android Resources and generate source files
                    createApkProcessResTask(tasks, variantScope);

                    // Add a task to process the java resources
                    createProcessJavaResTasks(tasks, variantScope);
                });

        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_AIDL_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> createAidlTask(tasks, variantScope));

        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_SHADER_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> createShaderTask(tasks, variantScope));

        // Add NDK tasks
        if(! isComponentModelPlugin) { recorder.record( ExecutionType.APP_TASK_MANAGER_CREATE_NDK_TASK, project.getPath(), variantScope.getFullVariantName(), () -> createNdkTasks(tasks, variantScope)); }else {
            if(variantData.compileTask ! =null) {
                variantData.compileTask.dependsOn(getNdkBuildable(variantData));
            } else {
                variantScope.getCompileTask().dependsOn(tasks, getNdkBuildable(variantData));
            }
        }
        variantScope.setNdkBuildable(getNdkBuildable(variantData));

        // Add external native build tasks

        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_EXTERNAL_NATIVE_BUILD_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> {
                    createExternalNativeBuildJsonGenerators(variantScope);
                    createExternalNativeBuildTasks(tasks, variantScope);
                });

        // Add a task to merge the jni libs folders
        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_JNILIBS_FOLDERS_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> createMergeJniLibFoldersTasks(tasks, variantScope));

        // Add a compile task
        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_COMPILE_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> {
                    CoreJackOptions jackOptions =
                            variantData.getVariantConfiguration().getJackOptions();
                    // create data binding merge task before the javac task so that it can
                    // parse jars before any consumer
                    createDataBindingMergeArtifactsTaskIfNecessary(tasks, variantScope);
                    AndroidTask<? extends JavaCompile> javacTask =
                            createJavacTask(tasks, variantScope);
                    if (jackOptions.isEnabled()) {
                        AndroidTask<TransformTask> jackTask =
                                createJackTask(tasks, variantScope, true /*compileJavaSource*/);
                        setJavaCompilerTask(jackTask, tasks, variantScope);
                    } else {
                        // Prevent the use of java 1.8 without jack, which would otherwise cause an
                        // internal javac error.
                        if (variantScope
                                .getGlobalScope()
                                .getExtension()
                                .getCompileOptions()
                                .getTargetCompatibility()
                                .isJava8Compatible()) {
                            // Only warn for users of retrolambda and dexguard
                            if (project.getPlugins().hasPlugin("me.tatarka.retrolambda")
                                    || project.getPlugins().hasPlugin("dexguard")) {
                                getLogger()
                                        .warn(
                                                "Jack is disabled, but one of the plugins you "
                                                        + "are using supports Java 8 language "
                                                        + "features.");
                            } else {
                                androidBuilder
                                        .getErrorReporter()
                                        .handleSyncError(
                                                variantScope
                                                        .getVariantConfiguration()
                                                        .getFullName(),
                                                SyncIssue
                                                        .TYPE_JACK_REQUIRED_FOR_JAVA_8_LANGUAGE_FEATURES,
                                                "Jack is required to support java 8 language "
                                                        + "features. Either enable Jack or remove "
                                                        + "sourceCompatibility "
                                                        + "JavaVersion.VERSION_1_8.");
                            }
                        }
                        addJavacClassesStream(variantScope);
                        setJavaCompilerTask(javacTask, tasks, variantScope);
                        getAndroidTasks()
                                .create(
                                        tasks,
                                        newAndroidJarTask.JarClassesConfigAction(variantScope)); createPostCompilationTasks(tasks, variantScope); }});// Add data binding tasks if enabled
        createDataBindingTasksIfNecessary(tasks, variantScope);

        createStripNativeLibraryTask(tasks, variantScope);

        if (variantData
                .getSplitHandlingPolicy()
                .equals(SplitHandlingPolicy.RELEASE_21_AND_AFTER_POLICY)) {
            if (getExtension().getBuildToolsRevision().getMajor() < 21) {
                throw new RuntimeException(
                        "Pure splits can only be used with buildtools 21 and later");
            }

            recorder.record(
                    ExecutionType.APP_TASK_MANAGER_CREATE_SPLIT_TASK,
                    project.getPath(),
                    variantScope.getFullVariantName(),
                    () -> createSplitTasks(tasks, variantScope));
        }

        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_PACKAGING_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> {
                    @Nullable
                    AndroidTask<BuildInfoWriterTask> fullBuildInfoGeneratorTask =
                            createInstantRunPackagingTasks(tasks, variantScope);
                    createPackagingTask(
                            tasks, variantScope, true /*publishApk*/, fullBuildInfoGeneratorTask);
                });

        // create the lint tasks.recorder.record( ExecutionType.APP_TASK_MANAGER_CREATE_LINT_TASK, project.getPath(), variantScope.getFullVariantName(), () -> createLintTasks(tasks, variantScope)); }... }Copy the code

In its createTasksForVariantData function, we can see the whole compilation process packaging all task (do not include additional), here is actually the process of the entire package, are as follows:

  • MERGE_MANIFEST
  • GENERATE_RES_VALUES
  • CREATE_RENDERSCRIPT
  • MERGE_RESOURCES
  • MERGE_ASSETS
  • BUILD_CONFIG
  • PROCESS_RES
  • AIDL
  • SHADER
  • NDK
  • EXTERNAL_NATIVE_BUILD
  • MERGE_JNILIBS_FOLDERS
  • COMPILE
  • SPLIT (this is a subcontract, only over 21 system will execute)
  • PACKAGING
  • LINT

PROCESS_RES (PROCESS_RES, PROCESS_RES, PROCESS_RES) :

recorder.record(
        ExecutionType.APP_TASK_MANAGER_CREATE_PROCESS_RES_TASK,
        project.getPath(),
        variantScope.getFullVariantName(),
        () -> {
            // Add a task to process the Android Resources and generate source files
            createApkProcessResTask(tasks, variantScope);

            // Add a task to process the java resources
            createProcessJavaResTasks(tasks, variantScope);
        });
Copy the code

The createApkProcessResTask function executes two small tasks. The createApkProcessResTask function is a function of ApplicationTaskManager’s parent TaskManager class.

public void createApkProcessResTask(
        @NonNull TaskFactory tasks,
        @NonNull VariantScope scope) {
    createProcessResTask(
            tasks,
            scope,
            new File(globalScope.getIntermediatesDir(),
                    "symbols/" + scope.getVariantData().getVariantConfiguration().getDirName()),
            true);
}

public void createProcessResTask(
        @NonNull TaskFactory tasks,
        @NonNull VariantScope scope,
        @Nullable File symbolLocation,
        boolean generateResourcePackage) {...// loop on all outputs. The only difference will be the name of the task, and location
    // of the generated data.
    for (BaseVariantOutputData vod : variantData.getOutputs()) {
        final VariantOutputScope variantOutputScope = vod.getScope();

        variantOutputScope.setProcessResourcesTask(androidTasks.create(tasks,
                new ProcessAndroidResources.ConfigAction(variantOutputScope, symbolLocation,
                        generateResourcePackage,
                        useAaptToGenerateLegacyMultidexMainDexProguardRules)));

        // always depend on merge res,
        variantOutputScope.getProcessResourcesTask().dependsOn(tasks,
                scope.getMergeResourcesTask());
        if(scope.getDataBindingProcessLayoutsTask() ! =null) {
            variantOutputScope.getProcessResourcesTask().dependsOn(tasks,
                    scope.getDataBindingProcessLayoutsTask().getName());
        }
        variantOutputScope
                .getProcessResourcesTask()
                .dependsOn(tasks, variantOutputScope.getManifestProcessorTask());

        if (vod.getMainOutputFile().getFilter(DENSITY) == null) { scope.setGenerateRClassTask(variantOutputScope.getProcessResourcesTask()); scope.getSourceGenTask().optionalDependsOn( tasks, variantOutputScope.getProcessResourcesTask()); }}}Copy the code

We start with ConfigAction, a subclass of ProcessAndroidResources.

The following code defines the dependency rules for this task, for example

variantOutputScope.getProcessResourcesTask().dependsOn(tasks,
                scope.getMergeResourcesTask());
Copy the code

You must rely on the MergeResources task to execute before it can be executed.

Let’s go back to ProcessAndroidResources, which subclass ConfigAction.

public static class ConfigAction implements TaskConfigAction<ProcessAndroidResources> {...@Override
    public void execute(@NonNull ProcessAndroidResources processResources) {...if (variantOutputData.getMainOutputFile()
                .getFilter(OutputFile.DENSITY) == null
                && variantData.generateRClassTask == null) {... processResources .setSourceOutputDir(scope.getVariantScope().getRClassSourceOutputDir()); processResources.setTextSymbolOutputDir(symbolLocation);if (config.getBuildType().isMinifyEnabled()) {
                if (config.getBuildType().isShrinkResources() && config.getJackOptions().isEnabled()) {
                    LoggingUtil.displayWarning(Logging.getLogger(getClass()),
                            scope.getGlobalScope().getProject(),
                            "shrinkResources does not yet work with useJack=true"); } processResources.setProguardOutputFile( scope.getVariantScope().getProcessAndroidResourcesProguardOutputFile()); }... } ConventionMappingHelper.map(processResources,"manifestFile".new Callable<File>() {
            @Override
            public File call(a) throws Exception {
                returnvariantOutputData.manifestProcessorTask.getOutputFile(); }}); . }}Copy the code

In its execute function, you can see that some information is set, such as the output path of various files. Here we use SourceOutputDir as an example:

processResources.setSourceOutputDir(scope.getVariantScope().getRClassSourceOutputDir());
Copy the code

The getRClassSourceOutputDir function is an abstract method of the abstract class VariantScope. It is implemented in VariantScope impl with the following code:

@Override
@NonNull
public File getRClassSourceOutputDir(a) {
    return new File(globalScope.getGeneratedDir(),
            "source/r/" + getVariantConfiguration().getDirName());
}
Copy the code

Among them

  • GlobalScope. GetGeneratedDir () is the [project] / app/build/generated/directory
  • GetVariantConfiguration ().getDirName() returns BuildType and Flavors (if any), such as Debug/or Baidu /debug/
  • GetRClassSourceOutputDir function to get the path is “[project] / app/build/generated/source/r/debug/”

Focused on the build directory/students should know that “[project] / app/build/generated/source/r/debug/” in the corresponding package name directory is under R.j ava file

So where is this path used and how is R.Java generated?

Back in the execute function, processResources is actually an object of ProcessAndroidResources, and since there is a setSourceOutputDir function, there is also a corresponding get function.

The get function is called in the doFullTaskAction function of ProcessAndroidResources.

protected void doFullTaskAction(a) throws IOException {
    // we have to clean the source folder output in case the package name changed.File srcOut = getSourceOutputDir(); .try{... AaptPackageConfig.Builder config =newAaptPackageConfig.Builder() .setManifestFile(manifestFileToPackage) .setOptions(getAaptOptions()) .setResourceDir(getResDir()) .setLibraries(getAndroidDependencies()) .setCustomPackageForR(getPackageForR()) .setSymbolOutputDir(getTextSymbolOutputDir()) .setSourceOutputDir(srcOut) .setResourceOutputApk(resOutBaseNameFile) .setProguardOutputFile(getProguardOutputFile()) .setMainDexListProguardOutputFile(getMainDexListProguardOutputFile()) .setVariantType(getType()) .setDebuggable(getDebuggable()) .setPseudoLocalize(getPseudoLocalesEnabled()) .setResourceConfigs(getResourceConfigs()) .setSplits(getSplits()) .setPreferredDensity(preferredDensity) .setBaseFeature(getBaseFeature()) .setPreviousFeatures(getPreviousFeatures()); builder.processResources(aapt, config, getEnforceUniquePackageName()); . }catch (IOException | InterruptedException | ProcessException e) {
        throw newRuntimeException(e); }}Copy the code

The information and packaging to a AaptPackageConfig. The Builder object, finally calling a processResources function.

ProcessResources is a function of AndroidBuilder.

public void processResources(
        @NonNull Aapt aapt,
        @NonNull AaptPackageConfig.Builder aaptConfigBuilder,
        boolean enforceUniquePackageName)
        throws IOException, InterruptedException, ProcessException { checkState(mTargetInfo ! =null."Cannot call processResources() before setTargetInfo() is called.");

    aaptConfigBuilder.setBuildToolInfo(mTargetInfo.getBuildTools());
    aaptConfigBuilder.setAndroidTarget(mTargetInfo.getTarget());
    aaptConfigBuilder.setLogger(mLogger);

    AaptPackageConfig aaptConfig = aaptConfigBuilder.build();

    try {
        aapt.link(aaptConfig).get();
    } catch (Exception e) {
        throw new ProcessException("Failed to execute aapt", e); }... }Copy the code

Aapt is an abstract class. The link method is AbstractAapt. The source code is as follows:

public ListenableFuture<Void> link(@NonNull AaptPackageConfig config)
        throws AaptException {
    validatePackageConfig(config);
    return makeValidatedPackage(config);
}
Copy the code

The validatePackageConfig function is called to check that the parameters are correct, and the makeValidatedPackage function is executed.

In AbstractAapt makeValidatedPackage is abstract method and its implementation in AbstractProcessExecutionAapt class, source code is as follows:

protected ListenableFuture<Void> makeValidatedPackage(@NonNull AaptPackageConfig config)
        throws AaptException {
    ProcessInfoBuilder builder = makePackageProcessBuilder(config);

    final ProcessInfo processInfo = builder.createProcess();
    ListenableFuture<ProcessResult> execResult = mProcessExecutor.submit(processInfo,
            mProcessOutputHandler);

    final SettableFuture<Void> result = SettableFuture.create();
    Futures.addCallback(execResult, new FutureCallback<ProcessResult>() {
        ...
    });

    return result;
}
Copy the code

Creates a ProcessInfoBuilder object, and then execute and get results, so the key is that the ProcessInfoBuilder object, take a look at makePackageProcessBuilder this function.

This function is also abstract, with two classes implementing AaptV1 and OutOfProcessAaptV2, which is obviously related to the current version of AAPT under the Android SDK.

The two methods are roughly similar, we only look at AaptV1, the source code is as follows:

protected ProcessInfoBuilder makePackageProcessBuilder(@NonNull AaptPackageConfig config)
        throws AaptException {
    ProcessInfoBuilder builder = newProcessInfoBuilder(); .// outputs
    if(config.getSourceOutputDir() ! =null) {
        builder.addArgs("-m");
        builder.addArgs(
                "-J", FileUtils.toExportableSystemDependentPath(config.getSourceOutputDir()));
    }

    if(config.getResourceOutputApk() ! =null) {
        builder.addArgs("-F", config.getResourceOutputApk().getAbsolutePath()); }...// Add the feature-split configuration if needed.
    if(config.getBaseFeature() ! =null) {
        builder.addArgs("--feature-of", config.getBaseFeature().getAbsolutePath());
        // --feature-after requires --feature-of to be set so these are only parsed if base
        // feature was set.
        for (File previousFeature : config.getPreviousFeatures()) {
            builder.addArgs("--feature-after", previousFeature.getAbsolutePath()); }}return builder;
}
Copy the code

You can see that this function is actually a combination of the aapt command, adding various parameters, where the getSourceOutputDir of interest is the value of the “-j” parameter.

Check out the instructions for AAPT:

Modifiers:

   -a  print Android-specific data (resources, manifest) when listing

   -c  specify which configurations to include.  The default is all

       configurations.  The value of the parameter should be a comma

       separated list of configuration values.  Locales should be specified

       as either a language or language-region pair.  Some examples:

            en

            port,en

            port,land,en_US

   -d  one or more device assets to include, separated by commas

   -f  force overwrite of existing files

   -g  specify a pixel tolerance to force images to grayscale, default 0

   -j  specify a jar or zip file containing classes to include

   -k  junk path of file(s) added

   -m  make package directories under location specified by -J

   -u  update existing packages (add new, replace older, remove deleted files)

   -v  verbose output

   -x  create extending (non-application) resource IDs

   -z  require localization of resource attributes marked with

       localization="suggested"

   -A  additional directory in which to find raw asset files

   -G  A file to output proguard options into.

   -D  A file to output proguard options for the main dex into.

   -F  specify the apk file to output

   -I  add an existing package to base include set

   -J  specify where to output R.java resource constant definitions

   -M  specify full path to AndroidManifest.xml to include in zip

   -P  specify where to output public resource definitions

   -S  directory in which to find resources.  Multiple directories will be scanned
Copy the code

As you can see, the -j parameter sets the output path of the r.java file so that we find the source.

If you look at the other code, you can see that the aapt command also adds some running parameters, such as the output path of the ASrc file, etc

Then go back and execute the command to complete the task.

conclusion

To summarize, the processResources process actually executes an aapt command to compile the resource file and generate some related files, such as the R file.