环境准备
为了方便查看gradle工具的源码,我们需要在build.gradle中加入以下代码
...
dependencies{
//compileOnly 仅在编译时使用,打包时不会打进去
compileOnly 'com.android.tools.build:gradle:3.5.4'
}
...
插件的声明
- 类似com.android.application的插件是这样声明和使用的
-
变体创建
比如以下代码通过不同维度交叉组合创建了四种变体
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文件
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文件
构建流程总结
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
-
编译资源
- 使用aapt2 工具对资源进行编译 工具 aapt2 命令 命令 aapt2 compile -o build/res.zip –dir res 输入 资源目录 产出 资源二进制文件压缩包 -
链接资源
- 使用 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 文件 -
编译 Java 文件
- 使用 javac 工具编译 Java 文件 工具 javac 命令 javac -d build -cp $ANDROID_HOME/platforms/android-28/android.jar com/*/.java 输入 资源二进制文件压缩包 产出 class 字节码文件 -
dex 编译
- 使用 dex 工具编译 class 代码 工具 dx 或 d8 命令 d8 –output build/ –lib $ANDROID_HOME/platforms/android-28/android.jar build/com/example/application/*.class 输入 资源二进制文件压缩包 产出 dex 文件 -
合并 dex 文件和资源文件
- 使用 zip 命令合并代码文件和资源文件 工具 zip 命令 命令 zip -j build/app-debug.apk build/classes.dex 输入 dex 文件、资源文件 产出 还未签名的 apk 文件 -
对 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
- 至此,一个简单的打包构建过程就结束了