preface

The story begins like this.

Before reading “Android development master class”, there is a column of startup optimization mentioned that Systrace + function peg is a good way to check the congestion.

The main way to do this is through Transform + Asm.

When I upgrade AGP(Android Gradle Plugin, Android Gradle package Plugin) to 4.0.0, the Demo does not work.

Analysis about the Demo, found that the code does not use direct registration Transform way pile, but get transformClassesWithDexBuilderForxxx corresponding Task, Set the Transform in this Task to the currently implemented Transform by reflection:

So why didn’t this work with AGP 4.0.0?

AS is known to us, during the process of building code, AS will print all tasks executed in the Build window. By observing the construction process of different versions of AGP, Found no print transformClassesWithDexBuilderForxxx 4.0.0 build process.

Oh, this…

AGP source code see AGP source code

In this article, we will briefly analyze the role of AGP, and then we will analyze the source code of Transform.

First, basic preparation

Before analyzing the source code, YOU should have a basic understanding of the Android packaging process, at least the following packaging process:

Otherwise, you may not be aware of the technical terms listed below.

Two, AGP source code open way

When I was looking at THE AGP code, I was struggling with whether to download the source code of AGP. Later, I listened to my colleague’s advice and directly used the code that the project relied on for analysis.

There are two main reasons:

  1. AGP source code is too big, 30 GB, and the version is very old
  2. Using AGP code that your project depends on is simple

Just add it to the project

Implementation "com. Android. Tools. Build: gradle: 4.4.1." "Copy the code

Can view.

Iii. Code analysis

By the way, the AGP version is 4.1.1.

The first step is to find the AppPlugin

In AS, if a project is created, it is added under the main module by default:

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

Custom Plugin’s friends all know that the source code. There must be a com in android. Application. The properties file and at the same time, this is we the entrance to the Plugin.

Global search com. Android. Application, open the com. Android. Application. The properties, content is:

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

Click the “Command” button to click the source code, find a Plugin declared in the AppPlugin, and finally jump to:

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

The package name is different from the previous one, and this is our final entrance.

Do you have any doubts? I added the apply plugin: com.android. Application. When will this code be called?

If you’ve noticed, every time we make a change to our build.gradle file, AS tells us to click on the “Sync Now” button, which triggers the configuration process in gradle, and finally runs Plugin#apply. You can verify this when you customize your Plugin.

The second step AppPlugin

AbstractAppPlugin > AbstractAppPlugin > AbstractAppPlugin > AbstractAppPlugin > AbstractAppPlugin > AbstractAppPlugin > AbstractAppPlugin > AbstractAppPlugin > AbstractAppPlugin > AbstractAppPlugin > AbstractAppPlugin > AbstractAppPlugin > AbstractAppPlugin > AbstractAppPlugin < AbstractAppPlugin >

@Override
public final void apply(@NonNull Project project) {
    CrashReporting.runAction(
            () -> {
                basePluginApply(project);
                pluginSpecificApply(project);
            });
}
Copy the code

Here we only need to focus on the two methods in the method block, basePluginApply and pluginSpecificApply.

BasePluginApply () {basePluginApply () {basePluginApply ();

private void basePluginApply(@NonNull Project project) { // ... Code to check the DependencyResolutionChecks omitted / / dependence. RegisterDependencyCheck (project, projectOptions); / /... Omit the path to check, check module, build parameters, such as the listener / / AGP version check AgpVersionChecker enforceTheSamePluginVersions (project); RecordingBuildListener buildListener = profilerInitializer.init (project, projectOptions); ProfileAgent.INSTANCE.register(project.getName(), buildListener); threadRecorder = ThreadRecorder.get(); / /... ThreadRecorder. Record (executionType.base_plugin_project_configure, project.getPath(), null, this::configureProject); ThreadRecorder. Record (executionType.base_plugin_project_base_extension_creation, project.getPath(), null, this::configureExtension); // create Task threadRecorder. Record (executionType.base_plugin_project_tasks_creation, project.getPath(), null, this::createTasks); }Copy the code

The key methods I have highlighted are configuring projects, configuring extensions, and creating tasks.

Step 3 Configure Project

It is important to note that this configuration is not for the Gradle lifecycle, but for the current Project.

private void configureProject(a) {
    / /... Perform a large number of services
    // Dependent version dependent
    Provider<ConstraintHandler.CachedStringBuildService> cachedStringBuildServiceProvider =
            new ConstraintHandler.CachedStringBuildService.RegistrationAction(project)
                    .execute();
    // Maven cache is relevant
    Provider<MavenCoordinatesCacheBuildService> mavenCoordinatesCacheBuildService =
            new MavenCoordinatesCacheBuildService.RegistrationAction(
                    project, cachedStringBuildServiceProvider)
                    .execute();
    // Depend on the library
    new LibraryDependencyCacheBuildService.RegistrationAction(project).execute();
    // aAPT is ready
    new Aapt2WorkersBuildService.RegistrationAction(project, projectOptions).execute();
    new Aapt2DaemonBuildService.RegistrationAction(project).execute();
    new SyncIssueReporterImpl.GlobalSyncIssueService.RegistrationAction(
            project, SyncOptions.getModelQueryMode(projectOptions))
            .execute();
    / / SDK
    Provider<SdkComponentsBuildService> sdkComponentsBuildService =
            new SdkComponentsBuildService.RegistrationAction(
                    project,
                    projectOptions,
                    project.getProviders()
                            .provider(() -> extension.getCompileSdkVersion()),
                    project.getProviders()
                            .provider(() -> extension.getBuildToolsRevision()),
                    project.getProviders().provider(() -> extension.getNdkVersion()),
                    project.getProviders().provider(() -> extension.getNdkPath()))
                    .execute();
    // Enforce minimum versions of certain plugins
    GradlePluginUtils.enforceMinimumVersionsOfPlugins(project, issueReporter);
    // Apply the Java plugin
    project.getPlugins().apply(JavaBasePlugin.class);
    dslServices =
            new DslServicesImpl(
                    projectServices,
                    new DslVariableFactory(syncIssueReporter),
                    sdkComponentsBuildService);
    // Message printing service registration
    MessageReceiverImpl messageReceiver =
            new MessageReceiverImpl(
                    SyncOptions.getErrorFormatMode(projectOptions),
                    projectServices.getLogger());
    / /... omit
    createLintClasspathConfiguration(project);
}
Copy the code

My understanding of the above code is that it is preparation for creating a Task, and the xxxactions described in the above code are also confusing and do not correspond to actions in a Task.

Step 4 Confirm the extension

The method to verify that the extension corresponds to is configureExtension.

The build.gradle file in your app module usually has something like this:

android { compileSdk 32 defaultConfig { applicationId "com.qidian.test" minSdk 21 targetSdk 32 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx. Test. Runner. AndroidJUnitRunner"} buildTypes {release {minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } debug { minifyEnabled false } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 TargetCompatibility JavAns.version_1_8} kotlinOptions {jvmTarget = '1.8'}}Copy the code

The purpose of configureExtension is to convert such script information into information that code can recognize:

private void configureExtension(a) {
    // Gradle DSL helper classes
    DslServices dslServices = globalScope.getDslServices();
    final NamedDomainObjectContainer<BaseVariantOutput> buildOutputs =
            project.container(BaseVariantOutput.class);
    / /... The code is omitted
    / /... The factory class and management of Variant, etc
    variantFactory = createVariantFactory(projectServices, globalScope);
    variantInputModel =
            new LegacyVariantInputManager(
                    dslServices,
                    variantFactory.getVariantType(),
                    new SourceSetManager(
                            project,
                            isPackagePublished(),
                            dslServices,
                            new DelayedActionsExecutor()));
    // Create an extension
    extension =
            createExtension(
                    dslServices, globalScope, variantInputModel, buildOutputs, extraModelInfo);
    globalScope.setExtension(extension);
    variantManager =
            new VariantManager<>(
                    globalScope,
                    project,
                    projectServices.getProjectOptions(),
                    extension,
                    variantFactory,
                    variantInputModel,
                    projectServices,
                    threadRecorder);
    registerModels(
            registry,
            globalScope,
            variantInputModel,
            extension,
            extraModelInfo);
    // create default Objects, signingConfig first as its used by the BuildTypes.
    variantFactory.createDefaultComponents(variantInputModel);
    // ... 
}
Copy the code

A brief look at the code shows that most of the code is related to variant and extension.

Taking a closer look at the generated extension, BasePlugin#createExtension is an abstract method that was eventually passed to the AppPlugin#createExtension method:

protected AppExtension createExtension(
        @NonNull DslServices dslServices,
        @NonNull GlobalScope globalScope,
        @NonNull
                DslContainerProvider<DefaultConfig, BuildType, ProductFlavor, SigningConfig>
                dslContainers,
        @NonNull NamedDomainObjectContainer<BaseVariantOutput> buildOutputs,
        @NonNull ExtraModelInfo extraModelInfo) {
    return project.getExtensions()
            .create(
                    "android",
                    getExtensionClass(),
                    dslServices,
                    globalScope,
                    buildOutputs,
                    dslContainers.getSourceSetManager(),
                    extraModelInfo,
                    new ApplicationExtensionImpl(dslServices, dslContainers));
}
Copy the code

This may seem unfamiliar at first, but if you’ve ever developed a plugin, you’ll know AppExtension, which gets any information from android {} under build.gradle mentioned above.

Step 5 Create Task

The BasePlugin#createTasks method is used to create the Task:

private void createTasks(a) {
    // Register tasks unrelated to Variant
    threadRecorder.record(
            ExecutionType.TASK_MANAGER_CREATE_TASKS,
            project.getPath(),
            null,
            () ->
                    TaskManager.createTasksBeforeEvaluate(
                            globalScope,
                            variantFactory.getVariantType(),
                            extension.getSourceSets()));
    // After Gradle configuration is complete, register variation-related tasks
    project.afterEvaluate(
            CrashReporting.afterEvaluate(
                    p -> {
                        variantInputModel.getSourceSetManager().runBuildableArtifactsActions();
                        threadRecorder.record(
                                ExecutionType.BASE_PLUGIN_CREATE_ANDROID_TASKS,
                                project.getPath(),
                                null.this::createAndroidTasks);
                    }));
}
Copy the code

There are two main methods in this method:

  • TaskManager#createTasksBeforeEvaluateStatic means that a batch of tasks are created before a Project is configured.
  • createAndroidTasks: registers a callback after the configuration life cycle is complete. After the Project configuration is complete and the Variant has been confirmed, another batch of tasks will be created.

TaskManager# createTasksBeforeEvaluate is a large section of the registered Task code inside, interested can look up the source code.

Step 6 Create a Task after the configuration

Wait for Project to enter the configuration lifecycle callback and go to createAndroidTasks:

final void createAndroidTasks() { if (extension.getCompileSdkVersion() == null) { // ... CompileSdkVersion related} //... // 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.");  } / /... / / set a few configuration 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); } AnalyticsUtil.recordFirebasePerformancePluginVersion(project); A / / comment to create the Variant variantManager. CreateVariants (); List<ComponentInfo<VariantT, VariantPropertiesT>> variants = variantManager.getMainComponents(); TaskManager<VariantT, VariantPropertiesT> taskManager = createTaskManager( variants, variantManager.getTestComponents(), ! variantInputModel.getProductFlavors().isEmpty(), globalScope, extension, threadRecorder); Create Task taskManager.createTasks(); / /... / / comment three created Task configure compose related tasks. The taskManager. CreatePostApiTasks (); // now publish all variant artifacts for non test variants since // tests don't publish anything. for (ComponentInfo<VariantT, VariantPropertiesT> component : variants) { component.getProperties().publishBuildArtifacts(); } / /... variantManager.setHasCreatedTasks(true); // notify our properties that configuration is over for us. GradleProperty.Companion.endOfEvaluation(); }Copy the code

First, you can see from comment 1 that all variants have been created in this step.

Then, from comments 2 and 3, we can see that createAndroidTasks creates tasks using taskManager twice.

Step 7 TaskManager creates multiple tasks for the first time

The TaskManager#createTasks method used to create the Task for the first time:

Public void createTasks() {// Lint-related Task TaskFactory.register (new) PrepareLintJarForPublish.CreationAction(globalScope)); // create a lifecycle task to build the lintChecks dependencies taskFactory.register( COMPILE_LINT_CHECKS_TASK, task -> task.dependsOn(globalScope.getLocalCustomLintChecks())); // Create top level test tasks. createTopLevelTestTasks(); / / key, Variants (variants and tests) for (ComponentInfo<VariantT, variantproperties > Variant: variants) { createTasksForVariant(variant, variants); } // Test related Task for (ComponentInfo< testEntimpl <? extends TestComponentPropertiesImpl>, TestComponentPropertiesImpl> testComponent : testComponents) { createTasksForTest(testComponent); } // Task createReportTasks(); }Copy the code

There are still many tasks registered, such as Lint, testing and logging related tasks.

The most important method is to obtain the variants created above, and iterate through the createTasksForVariant method. Let’s see what methods it registers for each Variant:

private void createTasksForVariant(
        @NonNull ComponentInfo<VariantT, VariantPropertiesT> variant,
        @NonNull List<ComponentInfo<VariantT, VariantPropertiesT>> variants) {
    / /... omit
    createAssembleTask(variantProperties);
    if (variantType.isBaseModule()) {
        createBundleTask(variantProperties);
    }
    doCreateTasksForVariant(variant, variants);
}
Copy the code

The createAssembleTask method, which you’ll be familiar with because we use commands like assembleDebug or assembleRelease every time we package, creates the AssembleTask.

The doCreateTasksForVariant method is a method for creating variation-related tasks, but in TaskManager it is an abstract method that is implemented by ApplicationTaskManager.

So what tasks are created in it? Scroll back and the pictures will tell you!

Step 8 TaskManager creates multiple tasks for the second time

TaskManager#createPostApiTasks (ViewBinding, DataBinding, Kotlin);

Here is not a and students analysis, directly look at the picture:

Here’s a quick explanation:

  • Blue: before Gradle configuration stagecreateTasksBeforeEvaluateRegistration of a Task
  • Orange: Tasks created after the Gradle configuration phase is complete
  • Red: Important Task
  • Arrows: Dependencies (not all)

Of course, I don’t list all tasks, and dependencies only list what I see (too much code, not all read).

If we combine the above picture with the previous official packaging flow chart, we find that many of them can be matched:

  1. There are AIDL, Source Code, Resource files and other processing tasks
  2. In the middle, there are tasks related to Class compilation and code obfuscation
  3. In the later stage, there was the creation and merging of Dex and packaging of APK-related tasks

Also, there are dependencies between tasks (not shown in the figure), such as when I run the following command:

./gradlew assembleDebug
Copy the code

This command will call the assembleDebug Task, after which it will finish executing the previously dependent tasks such as resource handling, compile related, packaging to generate the APK we want, and so on.

At this point, the source code analysis is almost complete, back to the second step, BasePlugin in the apply method, also executes the pluginSpecificApply method, but this method is an empty method.

conclusion

The purpose of this article is to give you an outline of AGP. What does AGP mainly do?

It can be found that most tasks registered in AGP are for packaging services, and each small Task is a screw to package the assembly line.

In the next article, I will analyze the process of Transform. See you next time!

If you think this article is good, “like” is the best affirmation!

Reference article:

“🍵 to supplement the Android skill tree — from AGP build process to APK packaging process” “Android Training manual” Gradle chapter — Source code analysis