Android构建流程

1,438 阅读3分钟

环境准备

为了方便查看gradle工具的源码,我们需要在build.gradle中加入以下代码

...
dependencies{
    //compileOnly 仅在编译时使用,打包时不会打进去
    compileOnly 'com.android.tools.build:gradle:3.5.4'
}
...

插件的声明

  • 类似com.android.application的插件是这样声明和使用的
    • 3

变体创建

比如以下代码通过不同维度交叉组合创建了四种变体

    buildTypes {
        release {}

        debug{}
    }
    flavorDimensions 'nation'
    productFlavors{
        cn{}
        en{}
    }

那么这几种变体是如何生成的呢?我们主要关注BasePlugin中的代码

下面我们关注com.android.build.gradle.BasePlugin的代码

  • 首先是createDefaultComponents
    variantFactory.createDefaultComponents(
                    buildTypeContainer, productFlavorContainer, signingConfigContainer);
    
    • 这行代码的具体实现在ApplicationVariantFactory是这样的
    • 根据我们配置的buildTypes,这里为我们创建了DEBUG和RELEASE两种buildType
          @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);
          }
      
  • 然后我们看下createTasks方法
        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);
                            }));
        }
    
    • threadRecorder.record(...createTasksBeforeEvaluate())主要创建了设备检测相关的task
    • project.afterEvaluate(...createAndroidTasks)主要负责了各种变体task的构建
      • 核心代码如下:variantManager.createAndroidTasks
      final void createAndroidTasks{
          ...
          List<VariantScope> variantScopes = variantManager.createAndroidTasks();
          ...
      }
      
      • createAndroidTasks方法通过createXXX方法调用创建了各个变体
      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;
      }
      

task的创建与执行

在ApplicationTaskManager中,有一个createTasksForVariantScope方法,这里面同样包含很多createXxxTask方法

  • createCheckManifestTask: 清单文件检查task
  • createMergeApkManifestsTask: 清单文件检查合并task
  • createAidlTask:Aidl创建task
  • createCompileTask -> createPostCompilationTasks -> createDexTasks ->DexArchiveBuilderTransformBuilder
    • 这里把class转换成为了dex文件
  • ...(其他的很多)
class ApplicationTaskManager{
    @Override
    public void createTasksForVariantScope(
            @NonNull final VariantScope variantScope,
            @NonNull List<VariantScope> variantScopesForLint) {
        createAnchorTasks(variantScope);
        createCheckManifestTask(variantScope);
        ...
    }
}

task详解->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;
    }
}

在AidlCompile的doTaskAction()方法中执行了AidlTask

  • 首先根据文件夹路径拼接成Aidl命令
  • 然后通过aidl命令生成.java文件 4

task详解->BuildConfig的生成

在我们的开发中,经常需要配置一些信息,比如baseUrl,混淆开关等,最终这些配置信息经过编译后会体现在BuildConfig中,方便我们的使用,如下图示

class ApplicationTaskManager{
    public void createBuildConfigTask(@NonNull VariantScope scope) {
        TaskProvider<GenerateBuildConfig> generateBuildConfigTask =
                taskFactory.register(new GenerateBuildConfig.CreationAction(scope));

        TaskFactoryUtils.dependsOn(
                scope.getTaskContainer().getSourceGenTask(), generateBuildConfigTask);
    }
}

首先调用了createBuildConfigTask,下面我们关注GenerateBuildConfig

  • 在doTaskAction方法中是,首先对一些默认属性进行了添加写入
  • 然后对用户自定义的一些属性属性进行添加
  • 最后通过IO操作在build下同包名目录下生成了BuildConfig.class文件

.class转.dex

.java编译成.class之后,会被转换成.dex文件
这是因为TaskManager调用了createDexTasks方法,这里面创建了DexArchiveBuilderTransform的实例,下面关注DexArchiveBuilderTransformcom.android.build.gradle.internal.transforms.DexArchiveBuilderTransform
下面是调用链:
DexArchiveBuilderTransform.transform->convertToDexArchive->launchProcessing-> dexArchiveBuilder.convert->最终调用了D8DexArchiveBuilder的convert方法,将class文件转换为dex文件

构建流程总结

7

Apk 构建的过程,用到了哪些工具

  • javac:.java -> .class
  • aidl: .aidl -> .class
  • aapt2 compile: res -> .flat
  • aapt2link: .flat & Manifest -> .ap_R.java(不确定)
  • d8:.class -> .dex
  • zip:.ap_&.dex -> .apk

手动生成apk

既然apk的构建过程是一大堆task构成呢,那么我们完全可以手动打包一个apk

  1. 编译资源

    -使用aapt2 工具对资源进行编译
    工具aapt2
    命令命令 aapt2 compile -o build/res.zip –dir res
    输入资源目录
    产出资源二进制文件压缩包
  2. 链接资源

    -使用 aapt2 工具将资源整合
    工具aapt2
    命令aapt2 link build/res.zip -I $ANDROID_HOME/platforms/android-29/android.jar – java build – manifest AndroidManifest.xml -o build/app-debug.apk
    输入资源二进制文件压缩包
    产出资源文件及 R.java 文件
  3. 编译 Java 文件

    -使用 javac 工具编译 Java 文件
    工具javac
    命令javac -d build -cp $ANDROID_HOME/platforms/android-28/android.jar com/*/.java
    输入资源二进制文件压缩包
    产出class 字节码文件
  4. dex 编译

    -使用 dex 工具编译 class 代码
    工具dx 或 d8
    命令d8 –output build/ –lib $ANDROID_HOME/platforms/android-28/android.jar build/com/example/application/*.class
    输入资源二进制文件压缩包
    产出dex 文件
  5. 合并 dex 文件和资源文件

    -使用 zip 命令合并代码文件和资源文件
    工具zip 命令
    命令zip -j build/app-debug.apk build/classes.dex
    输入dex 文件、资源文件
    产出还未签名的 apk 文件
  6. 对 apk 文件进行签名

    -使用 apksigner 工具对 apk 签名
    工具apksigner
    命令apksigner sign -ks ~/.android/debug.keystore build/app-debug.apk
    输入未签名的 apk 文件、keystore 文件
    产出已签名的 apk 文件

如果是第一次使用aapt手动打包,那么需要配置aapt环境变量

下面开始我的打包流程

  • 先在项目根目录下创建一个apkPack文件夹,用于保存打包过程中产生的文件
    • mkdir apkPack
  • 然后打包res文件,现在apkPack文件夹中有了①res.zip文件
    • aapt2 compile -o apkPack/res.zip --dir app/src/main/res
  • 然后链接资源,现在我们有了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
  • 然后使用javac命令编译.java文件,现在我们得到了若干.class文件 -javac -d apkPack -cp $ANDROID_HOME/platforms/android-28/android.jar app/src/main/java/com/**/**/*.java(以我的包名com.dsh.mydemos为例)
  • 然后使用d8工具得到classes.dex文件
    • d8 –-output apkPack/ –-lib $ANDROID_HOME/platforms/android-28/android.jar apkPack/com/dsh/mydemos/*.class
  • 接下来使用zip命令合并apk和classes文件
    • zip -j apkPack/app-debug.apk apkPack/classes.dex
  • 上面我们已经生成了新的app-debug.apk文件,但是并不能直接运行,会直接报错
  • 所以最后我们需要对apk进行签名,这里使用debug签名,输入密码完成签名
    • apksigner sign -ks ~/.android/debug.keystore apkPack/app-debug.apk
  • 至此,一个简单的打包构建过程就结束了