Environment to prepare

In order to easily view the source code of gradle tools, we need to add the following code to build.gradle

. Dependencies {/ / compileOnly just used at compile time, when the package will not play in compileOnly 'com. Android. View the build: gradle: 3.5.4'}...Copy the code

Declaration of plug-ins

  • Plugins like com.android.application are declared and used like this

Variations to create

For example, the following code creates four variants by cross-combining different dimensions

    buildTypes {
        release {}

        debug{}
    }
    flavorDimensions 'nation'
    productFlavors{
        cn{}
        en{}
    }
Copy the code

So how do these variations come about? We will focus on the code in BasePlugin

Here we focus on com. Android. Build. Gradle. BasePlugin code

  • The first is createDefaultComponents
    variantFactory.createDefaultComponents(
                    buildTypeContainer, productFlavorContainer, signingConfigContainer);
    Copy the code
    • This line of code implementation in ApplicationVariantFactory is like this
    • Based on the buildTypes we configured, we created DEBUG and RELEASE buildTypes for us
          @Override
          public void createDefaultComponents(
                  @NonNull NamedDomainObjectContainer<BuildType> buildTypes,
                  @NonNull NamedDomainObjectContainer<ProductFlavor> productFlavors,
                  @NonNull NamedDomainObjectContainer<SigningConfig> signingConfigs) {
              // must create signing config first so that build type 'debug' can be initialized
              // with the debug signing config.
              signingConfigs.create(DEBUG);
              buildTypes.create(DEBUG);
              buildTypes.create(RELEASE);
          }
      Copy the code
  • Then let’s look at the createTasks method
        private void createTasks() {
            threadRecorder.record(
                    ExecutionType.TASK_MANAGER_CREATE_TASKS,
                    project.getPath(),
                    null,
                    () -> taskManager.createTasksBeforeEvaluate());
    
            project.afterEvaluate(
                    CrashReporting.afterEvaluate(
                            p -> {
                                sourceSetManager.runBuildableArtifactsActions();
    
                                threadRecorder.record(
                                        ExecutionType.BASE_PLUGIN_CREATE_ANDROID_TASKS,
                                        project.getPath(),
                                        null,
                                        this::createAndroidTasks);
                            }));
        }
    Copy the code
    • threadRecorder.record(… CreateTasksBeforeEvaluate ()) mainly created equipment testing related task
    • project.afterEvaluate(… CreateAndroidTasks is responsible for building various variants of tasks
      • The core code is as follows: variantManager createAndroidTasks
      final void createAndroidTasks{ ... List<VariantScope> variantScopes = variantManager.createAndroidTasks(); . }Copy the code
      • The createAndroidTasks method creates the variants through the createXXX method call
      public List<VariantScope> createAndroidTasks() { variantFactory.validateModel(this); variantFactory.preVariantWork(project); if (variantScopes.isEmpty()) { populateVariantDataList(); } // Create top level test tasks. taskManager.createTopLevelTestTasks(! productFlavors.isEmpty()); for (final VariantScope variantScope : variantScopes) { createTasksForVariantData(variantScope); } taskManager.createSourceSetArtifactReportTask(globalScope); taskManager.createReportTasks(variantScopes); return variantScopes; }Copy the code

Task creation and execution

In ApplicationTaskManager, there’s a createTasksForVariantScope method, it also contains many createXxxTask method

  • CreateCheckManifestTask: Manifest file check task
  • Check the merger task createMergeApkManifestsTask: listing file
  • CreateAidlTask: Aidl creates a task
  • createCompileTask -> createPostCompilationTasks -> createDexTasks ->DexArchiveBuilderTransformBuilder
    • This converts the class to a dex file
  • . (Many others)
class ApplicationTaskManager{ @Override public void createTasksForVariantScope( @NonNull final VariantScope variantScope, @NonNull List<VariantScope> variantScopesForLint) { createAnchorTasks(variantScope); createCheckManifestTask(variantScope); . }}Copy the code

Task explanation – > AidlTask

class ApplicationTaskManager{ public TaskProvider<AidlCompile> createAidlTask(@NonNull VariantScope scope) { MutableTaskContainer taskContainer = scope.getTaskContainer(); TaskProvider<AidlCompile> aidlCompileTask = taskFactory.register(new AidlCompile.CreationAction(scope)); TaskFactoryUtils.dependsOn(taskContainer.getSourceGenTask(), aidlCompileTask); return aidlCompileTask; }}Copy the code

AidlTask is executed in the doTaskAction() method of AidlCompile

  • First, concatenate the Aidl command according to the folder path
  • The.java file is then generated using the aidl command

Task description -> build BuildConfig

In our development, we often need to configure some information, such as baseUrl, obfuscating switch, etc. Finally, this configuration information will be compiled in BuildConfig for our convenience, as shown in the following figure

class ApplicationTaskManager{ public void createBuildConfigTask(@NonNull VariantScope scope) { TaskProvider<GenerateBuildConfig> generateBuildConfigTask = taskFactory.register(new GenerateBuildConfig.CreationAction(scope)); TaskFactoryUtils.dependsOn( scope.getTaskContainer().getSourceGenTask(), generateBuildConfigTask); }}Copy the code

CreateBuildConfigTask is called first, so let’s focus on GenerateBuildConfig

  • In the doTaskAction method, some default properties are added first
  • Then add some user – defined attribute attributes
  • The buildConfig.class file is generated in the build directory with the same package name through IO operations

Turn.class. Dex

.java compiled into. After class, will be converted to. Dex file This is because the TaskManager call createDexTasks method, which creates DexArchiveBuilderTransform instance, The following attention DexArchiveBuilderTransformcom. Android. Build. Gradle. Internal. Transforms. DexArchiveBuilderTransform call chain is below: DexArchiveBuilderTransform.transform->convertToDexArchive->launchProcessing-> DexArchiveBuilder. Convert – > call the D8DexArchiveBuilder convert method finally, converts the class files to the dex file

Summary of the Build process

How Apk was built and what tools were used

  • Javac:.java ->.class
  • aidl: .aidl -> .class
  • aapt2 compile: res -> .flat
  • Aapt2link:.flat & manifest->.ap_r.java
  • D8:.class ->.dex
  • Zip:.ap_&.dex ->.apk

Manually generate apK

Since the apK construction process is composed of a large number of tasks, we can package an APK manually

  1. Compile resource

    Compile the resources using aapT2 tools
    tool aapt2
    The command Run aapt2 compile -o build/res.zip -dir res
    The input The resource directory
    output Resource binary file compression package
  2. Links to resources

    Use aapT2 tools to consolidate resources
    tool aapt2
    The command Aapt2 link build/res.zip -I $ANDROID_HOME/platforms/android-29/android.jar — Java build — manifest androidmanifest.xml -o build/app-debug.apk
    The input Resource binary file compression package
    output Resource files and R. Java files
  3. Compiling Java files

    Compile Java files using the Javac tool
    tool javac
    The command javac -d build -cp $ANDROID_HOME/platforms/android-28/android.jar com/*/.java
    The input Resource binary file compression package
    output Class bytecode file
  4. Dex compiler

    Compile the class code using the dex tool
    tool Dx or d8
    The command D8 – output build / – $ANDROID_HOME lib/platforms/android – 28 / android. Jar build/com/example/application / *. Class
    The input Resource binary file compression package
    output Dex file
  5. Merge dex files and resource files

    Use the zip command to merge code files and resource files
    tool Zip command
    The command zip -j build/app-debug.apk build/classes.dex
    The input Dex file and resource file
    output An APK file that has not been signed
  6. Sign the APK file

    Use the ApkSigner tool to sign apK
    tool apksigner
    The command apksigner sign -ks ~/.android/debug.keystore build/app-debug.apk
    The input Unsigned APK file, keystore file
    output Signed APK file

If you are using AAPT manually for the first time, you need to configure the AAPT environment variable

Let’s start my packing process

  • Start by creating an apkPack folder in the project root directory to hold the files generated during the packaging process
    • mkdir apkPack
  • Then pack the RES file, which is now in the apkPack folder1) res. Zipfile
    • aapt2 compile -o apkPack/res.zip --dir app/src/main/res
  • Then link resources, and now we have app-debug.apk
    • aapt2 link apkPack/res.zip -I $ANDROID_HOME/platforms/android-28/android.jar --java build --manifest app/src/main/AndroidManifest.xml -o apkPack/app-debug.apk
  • We then compiled the.java file using the javac command, and now we have several.class files –javac -d apkPack -cp $ANDROID_HOME/platforms/android-28/android.jar app/src/main/java/com/**/**/*.java(Take my package name com.dsh.mydemos for example)
  • Then use the D8 tool to get the classes.dex file
    • D8 -- output apkPack/ -- lib $ANDROID_HOME/platforms/android-28/android.jar apkPack/com/ DSH /mydemos/*.class
  • Next use the zip command to merge the APK and classes files
    • zip -j apkPack/app-debug.apk apkPack/classes.dex
  • Above we have generated a new app-debug.apk file, but it can not run directly, it will directly report an error
  • Therefore, we need to sign the APK. In this case, use debug signature and enter the password to complete the signature
    • apksigner sign -ks ~/.android/debug.keystore apkPack/app-debug.apk
  • At this point, a simple package build process is over