[Android Refinement Manual] Gradle Chapter--Analysis of the Main Processes of Android Gradle Plugin

Preparatory knowledge

  1. Understanding the basic development of gradle
  2. Understanding the use and development of gradle task and plugin
  3. Understanding the use of android gradle plugin

How far can I get after reading this article?

  1. Understanding the construction process of android gradle plugin
  2. Understanding the main task implementation of android gradle plugin
  3. Learn how to hook android build processes and add functions you want

Pre-reading preparation

  1. Project add android gradle plugin dependency
compile 'com.android.tools.build:gradle:3.0.1'

In this way, you can directly rely on the source code of the plugin, which is more convenient to read.

  1. Official Reference Source Address android gradle plugin source address

You can clone directly. EasyGradle For projects, just open the implementation'com. android. tools. build: gradle: 3.0.1'annotation in android-gradle-plugin-source/build.gradle.

com.android.application has the following main processes:

Preparations for plug-in startup

As mentioned earlier in the introduction of custom plug-ins, we need to define an xxx.properties file, which declares the entry class of the plug-in, and XXX is the id used in the application plugin. Here we need to know the entry class of the android gradle plugin. Just look at the com.android.application.properties file, which reads as follows:

implementation-class=com.android.build.gradle.AppPlugin

The entry is defined here as AppPlugin, which inherits from BasePlugin.
AppPlugin doesn't do much, mainly rewrite createTask Manager and createExtension, and most of the rest is done in BasePlugin.
The main tasks of plug-in preparation are as follows:

  1. Check plug-in version
// method: BasePlugin.apply()
checkPluginVersion();
  1. Check whether module is renamed
// method: BasePlugin.apply()
// The method traverses all subitems to determine if there is a duplicate id
this.checkModulesForErrors();
  1. Initialize plug-in information
// method: BasePlugin.apply()
PluginInitializer.initialize(project, this.projectOptions);
// Create Profiler files
ProfilerInitializer.init(project, this.projectOptions);
// Write plugin version in profile information
ProcessProfileWriter.getProject(project.getPath()).setAndroidPluginVersion(Version.ANDROID_GRADLE_PLUGIN_VERSION).setAndroidPlugin(this.getAnalyticsPluginType()).setPluginGeneration(PluginGeneration.FIRST);

II. Configuration Project

The main tasks of this phase of configuration project are as follows:

  1. Check whether the gradle version matches
// method: BasePlugin.configureProject()
this.checkGradleVersion();
  1. Create Android Builder and Data Binding Builder
  2. Introduce java plugin and jacoco plugin
this.project.getPlugins().apply(JavaBasePlugin.class);
this.project.getPlugins().apply(JacocoPlugin.class);
  1. Setting up Mixed Clearance after Construction

BuildListener was added to do cache cleaning in the build Finished callback

Configuring Extension

Implementation in BasePlugin.configureExtension()
In this stage, the following main things have been done:

  1. Create AppExtension, the android {} dsl used in build.gradle
this.extension = this.createExtension(...);

// createExtension is implemented in AppPlugin and android {} dsl is created
protected BaseExtension createExtension(...) {
    return (BaseExtension)project.getExtensions().create("android", AppExtension.class, new Object[]{project, projectOptions, instantiator, androidBuilder, sdkHandler, buildTypeContainer, productFlavorContainer, signingConfigContainer, buildOutputs, extraModelInfo});
}
  1. Create dependency management, ndk management, task management, variant management
  2. Register new configuration callback functions, including signingConfig, buildType, productFlavor
// BasePlugin.java createExtension()
// map the whenObjectAdded callbacks on the containers.
signingConfigContainer.whenObjectAdded(variantManager::addSigningConfig);
buildTypeContainer.whenObjectAdded(
    buildType -> {
        SigningConfig signingConfig =
                signingConfigContainer.findByName(BuilderConstants.DEBUG);
        buildType.init(signingConfig);
        // Add BuildType, which checks whether the name is valid, and then creates BuildTypeData
        variantManager.addBuildType(buildType);
    });
// AddiProduct Flavor checks for validity of naming, and then creates Product Flavor Data.
productFlavorContainer.whenObjectAdded(variantManager::addProductFlavor);
// VariantManager.java
// Adding Signing Config is a new configuration in signing Configs
public void addSigningConfig(SigningConfig signingConfig) {
    this.signingConfigs.put(signingConfig.getName(), signingConfig);
}
// VariantManager.java
public void addProductFlavor(CoreProductFlavor productFlavor) {
    String name = productFlavor.getName();
    // checkName checks
    checkName(name, "ProductFlavor");
    if(this.buildTypes.containsKey(name)) {
        throw new RuntimeException("ProductFlavor names cannot collide with BuildType names");
    } else {
        // Get the source location
        DefaultAndroidSourceSet mainSourceSet = (DefaultAndroidSourceSet)this.extension.getSourceSets().maybeCreate(productFlavor.getName());
        DefaultAndroidSourceSet androidTestSourceSet = null;
        DefaultAndroidSourceSet unitTestSourceSet = null;
        if(this.variantFactory.hasTestScope()) {
            // Getting the location of single source code
            androidTestSourceSet = (DefaultAndroidSourceSet)this.extension.getSourceSets().maybeCreate(computeSourceSetName(productFlavor.getName(), VariantType.ANDROID_TEST));
            unitTestSourceSet = (DefaultAndroidSourceSet)this.extension.getSourceSets().maybeCreate(computeSourceSetName(productFlavor.getName(), VariantType.UNIT_TEST));
        }

        // Create a productFlavorData object
        ProductFlavorData<CoreProductFlavor> productFlavorData = new ProductFlavorData(productFlavor, mainSourceSet, androidTestSourceSet, unitTestSourceSet, this.project);
        this.productFlavors.put(productFlavor.getName(), productFlavorData);
    }
}
  1. Create default debug signatures, create debug and release buildType s
variantFactory.createDefaultComponents(
    buildTypeContainer, productFlavorContainer, signingConfigContainer);
// ApplicationVariantFactory.java
public void createDefaultComponents(...) {
    signingConfigs.create("debug");
    buildTypes.create("debug");
    buildTypes.create("release");
}

4. Creating task s that do not rely on flavor

After the configuration phase is completed, the Task needed for building is created, which is implemented in BasePlugin.createTasks(). There are two main steps: creating task independent of flavor and creating a task for building.
Look at task that does not depend on flavor, but actually implements TaskManager. createTasks BeforeEvaluate ().
Several task s are created here, including uninstallAll, device Check, connected Check, preBuild, extract ProguardFiles, source Sets, assemble Android Test, compileLint, lint, lintChecks, cleanBuild CacheresolveConfigAttr, consumeConfigAttr.
These tasks are common tasks that do not rely on flavor data.

5. Create build task

Before introducing the following process, we first clarify several concepts, flavor, dimension, variant.
After android gradle plugin 3.x, each flavor must correspond to a dimension, which can be understood as a grouping of flavors, and then flavors in different dimensions are combined into a variant.
For instance:

flavorDimensions "size", "color"

productFlavors {
    big {
        dimension "size"
    }
    small {
        dimension "size"
    }
    blue {
        dimension "color"
    }
    red {
        dimension "color"
    }
}

The variant s generated by the above configuration are bigBlue, bigRed, smallBlue, smallRed. On this basis, plus build Types, they are bigBlueDebug, bigRed Debug, smallBlueDebug, smallRed Debug, bigBlueRelease, bigRed Release, smallBlueRelease and smallRed Release.

Create Android Tasks is called in project. After Evaluate at a different time. Do you remember the post Evaluate callback mentioned in the previous article? By this time, all module configurations have been completed. So at this stage you can get the corresponding flavor and other configurations.
In BasePlugin. createAndroid Tasks, you call VariantManager. createAndroid Tasks to complete the work.
When creating task, the flavor-related data structure is generated through populateVariant DataList, and then the task corresponding to flavor is created by calling createTasks ForVariant Data.
Look at what these two approaches do.
1.populateVariantDataList
In the method, the corresponding combination is created according to the flavor and dimension, stored in the flavor ComboList, and then the corresponding Variant Data is created by calling createVariant Data ForProduct Flavors.
Some of the important methods are as follows:

// Create a combination of flavor and dimension
List<ProductFlavorCombo<CoreProductFlavor>> flavorComboList =
                    ProductFlavorCombo.createCombinations(
                            flavorDimensionList,
                            flavorDsl);
// Create VariantData for each combination
for (ProductFlavorCombo<CoreProductFlavor>  flavorCombo : flavorComboList) {
    //noinspection unchecked
    createVariantDataForProductFlavors(
            (List<ProductFlavor>) (List) flavorCombo.getFlavorList());
}

Variant Data created are all subclasses of BaseVariant Data, which contains some Task s. You can see some important structures in BaseVariant Data and have a general understanding of BaseVariant Data.

public abstract class BaseVariantData implements TaskContainer {
    private final GradleVariantConfiguration variantConfiguration;
    private VariantDependencies variantDependency;
    private final VariantScope scope;
    public Task preBuildTask;
    public Task sourceGenTask;
    public Task resourceGenTask; // Resource Processing
    public Task assetGenTask;
    public CheckManifest checkManifestTask; // Detection of manifest
    public AndroidTask<PackageSplitRes> packageSplitResourcesTask; // Packaging resources
    public AndroidTask<PackageSplitAbi> packageSplitAbiTask;
    public RenderscriptCompile renderscriptCompileTask; 
    public MergeResources mergeResourcesTask; // Consolidation of resources
    public ManifestProcessorTask processManifest; // Processing manifest
    public MergeSourceSetFolders mergeAssetsTask; // Merge assets
    public GenerateBuildConfig generateBuildConfigTask; // Generating BuildConfig
    public GenerateResValues generateResValuesTask;
    public Sync processJavaResourcesTask;
    public NdkCompile ndkCompileTask; // ndk compilation
    public JavaCompile javacTask; 
    public Task compileTask;
    public Task javaCompilerTask; // java file compilation
    // ...
}

Variant Data saves many task s, and the next step is to create them.

2.createTasksForVariantData
After creating variant data, we need to create corresponding tasks for each variant data. The corresponding tasks are assembleXXXTask, prebuildXXX, generateXXXSource, generateXXXResources, generateXXXAssets, processXXXManifest and so on. We focus on several methods:

VariantManager.createAssembleTaskForVariantData() // Create assembleXXXTask
TaskManager.createTasksForVariantScope() // Is an abstract class, specifically implemented in Application Task Manager. createTasks ForVariantScope ()
TaskManager.createPostCompilationTasks() // Create. class to dex task, create transformTask, the transformation we created is added in this stage, is called in addCompileTask

// CreateTasks ForVariantScope is an abstract method, specifically implemented in a subclass. You can see Application Task Manager. createTasks ForVariantScope ()
// The implementation in createTasks ForVariant Scope can be found here if there is a need to view the relevant task source code in the business
void createTasksForVariantScope() {
    this.createCheckManifestTask(tasks, variantScope); // Detection of manifest
    this.handleMicroApp(tasks, variantScope);
    this.createDependencyStreams(tasks, variantScope);
    this.createApplicationIdWriterTask(tasks, variantScope); // application id 
    this.createMergeApkManifestsTask(tasks, variantScope); // Merge manifest
    this.createGenerateResValuesTask(tasks, variantScope);
    this.createRenderscriptTask(tasks, variantScope);
    this.createMergeResourcesTask(tasks, variantScope, true); // Consolidation of resource files
    this.createMergeAssetsTask(tasks, variantScope, (BiConsumer)null); // Merge assets
    this.createBuildConfigTask(tasks, variantScope); // Generating BuildConfig
    this.createApkProcessResTask(tasks, variantScope); // Processing resources
    this.createProcessJavaResTask(tasks, variantScope);
    this.createAidlTask(tasks, variantScope); // Handling aidl
    this.createShaderTask(tasks, variantScope);
    this.createNdkTasks(tasks, variantScope); // Processing ndk
    this.createExternalNativeBuildJsonGenerators(variantScope);
    this.createExternalNativeBuildTasks(tasks, variantScope);
    this.createMergeJniLibFoldersTasks(tasks, variantScope); // Merge jni
    this.createDataBindingTasksIfNecessary(tasks, variantScope); // Processing data binding
    this.addCompileTask(tasks, variantScope); 
    createStripNativeLibraryTask(tasks, variantScope);
    this.createSplitTasks(tasks, variantScope);
    this.createPackagingTask(tasks, variantScope, buildInfoWriterTask); // Packing apk
    this.createLintTasks(tasks, variantScope); // lint 
}

// CreatePost Compilation Tasks implementation:
// Processing Android Transform
void createPostCompilationTasks() {
    for (int i = 0, count = customTransforms.size(); i < count; i++) {
        Transform transform = customTransforms.get(i);
        // Transform Manager. addTransform actually creates a Task for transform
        transformManager
                .addTransform(tasks, variantScope, transform)
                .ifPresent(t -> {
                    if (!deps.isEmpty()) {
                        t.dependsOn(tasks, deps);
                    }
                    // if the task is a no-op then we make assemble task depend on it.
                    if (transform.getScopes().isEmpty()) {
                        variantScope.getAssembleTask().dependsOn(tasks, t);
                    }
                });
    }
}

VI. SUMMARY

The main process of android gradle plugin is basically over. The main flow chart is shown below.

Here are some key points:

  1. The entry class of com.android.application is AppPlugin, but most of the work is done in BasePlugin.
  2. The android {} dsl you see in build.gradle is declared in BasePlugin.configureExtension().
  3. The main task is generated in BasePlugin. createAndroid Tasks ().
  4. The main task implementation can be found in Task Manager
  5. transform will be transformed into TransformTask

The Android Training Manual series is updated every Friday
Welcome to the following account for updates:
Wechat Search Public Number: ZYLAB
Github
Nuggets

Tags: Android Gradle Java REST

Posted on Tue, 27 Aug 2019 19:31:18 -0700 by bhanu