preface
The beginning of the story is like this.
When reading "Android Development Master Course" before, it was mentioned in the column of startup optimization that systrace + function plug-in is a good way of Caton troubleshooting.
The main way is through Transform + Asm. I believe you are an old acquaintance.
When using the Demo for learning, I found that the Demo didn't work after upgrading AGP(Android Gradle Plugin) to 4.0.0.
After analyzing the Demo, it is found that the code does not use the method of directly registering Transform for stake insertion, but obtains the Task corresponding to transformClassesWithDexBuilderForxxx, and sets the Transform in the Task as the currently implemented Transform through reflection:
Then why can't this method work in AGP 4.0.0?
AS we all know, during the process of building code, AS will print the tasks executed into the Build window. By observing the construction process of different versions of AGP, it is found that transformClassesWithDexBuilderForxxx is not printed in the construction process of 4.0.0.
Ah, this
A good Task will be gone if it's gone. Well, see you for the AGP source code!
This article first briefly analyzes the role of AGP with you, and then analyzes the source code of Transform with you in subsequent articles.
1, Basic preparation
Before analyzing the source code, I think you should have a basic understanding of the Android packaging process, at least the packaging process shown in the figure below:
Otherwise, you may not understand the technical terms below.
2, How to open AGP source code
When looking at the AGP code, I was always struggling with whether to download the AGP source code. Later, I listened to the suggestions of my colleagues and leaders and directly used the code relied on by the project for analysis.
There are two main reasons:
- The source code of AGP is too large, 30g, and the version is very old
- Using the AGP code that the project depends on is simple
Just add it to the project
implementation "com.android.tools.build:gradle:4.1.1"
You can view it.
3, Code analysis
By the way, the version of AGP is 4.1.1.
The first step is to find AppPlugin
In AS, if a project is created, it is added under the main module by default:
apply plugin: 'com.android.application'
Small partners who have customized Plugin know that there must be a COM in the source code android. application. The properties file corresponds to it. This is our Plugin entry.
Global search com android. Application, open com android. application. Properties, which are:
implementation-class=com.android.build.gradle.AppPlugin
Press the "Command" button and click the source code. It is found that another Plugin is declared in AppPlugin. Finally, it jumps to:
implementation-class=com.android.build.gradle.internal.plugins.AppPlugin
The package name is different from the previous one. This is our final entry.
Do you have such doubts? I'll add apply plugin: com android. Application, when will this code be called?
I don't know if you have noticed that every time you change build When creating a Gradle file, the AS will ask us to click the "Sync Now" button. After clicking, the configuration process in Gradle will be triggered, and finally the Plugin#apply method will be run. You can verify it when customizing the Plugin.
Step 2 AppPlugin
The parent class of AppPlugin is AbstractAppPlugin, and the parent class of AbstractAppPlugin is BasePlugin. The plug-in starts in the BasePlugin#apply method:
@Override public final void apply(@NonNull Project project) { CrashReporting.runAction( () -> { basePluginApply(project); pluginSpecificApply(project); }); }
Here, we only need to focus on the two methods basePluginApply and pluginSpecificApply in the method block.
Enter the key method basePluginApply method. In the early stage of this method, a lot of inspection work has been done, including path, version and AGP version. After that, a lot of monitoring work has been done. Take a look at the source code:
private void basePluginApply(@NonNull Project project) { // ... Code omission // Dependency check DependencyResolutionChecks.registerDependencyCheck(project, projectOptions); // ... Omit path check, module check, etc., and build parameter listener // AGP version check AgpVersionChecker.enforceTheSamePluginVersions(project); // Build listener for process Task execution RecordingBuildListener buildListener = ProfilerInitializer.init(project, projectOptions); ProfileAgent.INSTANCE.register(project.getName(), buildListener); threadRecorder = ThreadRecorder.get(); //... Code omission // a key // 1. Configuration items threadRecorder.record( ExecutionType.BASE_PLUGIN_PROJECT_CONFIGURE, project.getPath(), null, this::configureProject); // 2. Configuration extension threadRecorder.record( ExecutionType.BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION, project.getPath(), null, this::configureExtension); // 3. Create a Task threadRecorder.record( ExecutionType.BASE_PLUGIN_PROJECT_TASKS_CREATION, project.getPath(), null, this::createTasks); }
The key methods I have marked out are configuring items, configuring extensions and creating tasks.
Step 3 configure Project
It should be noted that this configuration does not correspond to the Gradle life cycle, but does some configuration work for the current Project.
private void configureProject() { // ... Perform a large number of services // Dependent version dependent Provider<ConstraintHandler.CachedStringBuildService> cachedStringBuildServiceProvider = new ConstraintHandler.CachedStringBuildService.RegistrationAction(project) .execute(); // maven cache related Provider<MavenCoordinatesCacheBuildService> mavenCoordinatesCacheBuildService = new MavenCoordinatesCacheBuildService.RegistrationAction( project, cachedStringBuildServiceProvider) .execute(); // Dependent library correlation new LibraryDependencyCacheBuildService.RegistrationAction(project).execute(); // aapt preparation new Aapt2WorkersBuildService.RegistrationAction(project, projectOptions).execute(); new Aapt2DaemonBuildService.RegistrationAction(project).execute(); new SyncIssueReporterImpl.GlobalSyncIssueService.RegistrationAction( project, SyncOptions.getModelQueryMode(projectOptions)) .execute(); // SDK related 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()); // ... ellipsis createLintClasspathConfiguration(project); }
My understanding of the above code is the preparation before creating a Task. Moreover, the xxxAction described in the above code is also very confusing and does not correspond to the Action in the Task.
Step 4 confirm the extension
The corresponding method of confirming the extension is configureExtension.
Usually build. Com under the app module Gradle files often have such configurations:
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 JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } }
The purpose of configureExtension is to convert this kind of script information into information that can be recognized by the code:
private void configureExtension() { // Help class for Gradle DSL DslServices dslServices = globalScope.getDslServices(); final NamedDomainObjectContainer<BaseVariantOutput> buildOutputs = project.container(BaseVariantOutput.class); // ... Code omission // ... variant's factory class and management, etc variantFactory = createVariantFactory(projectServices, globalScope); variantInputModel = new LegacyVariantInputManager( dslServices, variantFactory.getVariantType(), new SourceSetManager( project, isPackagePublished(), dslServices, new DelayedActionsExecutor())); // Create 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); // ... }
A brief look at the code shows that most of the code is related to variant and extension.
Pay more attention to the generated extension. BasePlugin#createExtension is an abstract method, which is finally handed over to 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)); }
At first glance, it seems unfamiliar, but if you have developed a plug-in, you must know AppExtension, which can get the build mentioned above Any information in android {} under gradle.
Step 5 create a Task
This should be the most important step. Creating a Task is in the BasePlugin#createTasks method:
private void createTasks() { // Register tasks that are not related to Variant threadRecorder.record( ExecutionType.TASK_MANAGER_CREATE_TASKS, project.getPath(), null, () -> TaskManager.createTasksBeforeEvaluate( globalScope, variantFactory.getVariantType(), extension.getSourceSets())); // After the Gradle configuration phase is completed, register the tasks related to Variant project.afterEvaluate( CrashReporting.afterEvaluate( p -> { variantInputModel.getSourceSetManager().runBuildableArtifactsActions(); threadRecorder.record( ExecutionType.BASE_PLUGIN_CREATE_ANDROID_TASKS, project.getPath(), null, this::createAndroidTasks); })); }
There are two main methods in this method:
- Task manager #createtasksbeforeevaluate: the static method indicates that a batch of tasks will be created before Project configuration.
- createAndroidTasks: register a callback after the configuration life cycle is completed. After the Project configuration is completed, the Variant has been determined, and a batch of tasks will be created.
Task manager #createtasksbeforeevaluate contains a large section of code for registering tasks. If you are interested, you can check the source code yourself.
Step 6 create a Task after configuration
Wait until Project enters the callback of the configuration life cycle, and enter the method 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 some 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); // Note 1 create Variant variantManager.createVariants(); List<ComponentInfo<VariantT, VariantPropertiesT>> variants = variantManager.getMainComponents(); TaskManager<VariantT, VariantPropertiesT> taskManager = createTaskManager( variants, variantManager.getTestComponents(), !variantInputModel.getProductFlavors().isEmpty(), globalScope, extension, threadRecorder); // Note 2 create Task taskManager.createTasks(); // ... // Note 3: create Task configure compose related tasks 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(); }
First, it can be seen from note 1 that all variants have been created in this step.
Next, from notes 2 and 3, we can see that createAndroidTasks has used taskManager to create tasks twice.
Step 7 TaskManager creates multiple tasks for the first time
The TaskManager#createTasks method used to create a Task for the first time. Click this method:
public void createTasks() { // lint related tasks 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(); // Focus on traversing variant create tasks for all variants (main and tests) for (ComponentInfo<VariantT, VariantPropertiesT> variant : variants) { createTasksForVariant(variant, variants); } // Test related tasks for (ComponentInfo< TestComponentImpl<? extends TestComponentPropertiesImpl>, TestComponentPropertiesImpl> testComponent : testComponents) { createTasksForTest(testComponent); } // Information record related tasks createReportTasks(); }
There are still many registered tasks, Lint, tasks related to testing and information recording, etc.
The most important thing is to get the Variant created above, and traverse and execute the createTasksForVariant method. Let's see which methods it registers for each Variant:
private void createTasksForVariant( @NonNull ComponentInfo<VariantT, VariantPropertiesT> variant, @NonNull List<ComponentInfo<VariantT, VariantPropertiesT>> variants) { // ... ellipsis createAssembleTask(variantProperties); if (variantType.isBaseModule()) { createBundleTask(variantProperties); } doCreateTasksForVariant(variant, variants); }
You must be familiar with the method of createassemblyask, because we use the commands of assemblydebug or assemblyrelease every time we package. This method is to create the Task corresponding to the assembly.
The docreatestatsforvariant method is a method to create tasks related to Variant. However, in TaskManager, it is an abstract method, which is implemented by ApplicationTaskManager.
So what tasks are created in it? Turn back and the picture in the back will tell you!
Step 8 TaskManager creates multiple tasks for the second time
The second time multiple tasks are created, the TaskManager#createPostApiTasks method is called, which is mainly related to ViewBinding, DataBinding and Kotlin compilation. You can take a look at them if you are interested.
I won't analyze them with the students one by one here. Just look at the picture:
Briefly explain:
- Blue: tasks registered by createTasksBeforeEvaluate before Gradle configuration phase
- Orange: Task created after the Gradle configuration phase is completed
- Red: important tasks
- Arrow: dependencies (not all)
Of course, I didn't list all the tasks, and the dependencies only listed what I saw (there was too much code to read).
If we combine the above picture with the previous official packaging flow chart, we find that many can be corresponding:
- There are processing tasks such as AIDL, Source Code and Resource file in front
- There are tasks related to Class compilation and code confusion in the medium term
- Later, create and merge Dex and package Apk related tasks
Moreover, there are dependencies between tasks (not shown in the figure). For example, I command:
./gradlew assembleDebug
This command will call the Task corresponding to assemblydebug. Before that, it will complete the previously dependent tasks, such as resource processing, compiling related, packaging and generating the APK we want, etc.
Here, the source code is almost analyzed. Back to the second step, BasePlugin also executes the pluginSpecificApply method in the apply method, but this method is an empty method.
summary
The purpose of this article is to give you an outline of AGP. What has AGP done?
It can be found that most of the tasks registered in AGP are for packaging services, and each small Task is a screw for packaging the assembly line.
The next article will analyze the process of Transform with you. See you in the next issue!
If you think this article is good, "like" is the best affirmation!
Reference article:
< 🍵 Supplement Android skill tree -- from AGP construction process to APK packaging process
[Android cultivation manual] Gradle chapter - Gradle source code analysis