In this day and age, simple page development is hard to find a good job.

Therefore, in my spare time as an intern, I began to learn the Android Gradle construction process, and I wanted to organize this mental process into a blog, so as to deepen my understanding.

The paper

In this article, I will introduce some points to you:

  1. How does IncrementalTask implement increments? – IncrementalTask

  2. How is AndroidManifest merged? – ProcessManifest

  3. How are Resources handled? – MergeResources

  4. How is BuildConfig generated? – GenerateBuildConfig

PS: This article is based on Gradle 3.2.1

ApplicationTaskManager

First, we initialized the ApplicationTaskManager in AbstractAppPlugin in the previous article. Many, many tasks are created in ApplicationTaskManager, covering the entire build process. From the following we can briefly see what tasks are available.

@Override
    public void createTasksForVariantScope(@NonNull final VariantScope variantScope) {
        BaseVariantData variantData = variantScope.getVariantData();

        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));
        createBuildArtifactReportTask(variantScope);

        // Add a task to process the manifest(s)
        createMergeApkManifestsTask(variantScope);

        // Add a task to create the res values
        createGenerateResValuesTask(variantScope);

        // Add a task to compile renderscript files.
        createRenderscriptTask(variantScope);

        // Add a task to merge the resource folders
        createMergeResourcesTask(
                variantScope,
                true,
                Sets.immutableEnumSet(MergeResources.Flag.PROCESS_VECTOR_DRAWABLES));

        // Add tasks to compile shader
        createShaderTask(variantScope);

        // Add a task to merge the asset folders
        createMergeAssetsTask(variantScope);

        // Add a task to create the BuildConfig class
        createBuildConfigTask(variantScope);

        // Add a task to process the Android Resources and generate source files
        createApkProcessResTask(variantScope);

        // Add a task to process the java resources
        createProcessJavaResTask(variantScope);

        createAidlTask(variantScope);

        // Add NDK tasks
        createNdkTasks(variantScope);
        variantScope.setNdkBuildable(getNdkBuildable(variantData));

        // Add external native build tasks
        createExternalNativeBuildJsonGenerators(variantScope);
        createExternalNativeBuildTasks(variantScope);

        // Add a task to merge the jni libs folders
        createMergeJniLibFoldersTasks(variantScope);

        // Add feature related tasks if necessary
        if (variantScope.getType().isBaseModule()) {
            // Base feature specific tasks.
            taskFactory.create(new FeatureSetMetadataWriterTask.ConfigAction(variantScope));

            if (extension.getDataBinding().isEnabled()) {
                // Create a task that will package the manifest ids(the R file packages) of all
                // features into a file. This file's path is passed into the Data Binding annotation
                // processor which uses it to known about all available features.
                //
                // <p>see: {@link TaskManager#setDataBindingAnnotationProcessorParams(VariantScope)}
                taskFactory.create(
                        newDataBindingExportFeatureApplicationIdsTask.ConfigAction(variantScope)); }}else {
            // Non-base feature specific task.
            // Task will produce artifacts consumed by the base feature
            taskFactory.create(new FeatureSplitDeclarationWriterTask.ConfigAction(variantScope));
            if (extension.getDataBinding().isEnabled()) {
                // Create a task that will package necessary information about the feature into a
                // file which is passed into the Data Binding annotation processor.
                taskFactory.create(new DataBindingExportFeatureInfoTask.ConfigAction(variantScope));
            }
            taskFactory.create(new MergeConsumerProguardFilesConfigAction(variantScope));
        }

        // Add data binding tasks if enabled
        createDataBindingTasksIfNecessary(variantScope, MergeType.MERGE);

        // Add a compile task
        createCompileTask(variantScope);

        if (variantScope.getType().isBaseModule()) {
            CheckMultiApkLibrariesTask checkMultiApkLibrariesTask =
                    taskFactory.create(new CheckMultiApkLibrariesTask.ConfigAction(variantScope));
            // variantScope.setMergeJavaResourcesTask() is called in createCompileTask() above.
            // We set the merge java resources task to depend on this check, because merging java
            // resources is the first place an error could be thrown if there are duplicate
            // libraries.
            variantScope
                    .getTaskContainer()
                    .getMergeJavaResourcesTask()
                    .dependsOn(checkMultiApkLibrariesTask);
        }

        createStripNativeLibraryTask(taskFactory, variantScope);


        if (variantScope.getVariantData().getMultiOutputPolicy().equals(MultiOutputPolicy.SPLITS)) {
            if (extension.getBuildToolsRevision().getMajor() < 21) {
                throw new RuntimeException(
                        "Pure splits can only be used with buildtools 21 and later");
            }

            createSplitTasks(variantScope);
        }


        BuildInfoWriterTask buildInfoWriterTask = createInstantRunPackagingTasks(variantScope);
        createPackagingTask(variantScope, buildInfoWriterTask);

        // Create the lint tasks, if enabled
        createLintTasks(variantScope);

        taskFactory.create(new FeatureSplitTransitiveDepsWriterTask.ConfigAction(variantScope));

        createDynamicBundleTask(variantScope);
    }
Copy the code

IncrementalTask

Most of the above tasks inherit from IncrementalTask. So, if you want to analyze partial tasks, you first need to understand the main logic of IncrementalTask. Once you understand the logic of the Task, you should know where the code should look from.

public abstract class IncrementalTask extends AndroidBuilderTask {

    public static final String MARKER_NAME = "build_was_incremental";

    private File incrementalFolder;

    public void setIncrementalFolder(File incrementalFolder) {
        this.incrementalFolder = incrementalFolder;
    }

    @OutputDirectory @Optional
    public File getIncrementalFolder(a) {
        return incrementalFolder;
    }

    // Whether to increment
    @Internal
    protected boolean isIncremental(a) {
        return false;
    }
  
  	// Use this method for full builds
    protected abstract void doFullTaskAction(a) throws Exception;

  	// For incremental builds, go this way
    protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) throws Exception {
        // do nothing.
    }

  	/ / the entrance to the task
    @TaskAction
    void taskAction(IncrementalTaskInputs inputs) throws Exception {
      	// If it is not an incremental Task, or if the input is not incremental, go to the full build method -- doFullTaskAction
        if(! isIncremental() || ! inputs.isIncremental()) { getProject().getLogger().info("Unable do incremental execution: full task run");
            doFullTaskAction();
            return;
        }
				// Otherwise, go incremental
        doIncrementalTaskAction(getChangedInputs(inputs));
    }
	
  	// Inputs for incremental build
    private Map<File, FileStatus> getChangedInputs(IncrementalTaskInputs inputs) {
        final Map<File, FileStatus> changedInputs = Maps.newHashMap();
				
      	// Pass through the inputs that have expired and add them to map
        inputs.outOfDate(
                change -> {
                    FileStatus status = change.isAdded() ? FileStatus.NEW : FileStatus.CHANGED;
                    changedInputs.put(change.getFile(), status);
                });
				
      	// Pass through the inputs that have been removed to add them to map
        inputs.removed(change -> changedInputs.put(change.getFile(), FileStatus.REMOVED));

        returnchangedInputs; }}Copy the code

The above logic is simple and can be summarized as follows:

1. For a full build, use the doFullTaskAction method 2. For incremental builds, go doIncrementalTaskAction and calculate change inputs.Copy the code

Now that we know IncrementalTask, we can take a look at other simple tasks.

ProcessManifest

This Task is used to merge various Androidmanifestos and ultimately inherits from IncrementalTask. This method does not override the isIncremental method and therefore does not support increments, so we just need to look at the doFullTaskAction method. In addition, many tasks in AGP are configured using TaskConfigAction (usually a static inner class of a Task), and this Task is no exception.

 public static class ConfigAction implements TaskConfigAction<MergeManifests>
Copy the code

Ok, let’s look at the doFullTaskAction method

ProcessManifest.doFullTaskAction

    @Override
    protected void doFullTaskAction(a) throws IOException {
        // read the output of the compatible screen manifest.
        BuildElements compatibleScreenManifests =
                ExistingBuildElements.from(
                        InternalArtifactType.COMPATIBLE_SCREEN_MANIFEST, compatibleScreensManifest);

        
      	// Omit some checks

        @Nullable BuildOutput compatibleScreenManifestForSplit;

        ImmutableList.Builder<BuildOutput> mergedManifestOutputs = ImmutableList.builder();
				/ /... InstantRun logic omitted
      
        // FIX ME : multi threading.
        // TODO : LOAD the APK_LIST FILE .....
      	// The Apk set is iterated through different variants
        for (ApkData apkData : outputScope.getApkDatas()) {

            compatibleScreenManifestForSplit = compatibleScreenManifests.element(apkData);
            File manifestOutputFile =
                    FileUtils.join(
                            getManifestOutputDirectory(),
                            apkData.getDirName(),
                            SdkConstants.ANDROID_MANIFEST_XML);
            / /... InstantRun logic omitted
          
          	//MergingReport contains the results of the Manifest merge
            MergingReport mergingReport =
                    getBuilder()
              							// This contains the main Merge logic
                            .mergeManifestsForApplication(
                                    getMainManifest(),
                                    getManifestOverlays(),
                                    computeFullProviderList(compatibleScreenManifestForSplit),
                                    getNavigationFiles(),
                                    getFeatureName(),
                                    moduleMetadata == null
                                            ? getPackageOverride()
                                            : moduleMetadata.getApplicationId(),
                                    moduleMetadata == null
                                            ? apkData.getVersionCode()
                                            : Integer.parseInt(moduleMetadata.getVersionCode()),
                                    moduleMetadata == null
                                            ? apkData.getVersionName()
                                            : moduleMetadata.getVersionName(),
                                    getMinSdkVersion(),
                                    getTargetSdkVersion(),
                                    getMaxSdkVersion(),
                                    manifestOutputFile.getAbsolutePath(),
                                    // no aapt friendly merged manifest file necessary for applications.
                                    null /* aaptFriendlyManifestOutputFile */,
                                    instantRunManifestOutputFile.getAbsolutePath(),
                                    ManifestMerger2.MergeType.APPLICATION,
                                    variantConfiguration.getManifestPlaceholders(),
                                    getOptionalFeatures(),
                                    getReportFile());
						// Get the MERGE type XmlDocument in MergingReport
            XmlDocument mergedXmlDocument =
                    mergingReport.getMergedXmlDocument(MergingReport.MergedManifestKind.MERGED);
						// Extract the attributes from the XML documentImmutableMap<String, String> properties = mergedXmlDocument ! =null
                            ? ImmutableMap.of(
                                    "packageId",
                                    mergedXmlDocument.getPackageName(),
                                    "split",
                                    mergedXmlDocument.getSplitName(),
                                    SdkConstants.ATTR_MIN_SDK_VERSION,
                                    mergedXmlDocument.getMinSdkVersion())
                            : ImmutableMap.of();
						// Add attributes to the output
            mergedManifestOutputs.add(
                    new BuildOutput(
                            InternalArtifactType.MERGED_MANIFESTS,
                            apkData,
                            manifestOutputFile,
                            properties));
            / /... InstantRun logic omitted
        }
        new BuildElements(mergedManifestOutputs.build()).save(getManifestOutputDirectory());
        / /... InstantRun logic omitted
    }
Copy the code

According to my notes, we can divide it into the following steps:

1. ApkDatas will be traversed first. Each variant corresponds to an ApkData. Perform the Manifest Merge logic and save the results in the MergingReport 3. In MergingReport, get the XmlDocument 4 with MERGED property in one of its maps. Extract the individual attributes from the XmlDocument and store them in a collection as a BuildOutput object 5. To build this collection as BuildElement, the save method serializes the object into a Json file and saves it in the output folder of Manifest.Copy the code

In this a few steps, how I want to know is the Manifest of the merge, so we need to track mergeManifestsForApplication method.

AndroidBuilder.mergeManifestsForApplication

   public MergingReport mergeManifestsForApplication(...). {

        try {
						//Invoker is used to store all the data needed for Manifest merging. The necessary MainManifest is checked in its constructor.
            Invoker manifestMergerInvoker =
                    ManifestMerger2.newMerger(mainManifest, mLogger, mergeType)
                            .setPlaceHolderValues(placeHolders)
                            .addFlavorAndBuildTypeManifests(
                                    manifestOverlays.toArray(new File[manifestOverlays.size()]))
                            .addManifestProviders(dependencies)
                            .addNavigationFiles(navigationFiles)
                            .withFeatures(
                                    optionalFeatures.toArray(
                                            new Invoker.Feature[optionalFeatures.size()]))
                            .setMergeReportFile(reportFile)
                            .setFeatureName(featureName);

            / /...
						// These properties can directly override the corresponding properties in the ManifestSystemProperty
            setInjectableValues(manifestMergerInvoker,
                    packageOverride, versionCode, versionName,
                    minSdkVersion, targetSdkVersion, maxSdkVersion);
						// Merge the merge method.
            MergingReport mergingReport = manifestMergerInvoker.merge();
            mLogger.verbose("Merging result: %1$s", mergingReport.getResult());
          	// Merge the result of different processing, such as success directly into the file
            switch (mergingReport.getResult()) {
                case WARNING:
                   / /...
                case SUCCESS:
                    //....
                    break;
                case ERROR:
                    / /...
                default:
                    / /...
            }
            return mergingReport;
        } catch (ManifestMerger2.MergeFailureException e) {
            // TODO: unacceptable.
            throw newRuntimeException(e); }}Copy the code

The logic is not too complicated. Create an Invoker to hold all the data used by the Merge, and then call the Merge method to Merge it.

Invoker.merge

        public MergingReport merge(a) throws MergeFailureException {

            // provide some free placeholders values.
            ImmutableMap<ManifestSystemProperty, Object> systemProperties = mSystemProperties.build();
            if (systemProperties.containsKey(ManifestSystemProperty.PACKAGE)) {
                // if the package is provided, make it available for placeholder replacement.
                mPlaceholders.put(PACKAGE_NAME, systemProperties.get(ManifestSystemProperty.PACKAGE));
                // as well as applicationId since package system property overrides everything
                // but not when output is a library since only the final (application)
                // application Id should be used to replace libraries "applicationId" placeholders.
                if(mMergeType ! = MergeType.LIBRARY) { mPlaceholders.put(APPLICATION_ID, systemProperties.get(ManifestSystemProperty.PACKAGE)); } } FileStreamProvider fileStreamProvider = mFileStreamProvider ! =null
                    ? mFileStreamProvider : new FileStreamProvider();
            ManifestMerger2 manifestMerger =
                    new ManifestMerger2(
                            mLogger,
                            mMainManifestFile,
                            mLibraryFilesBuilder.build(),
                            mFlavorsAndBuildTypeFiles.build(),
                            mFeaturesBuilder.build(),
                            mPlaceholders.build(),
                            new MapBasedKeyBasedValueResolver<ManifestSystemProperty>(
                                    systemProperties),
                            mMergeType,
                            mDocumentType,
                            Optional.fromNullable(mReportFile),
                            mFeatureName,
                            fileStreamProvider,
                            mNavigationFilesBuilder.build());
            return manifestMerger.merge();
        }
Copy the code

Set up some placeholder, then use it for replacement, then generate ManifestMerger2 and finally merge.

ManifestMerger2.merge

Merge there is a lot of code in merge, which we split into several parts:

  1. Load the Main Manifest
// load the main manifest file to do some checking along the way.
        LoadedManifestInfo loadedMainManifestInfo = load(
                new ManifestInfo(
                        mManifestFile.getName(),
                        mManifestFile,
                        mDocumentType,
                        Optional.<String>absent() /* mainManifestPackageName */),
                selectors,
                mergingReportBuilder);
Copy the code
  1. Do some checking, such as whether package attributes are set
  2. Load the LIBRARY XML
// load all the libraries xml files early to have a list of all possible node:selector
        // values.
        List<LoadedManifestInfo> loadedLibraryDocuments =
                loadLibraries(
                        selectors,
                        mergingReportBuilder,
                        mainPackageAttribute.isPresent()
                                ? mainPackageAttribute.get().getValue()
                                : null);
Copy the code
  1. Perform Manifest system attribute injection, such as version and version_name
// perform system property injection
        performSystemPropertiesInjection(mergingReportBuilder,
                loadedMainManifestInfo.getXmlDocument());
Copy the code
  1. Traverse mFlavorsAndBuildTypeFiles, do some check
Optional<XmlDocument> xmlDocumentOptional = Optional.absent();
        for (File inputFile : mFlavorsAndBuildTypeFiles) {
            mLogger.verbose("Merging flavors and build manifest %s \n", inputFile.getPath());
            LoadedManifestInfo overlayDocument = load(
                    new ManifestInfo(null, inputFile, XmlDocument.Type.OVERLAY,
                            Optional.of(mainPackageAttribute.get().getValue())),
                    selectors,
                    mergingReportBuilder);

            // Check if package is defined
            Optional<XmlAttribute> packageAttribute =
                    overlayDocument.getXmlDocument().getPackage();
            // If both files define packages, their packages should be the same
            if(loadedMainManifestInfo.getOriginalPackageName().isPresent() && packageAttribute.isPresent() && ! loadedMainManifestInfo.getOriginalPackageName().get().equals( packageAttribute.get().getValue())) { String message = mMergeType == MergeType.APPLICATION ? String.format("Overlay manifest:package attribute declared at %1$s value=(%2$s)\n"
                                        + "\thas a different value=(%3$s) "
                                        + "declared in main manifest at %4$s\n"
                                        + "\tSuggestion: remove the overlay declaration at %5$s "
                                        + "\tand place it in the build.gradle:\n"
                                        + "\t\tflavorName {\n"
                                        + "\t\t\tapplicationId = \"%2$s\"\n"
                                        + "\t\t}",
                                packageAttribute.get().printPosition(),
                                packageAttribute.get().getValue(),
                                mainPackageAttribute.get().getValue(),
                                mainPackageAttribute.get().printPosition(),
                                packageAttribute.get().getSourceFile().print(true))
                        : String.format(
                                "Overlay manifest:package attribute declared at %1$s value=(%2$s)\n"
                                        + "\thas a different value=(%3$s) "
                                        + "declared in main manifest at %4$s",
                                packageAttribute.get().printPosition(),
                                packageAttribute.get().getValue(),
                                mainPackageAttribute.get().getValue(),
                                mainPackageAttribute.get().printPosition());
                mergingReportBuilder.addMessage(
                        overlayDocument.getXmlDocument().getSourceFile(),
                        MergingReport.Record.Severity.ERROR,
                        message);
                return mergingReportBuilder.build();
            }
           / /...
        }
Copy the code
  1. When merging library, force mainManifest’s package into it
if (mMergeType == MergeType.LIBRARY) {
            // extract the package name...
            String mainManifestPackageName = loadedMainManifestInfo.getXmlDocument().getRootNode()
                    .getXml().getAttribute("package");
            // save it in the selector instance.
            if(! Strings.isNullOrEmpty(mainManifestPackageName)) { xmlDocumentOptional.get().getRootNode().getXml() .setAttribute("package", mainManifestPackageName); }}Copy the code
  1. Merge the Manifest of the library
for (LoadedManifestInfo libraryDocument : loadedLibraryDocuments) {
            mLogger.verbose("Merging library manifest " + libraryDocument.getLocation());
            xmlDocumentOptional = merge(
                    xmlDocumentOptional, libraryDocument, mergingReportBuilder);
            if(! xmlDocumentOptional.isPresent()) {returnmergingReportBuilder.build(); }}Copy the code
  1. Replace the PlaceHolder in the Manifest
if(! mOptionalFeatures.contains(Invoker.Feature.NO_PLACEHOLDER_REPLACEMENT)) {// do one last placeholder substitution, this is useful as we don't stop the build
            // when a library failed a placeholder substitution, but the element might have
            // been overridden so the problem was transient. However, with the final document
            // ready, all placeholders values must have been provided.
            MergingReport.Record.Severity severity =
                    mMergeType == MergeType.LIBRARY
                            ? MergingReport.Record.Severity.INFO
                            : MergingReport.Record.Severity.ERROR;
            performPlaceHolderSubstitution(
                    loadedMainManifestInfo,
                    xmlDocumentOptional.get(),
                    mergingReportBuilder,
                    severity);
            if (mergingReportBuilder.hasErrors()) {
                returnmergingReportBuilder.build(); }}Copy the code
  1. Let’s do a little more processing of the final Manifest (finalMergedDocument), but we won’t care about it here
  2. Finally, the merged Androidmanifest.xml file is generated

At this point, a complete Manifest merge process is complete.

MergeResources

Looking at the MergeResources Task, we see that it inherits from IncrementalTask, which overwrites isIncremental and returns true.

So we need to look at the doFullTaskAction method as well as the doIncrementalTaskAction method.

MergeResources.doFullTaskAction

This method has many steps, so let’s just look at the main steps.

  1. Clean up the previous output folder
				File destinationDir = getOutputDir();
        FileUtils.cleanOutputDir(destinationDir);
        if(dataBindingLayoutInfoOutFolder ! =null) {
            FileUtils.deleteDirectoryContents(dataBindingLayoutInfoOutFolder);
        }
Copy the code
  1. Obtaining resource files
List<ResourceSet> resourceSets = getConfiguredResourceSets(preprocessor);
Copy the code

This method returns a ResourceSet collection containing your own res, library res, and generated res

  1. Create a ResourceMerger and populate it with the ResourceSet collection you just obtained
			// create a new merger and populate it with the sets.
        ResourceMerger merger = new ResourceMerger(minSdk.get());
        MergingLog mergingLog = null;
        if(blameLogFolder ! =null) {
            FileUtils.cleanOutputDir(blameLogFolder);
            mergingLog = new MergingLog(blameLogFolder);
        }
				
				/ / ResourceCompilationService according to aaptGeneration judgment, the use of AAPT or AAPT2, etc
        try(ResourceCompilationService resourceCompiler = getResourceProcessor(....) ) {for (ResourceSet resourceSet : resourceSets) {
                resourceSet.loadFromFiles(getILogger());
              	/ / fill the merger
                merger.addDataSet(resourceSet);
            }
Copy the code
  1. Create MergedResourceWriter, which is responsible for compiling resource files and stripping Databinding stuff from layout files
MergedResourceWriter writer =
                    new MergedResourceWriter(
                            workerExecutorFacade,
                            destinationDir,
                            getPublicFile(),
                            mergingLog,
                            preprocessor,
                            resourceCompiler,
                            getIncrementalFolder(),
                            dataBindingLayoutProcessor,
                            mergedNotCompiledResourcesOutputDirectory,
                            pseudoLocalesEnabled,
                            getCrunchPng());
Copy the code
  1. Merger of resources
merger.mergeData(writer, false /*doCleanUp*/);
Copy the code
  1. After passing MergedResourceWriter into this method, the MergedResourceWriter’s start, ignoreItemInMerge, removeItem, addItem, and End methods are called to process the resource. The Item in the above method represents the resource file (each file in the RES directory, which could be an image resource or an XML file). Each Item is converted to a CompileResourceRequest in MergedResourceWriter and added to the collection compileresourCerequests. This collection is defined as follows:
@NonNull
    private final ConcurrentLinkedQueue<CompileResourceRequest> mCompileResourceRequests =
            new ConcurrentLinkedQueue<>();
Copy the code

So why are queues in the Concurrent package used to ensure concurrency? There is a comment in removeItem

/*
* There are two reasons to skip this: 1. we save an IO operation by
* deleting a file that will be overwritten. 2. if we did delete the file,
* we would have to be careful about concurrency to make sure we would be
* deleting the *old* file and not the overwritten version.
*/
Copy the code

We want to make sure that the files we delete are older versions of the files, not newer versions of the files overwritten.

  1. In the Start method of MergedResourceWriter, some variables are initialized. In the end method, mCompileResourceRequests are processed. The key points are:
					while(! mCompileResourceRequests.isEmpty()) { CompileResourceRequest request = mCompileResourceRequests.poll();try {
                    / /... Process DataBinding logic
	
                  	//mResourceCompiler is an interface whose implementation classes are Aapt2 related
                  	// Call the submitCompile method to compile the resource files using Aapt2
                    mResourceCompiler.submitCompile(
                            new CompileResourceRequest(
                                    fileToCompile,
                                    request.getOutputDirectory(),
                                    request.getInputDirectoryName(),
                                    pseudoLocalesEnabled,
                                    crunchPng,
                                    ImmutableMap.of(),
                                    request.getInputFile()));
                  	// Put the file into the map after compiling
                    mCompiledFileMap.put(
                            fileToCompile.getAbsolutePath(),
                            mResourceCompiler.compileOutputFor(request).getAbsolutePath());

                } catch (ResourceCompilationException | IOException e) {
                    throwMergingException.wrapException(e) .withFile(request.getInputFile()) .build(); }}/ /... Then release some resources and call the McOmpiledfilemap. store method to store the map
Copy the code

At this point the MergeResources process is over.

GenerateBuildConfig

We often use the BuildConfig. Java class to get Gradle configuration properties, so you must be wondering how BuildConfig is generated.

The GenerateBuildConfig class is a Task specifically designed to GenerateBuildConfig.

First, let’s look at the annotation method of the @TaskAction annotation, which is the entry point to the Task.

		@TaskAction
    void generate(a) throws IOException {
      	// When packageName changes, be sure to remove the output folder, otherwise there will be two classes
        File destinationDir = getSourceOutputDir();
        FileUtils.cleanOutputDir(destinationDir);
			
      	// Create BuildConfigGenerator and configure the PackageName and output folder
        BuildConfigGenerator generator = new BuildConfigGenerator(
                getSourceOutputDir(),
                getBuildConfigPackageName());

        // Add the fields in BuildConfig using Generator
        generator
                .addField(
                        "boolean"."DEBUG",
                        isDebuggable() ? "Boolean.parseBoolean(\"true\")" : "false")
                .addField("String"."APPLICATION_ID".'"' + appPackageName.get() + '"')
                .addField("String"."BUILD_TYPE".'"' + getBuildTypeName() + '"')
                .addField("String"."FLAVOR".'"' + getFlavorName() + '"')
                .addField("int"."VERSION_CODE", Integer.toString(getVersionCode()))
                .addField(
                        "String"."VERSION_NAME".'"' + Strings.nullToEmpty(getVersionName()) + '"')
          			// Those are the basic attributes. The custom attributes are added in getItems()
                .addItems(getItems());

        List<String> flavors = getFlavorNamesWithDimensionNames();
        int count = flavors.size();
        if (count > 1) {
            for (int i = 0; i < count; i += 2) {
              	/ / add flavor
                generator.addField(
                        "String"."FLAVOR_" + flavors.get(i + 1), '"' + flavors.get(i) + '"'); }}// Finally generate a class
        generator.generate();
    }
Copy the code

The logic is simple, so let’s see what properties get in getItems(). This items are initially by VariantConfiguration. GetBuildConfigItems assignment:

@NonNull
public List<Object> getBuildConfigItems(a) {
    List<Object> fullList = Lists.newArrayList();

    // keep track of the names already added. This is because we show where the items
    // come from so we cannot just put everything a map and let the new ones override the
    // old ones.
    Set<String> usedFieldNames = Sets.newHashSet();

  	// Add variation-specific fields
    Collection<ClassField> list = mBuildConfigFields.values();
    if(! list.isEmpty()) { fullList.add("Fields from the variant");
        fillFieldList(fullList, usedFieldNames, list);
    }

  	// Add Key as the Filed property
    list = mBuildType.getBuildConfigFields().values();
    if(! list.isEmpty()) { fullList.add("Fields from build type: " + mBuildType.getName());
        fillFieldList(fullList, usedFieldNames, list);
    }
		
  	// Add a Field attribute for each Flavor
    for (F flavor : mFlavors) {
        list = flavor.getBuildConfigFields().values();
        if(! list.isEmpty()) { fullList.add("Fields from product flavor: "+ flavor.getName()); fillFieldList(fullList, usedFieldNames, list); }}// Add the Field attribute to the default Config
    list = mDefaultConfig.getBuildConfigFields().values();
    if(! list.isEmpty()) { fullList.add("Fields from default config.");
        fillFieldList(fullList, usedFieldNames, list);
    }

    return fullList;
}
Copy the code

So we define BuildConfigField in Gradle, and that’s how it’s generated.

buildConfigField("String"."HAHA"."\"haahahah\"")
Copy the code

You can then write its properties to buildConfig.java.

subsequent

This article explains how to analyze various AGP tasks. If you want to view the source code of other AGP tasks, you need to analyze it yourself. If you have any questions, feel free to post them in the comments.

Finally ridicule: AGP source code is easy to change, really let learners feel confused.