Preliminary knowledge

  1. Understand the basic development of Gradle
  2. Understand gradle Task and Plugin usage and development
  3. Understand the use of android Gradle Plugin

How far can I go after reading this article

  1. Understand the building process of Android Gradle Plugin
  2. Understand the implementation of major tasks of android Gradle Plugin
  3. Hook the Android build process and add the features you want

Prepare before reading

  1. Add android Gradle Plugin dependency to the project
compile 'com. Android. Tools. Build: gradle: 3.0.1'
Copy the code

In this way, you can rely directly on the source code of the plugin, which is easier to read. Android Gradle plugin plugin

You can clone the EasyGradle project, The android – gradle – plugin – source/build. The implementation in the gradle ‘com. Android. View the build: gradle: 3.0.1’ annotation to open.

Com.android. application mainly has the following processes:

I. Preparation for plug-in startup

Properties file to declare the entry class of the plugin. XXX is the id used by the Apply Plugin. Here we need to know the entry class of android Gradle Plugin. The com. Android. Application. Can the properties file, content is as follows:

implementation-class=com.android.build.gradle.AppPlugin
Copy the code

The entry is defined as AppPlugin, which inherits from BasePlugin. AppPlugin doesn’t do much, but overrides createTaskManager and createExtension, and does most of the rest in BasePlugin. The main things to do in plug-in preparation:

  1. Checking the Plug-in version
// method: BasePlugin.apply()
checkPluginVersion();
Copy the code
  1. Check whether the modules have the same name
// method: BasePlugin.apply()
// The method iterates through all subitems to see if there are duplicate ids
this.checkModulesForErrors();
Copy the code
  1. Initialize the plug-in information
// method: BasePlugin.apply()
PluginInitializer.initialize(project, this.projectOptions);
// Create a Profiler file
ProfilerInitializer.init(project, this.projectOptions);
// Write the plugin version to the profiler messageProcessProfileWriter.getProject(project.getPath()).setAndroidPluginVersion(Version.ANDROID_GRADLE_PLUGIN_VERSION).setAnd roidPlugin(this.getAnalyticsPluginType()).setPluginGeneration(PluginGeneration.FIRST);
Copy the code

2. Configuration items

The main tasks in this stage of the configuration project are as follows:

  1. Check whether gradle versions match
// method: BasePlugin.configureProject()
this.checkGradleVersion();
Copy the code
  1. Create AndroidBuilder and DataBindingBuilder
  2. Introduce the Java Plugin and Jacoco plugin
this.project.getPlugins().apply(JavaBasePlugin.class);
this.project.getPlugins().apply(JacocoPlugin.class);
Copy the code
  1. To set up the mix cleanup after the build is complete, add BuildListener, which does the cache cleanup in the buildFinished callback

3. Configure Extension

Implementation in BasePlugin. ConfigureExtension () this stage mainly do the following things:

  1. Create an AppExtension, the Android {} DSL used in build.gradle
this.extension = this.createExtension(...) ;// createExtension is implemented in AppPlugin to create the Android {} DSL
protected BaseExtension createExtension(...) {
    return (BaseExtension)project.getExtensions().create("android", AppExtension.class.new Object[]{project, projectOptions, instantiator, androidBuilder, sdkHandler, buildTypeContainer, productFlavorContainer, signingConfigContainer, buildOutputs, extraModelInfo});
}
Copy the code
  1. Create dependency management, NDK management, task management, Variant management
  2. Register newly configured 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);
        // addBuildType, which checks if the name is valid, then creates BuildTypeData
        variantManager.addBuildType(buildType);
    });
// addProductFlavor checks that the name is valid and creates ProductFlavorData
productFlavorContainer.whenObjectAdded(variantManager::addProductFlavor);
// VariantManager.java
// addSigningConfig adds a new configuration to signingConfigs
public void addSigningConfig(SigningConfig signingConfig) {
    this.signingConfigs.put(signingConfig.getName(), signingConfig);
}
// VariantManager.java
public void addProductFlavor(CoreProductFlavor productFlavor) {
    String name = productFlavor.getName();
    // checkName will check
    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()) {
            // Get the single test source location
            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 productFlavorData object
        ProductFlavorData<CoreProductFlavor> productFlavorData = new ProductFlavorData(productFlavor, mainSourceSet, androidTestSourceSet, unitTestSourceSet, this.project);
        this.productFlavors.put(productFlavor.getName(), productFlavorData); }}Copy the code
  1. Create the default Debug signature, and create both debug and Release buildTypes
variantFactory.createDefaultComponents(
    buildTypeContainer, productFlavorContainer, signingConfigContainer);
// ApplicationVariantFactory.java
public void createDefaultComponents(...) {
    signingConfigs.create("debug");
    buildTypes.create("debug");
    buildTypes.create("release");
}
Copy the code

Create tasks that do not rely on flavor

CreateTasks () is a baseplugin.createTasks (), which consists of two steps: create a Task that does not depend on flavor and create a build Task. Can’t rely on the flavor of the task first, actually now TaskManager… createTasksBeforeEvaluate (). UninstallAll, deviceCheck, connectedCheck, preBuild, extractProguardFiles, sourceSets, assembleAndroidTest, CompileLint, lint, lintChecks cleanBuildCacheresolveConfigAttr, consumeConfigAttr. These tasks are common tasks that do not rely on flavor data.

Create a build task

Before introducing the following process, clarify several concepts: flavor, Dimension and variant. After Android Gradle plugin 3.x, each flavor must correspond to a dimension, which can be understood as a group of flavors. Then, the flavors in different dimensions are combined into a variant. Here’s an example:

flavorDimensions "size"."color"

productFlavors {
    big {
        dimension "size"
    }
    small {
        dimension "size"
    }
    blue {
        dimension "color"
    }
    red {
        dimension "color"}}Copy the code

Variant = bigBlue, bigRed, smallBlue, smallRed; buildTypes = bigBlueDebug, bigRedDebug, smallBlueDebug; SmallRedDebug, bigBlueRelease, bigRedRelease, smallBlueRelease, smallRedRelease.

AfterEvaluate is a callback to createAndroidTasks. AfterEvaluate is a callback to createAndroidTasks. At this point all module configurations are complete. So you can get the flavor and other configurations at this stage. In BasePlugin createAndroidTasks, is called VariantManager. CreateAndroidTasks to get the job done. Create a task, will be generated by populateVariantDataList flavor related data structure, and then call createTasksForVariantData create flavor the corresponding task. Look at each of these two methods do 1. PopulateVariantDataList in method, will first according to the flavor and create the corresponding dimension combination, lying in the flavorComboList, Called after createVariantDataForProductFlavors create the corresponding VariantData. Among them, there are several important methods:

// Create a flavor and dimension combination
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());
}
Copy the code

Create VariantData are BaseVariantData subclasses, which save some tasks, you can take a look at BaseVariantData in some important structures, to get a general understanding of BaseVariantData.

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; / / testing the manifest
    public AndroidTask<PackageSplitRes> packageSplitResourcesTask; // Package resources
    public AndroidTask<PackageSplitAbi> packageSplitAbiTask;
    public RenderscriptCompile renderscriptCompileTask; 
    public MergeResources mergeResourcesTask; // Merge resources
    public ManifestProcessorTask processManifest; / / handle the manifest
    public MergeSourceSetFolders mergeAssetsTask; / / merge assets
    public GenerateBuildConfig generateBuildConfigTask; / / generated BuildConfig
    public GenerateResValues generateResValuesTask;
    public Sync processJavaResourcesTask;
    public NdkCompile ndkCompileTask; / / the NDK compiler
    public JavaCompile javacTask; 
    public Task compileTask;
    public Task javaCompilerTask; // Java file compilation
    // ...
}
Copy the code

The next step is to create tasks that are stored in VariantData.

2. CreateTasksForVariantData created over the variant data, will give each variantData create the corresponding task, and the corresponding task assembleXXXTask, prebuildXXX, GenerateXXXSource, generateXXXResources, generateXXXAssets, processXXXManifest, etc., focusing on several methods:

VariantManager.createAssembleTaskForVariantData() / / create assembleXXXTask
TaskManager.createTasksForVariantScope() / / is an abstract class, specific implementation in ApplicationTaskManager. CreateTasksForVariantScope ()
TaskManager.createPostCompilationTasks() // Create the.class to dex task and create the transformTask. The transform we create is added in this stage and is called in addCompileTask

/ / createTasksForVariantScope is an abstract method, the specific implementation in the subclass, can have a look at ApplicationTaskManager. CreateTasksForVariantScope ()
/ / createTasksForVariantScope implementation, if there is a need to look at in the business of task related to the source code, you can come here to find
void createTasksForVariantScope() {
    this.createCheckManifestTask(tasks, variantScope); / / testing the manifest
    this.handleMicroApp(tasks, variantScope);
    this.createDependencyStreams(tasks, variantScope);
    this.createApplicationIdWriterTask(tasks, variantScope); // application id 
    this.createMergeApkManifestsTask(tasks, variantScope); / / merge the manifest
    this.createGenerateResValuesTask(tasks, variantScope);
    this.createRenderscriptTask(tasks, variantScope);
    this.createMergeResourcesTask(tasks, variantScope, true); // Merge resource files
    this.createMergeAssetsTask(tasks, variantScope, (BiConsumer)null); / / merge assets
    this.createBuildConfigTask(tasks, variantScope); / / generated BuildConfig
    this.createApkProcessResTask(tasks, variantScope); // Process resources
    this.createProcessJavaResTask(tasks, variantScope);
    this.createAidlTask(tasks, variantScope); / / processing aidl
    this.createShaderTask(tasks, variantScope);
    this.createNdkTasks(tasks, variantScope); / / handle the NDK
    this.createExternalNativeBuildJsonGenerators(variantScope);
    this.createExternalNativeBuildTasks(tasks, variantScope);
    this.createMergeJniLibFoldersTasks(tasks, variantScope); / / merge jni
    this.createDataBindingTasksIfNecessary(tasks, variantScope); / / processing databinding
    this.addCompileTask(tasks, variantScope); 
    createStripNativeLibraryTask(tasks, variantScope);
    this.createSplitTasks(tasks, variantScope);
    this.createPackagingTask(tasks, variantScope, buildInfoWriterTask); / / packaging apk
    this.createLintTasks(tasks, variantScope); // lint 
}

/ / createPostCompilationTasks implementation:
// Handle Android Transform
void createPostCompilationTasks() {
    for (int i = 0, count = customTransforms.size(); i < count; i++) {
        Transform transform = customTransforms.get(i);
        / / TransformManager. AddTransform is actually created a Task of the 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); }}); }}Copy the code

Six, summarized

The main flow of the Android Gradle Plugin is basically over. The main flow chart is shown below

Here are a few key points:

  1. The com.Android. application entry class is AppPlugin, but most of the work is done in BasePlugin
  2. Seen in the build. Gradle android {} a DSL is in BasePlugin configureExtension statement ()
  3. The main task is in BasePlugin. CreateAndroidTasks generated in the ()
  4. The implementation of the main task can be found in TaskManager
  5. The transform transforms into a TransformTask

About me