The secret behind the Android Studio source code analysis series one click Run

foreword

Keywords: the secret behind Android studio run, android compilation process

提示:可能需要大概30分钟阅读时间

Background: Usually in the development process, most people click the Run' button to run the app, Android Studio will start, the code miraculously becomes APK, installed on the mobile phone, and the APP interface is displayed. What's going on behind the scenes?


1. The secret behind clicking Run?

1.1 Gradle tasks behind Run

To understand this problem, you need to understand the Gradle build process. Let's look at the Gradle build process.

The Gradle life cycle is divided into three phases, namely Initialization (initialization phase), Configuration (configuration phase), and Execution (execution phase), and the execution phase will execute a series of tasks for the main construction work .

Then the construction work behind the natural Run button is also composed of a series of tasks, so do we have a way to check what these tasks are? Android Studio provides a powerful logging function, which mainly requires the following three steps:

  1. 1.1 Click View > Tool Windows > Build , and the Build process will be displayed below the Android Studio interface ;     

From the above figure, you can see that the overall process after clicking Build is divided into

1.1.1.1 Load buid

1.1.1.2 Config build 

1.1.1.3 Calculate task graph

1.1.1.4 run task

So let's expand the above and see what has been done specifically?

1.1.2 Expanded

At this point, if we click the Run button, let's see what will happen

 , through observation, we found that it is to go through the previous build process, and then add install, that is to say, the compiled Apk is installed on the user's mobile phone

1.2 Task name

completed successfully	1 s 382 ms
Run build	981 ms
Load build	3 ms
Evaluate settings	2 ms
Finalize build cache configuration	
Configure build	122 ms
Load projects	2 ms
Calculate task graph	148 ms
Run tasks	598 ms
:app:buildInfoDebugLoader	9 ms
:app:preBuild	1 ms
:app:preDebugBuild	10 ms
:app:compileDebugAidl	
:app:checkDebugManifest	1 ms
:app:compileDebugRenderscript	
:app:generateDebugBuildConfig	2 ms
:app:prepareLintJar	
:app:generateDebugSources	
:app:javaPreCompileDebug	21 ms
:app:mainApkListPersistenceDebug	
:app:generateDebugResValues	3 ms
:app:generateDebugResources	
:app:mergeDebugResources	97 ms
:app:createDebugCompatibleScreenManifests	2 ms
:app:processDebugManifest	72 ms
:app:processDebugResources	25 ms
:app:compileDebugJavaWithJavac	27 ms
:app:instantRunMainApkResourcesDebug	1 ms
:app:mergeDebugShaders	2 ms
:app:compileDebugShaders	3 ms
:app:generateDebugAssets	
:app:mergeDebugAssets	3 ms
:app:validateSigningDebug	
:app:signingConfigWriterDebug	1 ms
:app:processInstantRunDebugResourcesApk	1 ms
:app:checkManifestChangesDebug	5 ms
:app:checkDebugDuplicateClasses	2 ms
:app:transformClassesWithExtractJarsForDebug	1 ms
:app:transformClassesWithInstantRunVerifierForDebug	26 ms
:app:transformClassesWithDependencyCheckerForDebug	9 ms
:app:mergeDebugJniLibFolders	1 ms
:app:processDebugJavaRes	
:app:transformNativeLibsWithMergeJniLibsForDebug	8 ms
:app:transformResourcesWithMergeJavaResForDebug	8 ms
:app:transformNativeLibsAndResourcesWithJavaResourcesVerifierForDebug	1 ms
:app:transformClassesWithInstantRunForDebug	24 ms
:app:transformClassesAndClassesEnhancedWithInstantReloadDexForDebug	14 ms
:app:incrementalDebugTasks	1 ms
:app:preColdswapDebug	1 ms
:app:fastDeployDebugExtractor	1 ms
:app:generateDebugInstantRunAppInfo	2 ms
:app:transformClassesWithDexBuilderForDebug	31 ms
:app:transformDexArchiveWithExternalLibsDexMergerForDebug	3 ms
:app:transformDexArchiveWithDexMergerForDebug	5 ms
:app:transformDexWithInstantRunDependenciesApkForDebug	2 ms
:app:instantRunSplitApkResourcesDebug	3 ms
:app:transformDexWithInstantRunSlicesApkForDebug	2 ms
:app:packageDebug	10 ms
:app:buildInfoGeneratorDebug	12 ms
:app:compileDebugSources	1 ms
:app:assembleDebug	

The above tasks can be roughly divided into five stages:

  1. Preparation of dependencies : At this stage gradle detects whether all libraries that the module depends on are ready . If this module depends on another module, the other module must also be compiled;
  2. Merging resources and processing Manifest : package resources and Manifest files;
  3. Compiling : processing compiler annotations, source code is compiled into bytecode;
  4. Post-processing (transform ) : All tasks prefixed with "transform" are processed at this stage;
  5. Packaging and publishing : library generates .aar file, application generates .apk file

1.3 Detailed description of some Tasks

  1. mergeDebugResources : decompress all aar packages , and merge all resource files into relevant directories;
  2. processDebugManifest : Merge the nodes in AndroidManifest.xml in all aar packages into the AndroidManifest.xml of the project
  3. processDebugResources

a. Call aapt to generate the project and all R.java that aar depends on b. Generate resource index file c. Output symbol table

  1. compileDebugJavaWithJavac : used to compile java files into class files

2. The relationship between Android Gradle Plugin and AS

1. Android Gradle Plugin core source code analysis

With the above preliminary understanding, let's take a look at how the Android Gradle Plugin implements the build process.

The classes corresponding to the apply plugin: 'com.android.application' and apply plugin:'com.android.library' we use are AppPlugin and LibraryPlugin respectively. They all inherit BasePlugin:

public abstract class BasePlugin<E extends BaseExtension2> implements Plugin<Project>{
    @Override
    public final void apply(@NonNull Project project) {
      	//核心方法
        basePluginApply(project);
      	//因为 apply 是 final
      	//所以提供一个 pluginSpecificApply 用于 AppPlugin 和 LibraryPlugin 加入特殊逻辑
        pluginSpecificApply(project);
    }
}

I am very happy to see that the rewritten apply method is final, that is to say, we only need to analyze the logic in BasePlugin. The core method is basePluginApply, the source code is as follows:

private void basePluginApply(@NonNull Project project) {
      	//核心三个方法
				configureProject();
      	configureExtension();
				createTasks();
    }

configureProject()

    private void configureProject() {
   			//主构建器类,它提供了处理构建的所有数据,比如 DefaultProductFlavor、DefaultBuildType 以及一些依赖项,在执行的时候使用它们特定的构建步骤
        AndroidBuilder androidBuilder = new AndroidBuilder();
				//...
        // 依赖 Java 插件
        project.getPlugins().apply(JavaBasePlugin.class);
				//Plugin 的全局变量
        new GlobalScope()
    }

configureExtension()

private void configureExtension() {
        ObjectFactory objectFactory = project.getObjects();
      	//创建 BuildType、ProductFlavor、SigningConfig、BaseVariantOutput 的容器
        final NamedDomainObjectContainer<BuildType> buildTypeContainer =
                project.container());
        final NamedDomainObjectContainer<ProductFlavor> productFlavorContainer =
                project.container());
        final NamedDomainObjectContainer<SigningConfig> signingConfigContainer =
                project.container());

        final NamedDomainObjectContainer<BaseVariantOutput> buildOutputs =
                project.container(BaseVariantOutput.class);

        project.getExtensions().add("buildOutputs", buildOutputs);

        sourceSetManager = new SourceSetManager();
				//创建 BaseExtension
        extension = createExtension();

        globalScope.setExtension(extension);

        variantFactory = createVariantFactory(globalScope, extension);
				//创建 TaskManager
        taskManager = createTaskManager();
				//创建 VariantManager
        variantManager = new VariantManager();

        registerModels(registry, globalScope, variantManager, extension, extraModelInfo);
        variantFactory.createDefaultComponents(
                buildTypeContainer, productFlavorContainer, signingConfigContainer);
    }

createTask()

private void createTasks() {
      	//注册一些默认 Task,比如 uninstallAll、deviceCheck、connectedCheck 等
        taskManager.createTasksBeforeEvaluate()

        createAndroidTasks();
    }

    final void createAndroidTasks() {
      	//...
        List<VariantScope> variantScopes = variantManager.createAndroidTasks();
    }

Then it goes all the way to the TaskManager#createTasksForVariantScope method, which is an abstract method, let's look at its implementation in ApplicationTaskManager:

public void createTasksForVariantScope(@NonNull final VariantScope variantScope) {
        createAnchorTasks(variantScope);
      	//checkXxxManifest 检查 Manifest 文件存在、路径
        createCheckManifestTask(variantScope);

        handleMicroApp(variantScope);

        //把依赖放到 TransformManager 中
        createDependencyStreams(variantScope);

        // Add a task to publish the applicationId.
        createApplicationIdWriterTask(variantScope);

        // 合并 manifest
        createMergeApkManifestsTask(variantScope);

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

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

        // 合并 resource
        createMergeResourcesTask(
                variantScope,
                true,
                Sets.immutableEnumSet(MergeResources.Flag.PROCESS_VECTOR_DRAWABLES));

        // 合并 assets 文件夹
        createMergeAssetsTask(variantScope);

        // 生成 BuildConfig.java 文件
        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 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.register(new FeatureSetMetadataWriterTask.CreationAction(variantScope));

            createValidateSigningTask(variantScope);
            // Add a task to produce the signing config file.
            taskFactory.register(new SigningConfigWriterTask.CreationAction(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.register(
                        new DataBindingExportFeatureApplicationIdsTask.CreationAction(
                                variantScope));

            }
        } else {
            // Non-base feature specific task.
            // Task will produce artifacts consumed by the base feature
            taskFactory.register(
                    new FeatureSplitDeclarationWriterTask.CreationAction(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.register(
                        new DataBindingExportFeatureInfoTask.CreationAction(variantScope));
            }
            taskFactory.register(new MergeConsumerProguardFilesTask.CreationAction(variantScope));
        }

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

        // Add a compile task
        createCompileTask(variantScope);

        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);
        }


        TaskProvider<BuildInfoWriterTask> buildInfoWriterTask =
                createInstantRunPackagingTasks(variantScope);
        createPackagingTask(variantScope, buildInfoWriterTask);

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

        taskFactory.register(new FeatureSplitTransitiveDepsWriterTask.CreationAction(variantScope));

        createDynamicBundleTask(variantScope);
    }


3. Comprehensive Android build process

3.1 The new version of the Android build process

3.2 Old version of Android build process

From the flow chart above, I can see that the APK building process can be divided into the following seven steps:

  1. Pack res resource files through aapt to generate R.java, resources.arsc and res files (binary and non-binary such as res/raw and pic remain as they are)
  2. Process the .aidl file and generate the corresponding Java interface file
  3. Compile R.java, Java interface files, and Java source files through Java Compiler to generate .class files
  4. Use the dx/d8 tool to process .class files and .class files in third-party libraries to generate classes.dex
  5. Through the apkbuilder tool, package the resources.arsc generated by aapt together with the res file, assets file and classes.dex to generate apk
  6. Use the Jarsigner tool to debug or release sign the above apk
  7. Use the zipalign tool to align the signed apk
  8. It has been verified again through the above process. If we want to increase the speed of Apk construction, then use the AAR package or the already packaged jar to avoid secondary compilation, thereby reducing the time-consuming compilation. This is an optimization point.

3.3:apt

aapt (Android Asset Packaging Tool), used to package resource files, generate R.java and compiled resources. The source code of aapt is located in the frameworks/base/tools/aapt2 directory.

3.4: addl

Used to process aidl files, the source code is located in frameworks/base/tools/aidl. Input the file with aidl suffix, output the C/S side Java code that can be used for process communication, located in build/generated/source/aidl.

3.5: Java source code compilation

With the Java files generated by R.java and aidl, plus the source code of the project, you can now use javac to perform normal java compilation to generate class files.

Input: folder of java source (also includes: R.java under build/generated, Java files generated by aidl and BuildConfig.java)

Output: For gradle compilation, you can see the output class file in build/intermediates/classes.

3.4:dex

Use the dx/d8 tool to convert class files into dex files, generate constant pools, eliminate redundant data, etc.

3.6:apkbuilder

The APK file is packaged and generated. Now it has been packaged by the ApkBuilder class of sdklib.jar. The input is the .ap_ file containing resources.arsc we generated before, the dex file generated in the previous step, and other resources such as jni and jar packages. H.

3.7:Jar Signer

Sign the apk file, which is required for the APK to be installed on the device. Now not only jarsigner but also apksigner can be used.

3.8:zipalign

Align the signed apk file so that all resource files in the apk are an integer multiple of four bytes from the start of the file, so that it will be faster when accessing the apk file through memory mapping.

It should be noted that using different signing tools has an impact on the alignment. If you use jarsigner, you can only perform zipalign after the APK file is signed; if you use apksigner, you can only perform zipalign before the APK file is signed

3.9 APK build more detailed process

Source: https://docs.google.com/viewer?a=v&pid=sites&srcid=YW5kcm9pZC5jb218dG9vbHN8Z3g6MzVmYmFlN2FhYjkzMzc2Ng

Four AS source code analysis

 4.1 Core code

The source code of the AS3.4.0 version is analyzed here. The compilation of the entire AS source code is quite complicated and takes a long time. I will publish a special tutorial on compiling AS later.

4.2 Check project and read basic configuration

AndroidRunConfigurationBase-->getState

 

The core method is doGetState(), and then we will look at the source code of this method. Since this method is relatively large, we only look at a part of it

Select the device here for deployment and installation

4.3 RunStats

IDEA allows you to perform some tasks before execution, for example, you have to compile a Java project before running it. Our Android project is similar, before installation and deployment, you have to compile and package. This process is called: Before Launch.

 Can be configured when compiling the app

 AS provides us with Gradle-aware Make by default. The source code corresponding to Before launch is: BeforeRunBuilder

public interface BeforeRunBuilder {
  boolean build(@NotNull GradleTaskRunner taskRunner, @NotNull List<String> commandLineArguments) throws InterruptedException,
                                                                                                         InvocationTargetException;
}

The following code essentially executes Gradle Tasks. In the Debug environment, the default is assembleDebug. If the user changes the Build Variants, it will change accordingly, as shown in the figure below

4.4 Preparatory work before compiling

You can see that in the createBuilder() method

 DefaultGradleBuilder is here to assemble the gradle compilation command, for example:

组装 Gradle task:gradle[:"assemble" + compileType]

 In the end, GradleBuildInvoker will be called through the reflection method. This object mainly calls the gradle method directly, and then displays the execution process in the Message of the AS panel, and at the same time displays it in the Gradle console. This is where AS and Gradle really combine. Next, let's look at the specific core methods of this object.

4.5 Installation and deployment

After the build is complete, it will return to the execution phase of RunState. This phase should be called deployment. The core code is in

AndroidRunState--execute() method

At this time, the LaunchTaskRunner object will be called. Since this is a Runable, its run method will be called next.

By analyzing the source code, we can see that InstantRun related logic, version judgment, device judgment, output log, call pm command to install APK, evoke the first screen, etc.

五 总结

 Through the above analysis, I believe that everyone has already understood the entire process of AS compiling apk, and there may be some details that are not very clear, so you can read the code by yourself. It is necessary to clearly know the RunStats object that AS compiles apk depends on. This is a very important process control state machine when AS is compiling. At the same time, it is necessary to understand the combination of AS and gradle at that stage. After the combination of the two is completed, The rest of the work is handed over to gradle. After gradle finishes its work, it will notify AS to complete the final installation and deployment.

Guess you like

Origin blog.csdn.net/qq_18757557/article/details/130481880