背景
Gradle是一个开源的自动化构建工具,其设计非常灵活可适用不同平台。针对不同平台其通过编写不同的插件去实现,其本身架构不做处理。保持了很好的扩展性和灵活性。 针对android平台其使用的是android gradle plugin工具.
准备工作
要分析其整个流程,我们首先就要准备对应的源码。这里有两种方式,一种是使用上一篇的思路直接下载gradle源码,其包含了AGP. 今天我们介绍第二种方案,只下载AGP.
AGP源码下载
- 使用Android Studio新建一个项目,删除app下除了build.gradle的其他文件
- 修改app下的build.gradle,移除其他配置,改成下面方式
plugins {
id 'java'
}
sourceCompatibility = 1.8
dependencies {
implementation gradleApi()
implementation "com.android.tools.build:gradle:3.0.0"
}
复制代码
- 修改RootProject的build.gradle配置,移除classPath配置
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
//classpath "com.android.tools.build:gradle:7.0.3"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
复制代码
到这里我们就可以在External Libraries下找到AGP的源码,这里我们使用3.0.0源码,最新的改动比较大,流程比较复杂。因为我们是梳理了解其整个流程,同时开发项目目前并不会立马使用最新的插件。因此这里以3.0.0版本分析。
这里我们可以看到多个Plugin,其中有两个AppPlugin和LibraryPlguin,因为我们开发app,其目录下使用的是appplugin,所以我们下面就依次为入口,分析apk的整体构建流程。
APK构建
apk构建源码流程图
通过打包流程的梳理,其大致分为三个简单
- configProject
- configExtension
- createTasks
前两个阶段主要是完成前期的配置。重点是createTasks,其主要创建了那些任务,以及每个任务具体功能是什么。下面我们着重分析这方面,其他两个阶段本文就不予深入研究。
Apk打包任务分析
createTasks
该方法调用createTasksBeforeEvaluate,在AppPlugin执行之前,创建一些与构建关系不大的任务,前必须提取创建的
createTasksBeforeEvaluate
public void createTasksBeforeEvaluate(@NonNull TaskFactory tasks) {
androidTasks.create(tasks, UNINSTALL_ALL, uninstallAllTask -> {
uninstallAllTask.setDescription("Uninstall all applications.");
uninstallAllTask.setGroup(INSTALL_GROUP);
});
androidTasks.create(tasks, DEVICE_CHECK, deviceCheckTask -> {
deviceCheckTask.setDescription(
"Runs all device checks using Device Providers and Test Servers.");
deviceCheckTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
});
androidTasks.create(tasks, CONNECTED_CHECK, connectedCheckTask -> {
connectedCheckTask.setDescription(
"Runs all device checks on currently connected devices.");
connectedCheckTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
});
//创建MAIN_PREBUILD任务
androidTasks.create(tasks, MAIN_PREBUILD, task -> {});
//创建提取混淆文件任务
AndroidTask<ExtractProguardFiles> extractProguardFiles =
androidTasks.create(
tasks, EXTRACT_PROGUARD_FILES, ExtractProguardFiles.class, task -> {});
...
//LinkTask最后配置,但其创建是在这里,在后续anchor Task使用前创建
createGlobalLintTask(tasks);
}
复制代码
createAndroidTasks
接下来在Project完成配置后,调用createAndroidTasks,开始创建Android构建相关的任务
project.afterEvaluate(
project ->
threadRecorder.record(
ExecutionType.BASE_PLUGIN_CREATE_ANDROID_TASKS,
project.getPath(),
null,
() -> createAndroidTasks(false)));
复制代码
因为Android支持配置多渠道版本构建,因此具体的构建任务通过VariantManager完成,其在第二阶段创建。
threadRecorder.record(
ExecutionType.VARIANT_MANAGER_CREATE_ANDROID_TASKS,
project.getPath(),
null,
() -> {
variantManager.createAndroidTasks();
ApiObjectFactory apiObjectFactory =
new ApiObjectFactory(
androidBuilder,
extension,
variantFactory,
instantiator,
project.getObjects());
for (VariantScope variantScope : variantManager.getVariantScopes()) {
BaseVariantData variantData = variantScope.getVariantData();
apiObjectFactory.create(variantData);
}
});
复制代码
因此真正创建AndroidTasks是在VariantManager中
VariantManager创建任务
populateVariantDataList处理渠道数据
其一次遍历创建所有对应的渠道变体
我们首先看一卡项目中如何配置,这样理解其处理过程更容易
flavorDimensions "api", "mode", "corp"
productFlavors {
Compainion {
dimension:"mode"
}
apiVersion {
dimension:"api"
}
corp {
dimension:"corp"
}
}
复制代码
/**
* Create all variants.
*/
public void populateVariantDataList() {
//获取渠道数据
List<String> flavorDimensionList = extension.getFlavorDimensionList();
//判断flavorDimensionList是否存在,或只有一个的情况
//如果只有一个则将其设置给所部渠道
...
else if (flavorDimensionList.size() == 1) {
// if there's only one dimension, auto-assign the dimension to all the flavors.
String dimensionName = flavorDimensionList.get(0);
for (ProductFlavorData<CoreProductFlavor> flavorData : productFlavors.values()) {
CoreProductFlavor flavor = flavorData.getProductFlavor();
if (flavor.getDimension() == null && flavor instanceof DefaultProductFlavor) {
((DefaultProductFlavor) flavor).setDimension(dimensionName);
}
}
}
//创建遍历所以渠道的遍历器
Iterable<CoreProductFlavor> flavorDsl =
Iterables.transform(productFlavors.values(),
ProductFlavorData::getProductFlavor);
//根据dimen组合创建所有的渠道
List<ProductFlavorCombo<CoreProductFlavor>> flavorComboList =
ProductFlavorCombo.createCombinations(
flavorDimensionList,
flavorDsl);
//为对应的渠道创建渠道数据
for (ProductFlavorCombo<CoreProductFlavor> flavorCombo : flavorComboList) {
//noinspection unchecked
createVariantDataForProductFlavors(
(List<ProductFlavor>) (List) flavorCombo.getFlavorList());
}
}
复制代码
createTasksForVariantData
其会调用该方法,为每一个变体创建对应的构建任务
for (final VariantScope variantScope : variantScopes) {
recorder.record(
ExecutionType.VARIANT_MANAGER_CREATE_TASKS_FOR_VARIANT,
project.getPath(),
variantScope.getFullVariantName(),
() -> createTasksForVariantData(tasks, variantScope));
}
复制代码
创建对应BuildType的assemble任务
//创建assmeble任务
if (buildTypeData.getAssembleTask() == null) { buildTypeData.setAssembleTask(taskManager.createAssembleTask(tasks, buildTypeData));
}
//创建不同BuildType的assembe任务:assembleDebug,assembleRelease
tasks.named("assemble", new Action<Task>() {
@Override
public void execute(Task task) {
assert buildTypeData.getAssembleTask() != null;
task.dependsOn(buildTypeData.getAssembleTask().getName());
}
});
//创建每个变体的assemble任务
createAssembleTaskForVariantData(tasks, variantData)
复制代码
第二部分工作则是调用taskManager的createTasksForVariantScope开始真正的任务构建
...
else {
taskManager.createTasksForVariantScope(tasks, variantScope);
}
复制代码
ApplicationTaskManager创建任务
在处理完多渠道数据后,为特定渠道创建任务就进入到ApplicationTaskManager中
createAnchorTasks
其主要创建preBuild任务,和对应的资源锚点任务
- generateSource
- generateResouce
- generateAssets
- compileSource
其任务为空任务,主要是为了建立依赖关系
public void createAnchorTasks(@NonNull TaskFactory tasks, @NonNull VariantScope scope) {
//创建preBuild任务,并依赖最初的提取混淆文件任务
//具体其方法内部代码
createPreBuildTasks(tasks, scope);
scope.setSourceGenTask(androidTasks.create(
tasks,scope.getTaskName("generate", "Sources"),
Task.class,
task -> {
variantData.sourceGenTask = task;
task.dependsOn(PrepareLintJar.NAME);
}));
// res 资源生成任务
scope.setResourceGenTask(androidTasks.create(
tasks,scope.getTaskName("generate", "Resources"),
Task.class,
task -> {
variantData.resourceGenTask = task;
}));
scope.setAssetGenTask(androidTasks.create(tasks,
scope.getTaskName("generate", "Assets"),
Task.class,
task -> {
variantData.assetGenTask = task;
}));
//创建编译 Source任务,并将assemble依赖与它
createCompileAnchorTask(...)
}
private void createPreBuildTasks(@NonNull TaskFactory tasks, @NonNull VariantScope scope) {
scope.setPreBuildTask(createVariantPreBuildTask(tasks, scope));
scope.getPreBuildTask().dependsOn(tasks, MAIN_PREBUILD);
if (runJavaCodeShrinker(scope)) {
scope.getPreBuildTask().dependsOn(tasks, EXTRACT_PROGUARD_FILES);
}
}
//createVariantPreBuildTask最终调用createDefalutPreBuildTask
protected AndroidTask<? extends DefaultTask> createDefaultPreBuildTask(
@NonNull TaskFactory tasks, @NonNull VariantScope scope) {
return getAndroidTasks()
.create(
tasks,
scope.getTaskName("pre", "Build"),
task -> {
scope.getVariantData().preBuildTask = task;
});
}
复制代码
createCheckManifestTask
其检测MainfestTask是否存在,同时与preBuild建立依赖关系
CheckMainfest任务
public class CheckManifest extends DefaultAndroidTask {
...
@TaskAction
void check() {
if (!isOptional && manifest != null && !manifest.isFile()) {
throw new IllegalArgumentException(
String.format(
"Main Manifest missing for variant %1$s. Expected path: %2$s",
getVariantName(), getManifest().getAbsolutePath()));
}
}
...
}
复制代码
createDependencyStreams
其主要向TransformManager添加各种stream操作,其中有dataBing。
createApplicationIdWriterTask
该任务主要就是处理applicationID目录,应用必须有applicationId
private void createApplicationIdWriterTask(
@NonNull TaskFactory tasks, @NonNull VariantScope variantScope) {
//创建对应应用ID下目录
File applicationIdOutputDirectory =
FileUtils.join(
globalScope.getIntermediatesDir(),
"applicationId",
//创建对应applicationID写入任务 variantScope.getVariantConfiguration().getDirName());
AndroidTask<ApplicationIdWriterTask> writeTask =
androidTasks.create(
tasks,
new ApplicationIdWriterTask.ConfigAction(
variantScope, applicationIdOutputDirectory));
variantScope.addTaskOutput(
TaskOutputHolder.TaskOutputType.METADATA_APP_ID_DECLARATION,
ApplicationId.getOutputFile(applicationIdOutputDirectory),
writeTask.getName());
}
复制代码
createMergeApkManifestsTask
其主要是合并应用和各个依赖的Manifest.xml文
recorder.record(
ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_MANIFEST_TASK,
project.getPath(),
variantScope.getFullVariantName(),
() -> createMergeApkManifestsTask(tasks, variantScope));
复制代码
CompatibleScreensManifest
该处理Manifest设备兼容任务对应的类,其generareAll方法处理合并等操作,内部会调用generate
@TaskAction
public void generateAll() throws IOException {
// process all outputs.
outputScope.parallelForEach(
VariantScope.TaskOutputType.COMPATIBLE_SCREEN_MANIFEST, this::generate);
// now write the metadata file.
outputScope.save(
ImmutableList.of(VariantScope.TaskOutputType.COMPATIBLE_SCREEN_MANIFEST),
outputFolder);
}
复制代码
MergeManifests
其合并Mainifest任务,在其doFullTaskAction做的处理
@Override
protected void doFullTaskAction() throws IOException {
if (packageManifest != null && !packageManifest.isEmpty()) {
packageOverride =
ApplicationId.load(packageManifest.getSingleFile()).getApplicationId();
} else {
packageOverride = getPackageOverride();
}
...
for (ApkData apkData : splitsToGenerate) {
File manifestOutputFile =
FileUtils.join(getManifestOutputDirectory(),
apkData.getDirName(),
SdkConstants.ANDROID_MANIFEST_XML);
...
XmlDocument mergedXmlDocument = mergingReport.getMergedXmlDocument(MergingReport.MergedManifestKind.MERGED);
...
outputScope.save( ImmutableList.of(VariantScope.TaskOutputType.MERGED_MANIFESTS),
getManifestOutputDirectory());
}
...
}
复制代码
createGenerateResValuesTask
其主要是创建生成resValues的任务
public void createGenerateResValuesTask(
@NonNull TaskFactory tasks,
@NonNull VariantScope scope) {
AndroidTask<GenerateResValues> generateResValuesTask = androidTasks.create(
tasks, new GenerateResValues.ConfigAction(scope));
scope.getResourceGenTask().dependsOn(tasks, generateResValuesTask);
}
复制代码
具体细节这里就不展开了,其对应的类是GenerateResValues
createRenderscriptTask
其创建了编译渲染脚本文件的任务
public void createRenderscriptTask(
@NonNull TaskFactory tasks,
@NonNull VariantScope scope) {
//创建任务
scope.setRenderscriptCompileTask(
androidTasks.create(tasks, new RenderscriptCompile.ConfigAction(scope)));
GradleVariantConfiguration config = scope.getVariantConfiguration();
//根据是否是测试,建立不同的依赖关系
if (config.getType().isForTesting()) {
scope.getRenderscriptCompileTask().dependsOn(tasks, scope.getManifestProcessorTask());
} else {
scope.getRenderscriptCompileTask().dependsOn(tasks, scope.getPreBuildTask());
}
scope.getResourceGenTask().dependsOn(tasks, scope.getRenderscriptCompileTask());
// only put this dependency if rs will generate Java code
if (!config.getRenderscriptNdkModeEnabled()) {
scope.getSourceGenTask().dependsOn(tasks, scope.getRenderscriptCompileTask());
}
复制代码
createMergeResourcesTask
其创建用于合并Resource的任务
recorder.record(
ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_RESOURCES_TASK,
project.getPath(),
variantScope.getFullVariantName(),
(Recorder.VoidBlock) () -> createMergeResourcesTask(tasks, variantScope, true))
复制代码
createMergeAssetsTask
其创建用于合并Assets的任务
recorder.record(
ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_ASSETS_TASK,
project.getPath(),
variantScope.getFullVariantName(),
() -> createMergeAssetsTask(tasks, variantScope, null));
复制代码
createBuildConfigTask
其创建我们应用生成BuildConfig.java文件的类
recorder.record(
ExecutionType.APP_TASK_MANAGER_CREATE_BUILD_CONFIG_TASK,
project.getPath(),
variantScope.getFullVariantName(),
() -> createBuildConfigTask(tasks, variantScope));
复制代码
createApkProcessResTask
其通过aapt处理对应的资源文件。其中包含R文件的生成。因为其过程比较复杂,我们后面单独列出一个章节分析。
createProcessJavaResTask
其主要负责处理Java资源文件。细节就不分析了。去主要分为两步
- 通过ProcessJavaResConfigAction同步配置,其将对应的全部资源文件合成到一个文件
- 然后使用MergeJavaResourcesTransform通过我们的PackageingOptions创建一个merge
createAidlTask
其主要用于处理我们使用跨进程通信aidl文件,将其转化生成对应的类
recorder.record(
ExecutionType.APP_TASK_MANAGER_CREATE_AIDL_TASK,
project.getPath(),
variantScope.getFullVariantName(),
() -> createAidlTask(tasks, variantScope));
复制代码
其内部会设置对应的编译和代码生成任务,同时其依赖的为preBuild任务,因此其是和资源处理同时进行的,后面我们整理大致apk打包流程图,也就理解了其步骤。
public AndroidTask<AidlCompile> createAidlTask(@NonNull TaskFactory tasks, @NonNull VariantScope scope) {
AndroidTask<AidlCompile> aidlCompileTask = androidTasks
.create(tasks, new AidlCompile.ConfigAction(scope));
scope.setAidlCompileTask(aidlCompileTask);
scope.getSourceGenTask().dependsOn(tasks, aidlCompileTask);
aidlCompileTask.dependsOn(tasks, scope.getPreBuildTask());
return aidlCompileTask;
}
复制代码
后面是一些Ndk开发相关的任务,这里平时开发用的并不多,就不列出来了
createMergeJniLibFoldersTasks
其主要是用于处理jni文件夹的资源
recorder.record(
ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_JNILIBS_FOLDERS_TASK,
project.getPath(),
variantScope.getFullVariantName(),
() -> createMergeJniLibFoldersTasks(tasks, variantScope));
复制代码
addCompileTask
其创建编译java,kotlin文件,然后将其转成dex相关的工作
private void addCompileTask(@NonNull TaskFactory tasks, @NonNull VariantScope variantScope) {
//创建javacTask,其中javac是java中,编译.java
//到class的程序
AndroidTask<? extends JavaCompile> javacTask = createJavacTask(tasks, variantScope);
//检测对java8以上语法的支持
VariantScope.Java8LangSupport java8LangSupport = variantScope.getJava8LangSupportType();
if (java8LangSupport == VariantScope.Java8LangSupport.INVALID) {
return;
}
//添加由Javac编译生成的Class流操作
addJavacClassesStream(...)
//生成编译任务
setJavaCompilerTask(javacTask, tasks, variantScope);
//处理编译后,转化成dex
createPostCompilationTasks(tasks, variantScope);
}
复制代码
addJavaClassesStream
其会针对JAVAC的输出创建对应的流,方便编译前后的字节码hook处理
public void addJavacClassesStream(VariantScope scope) {
scope.getTransformManager()
.addStream(
OriginalStream.builder(project, "javac-output")
.addContentTypes(
DefaultContentType.CLASSES, DefaultContentType.RESOURCES)
.addScope(Scope.PROJECT)
.setFileCollection(scope.getOutput(JAVAC))
.build());
scope.getTransformManager()
.addStream(
OriginalStream.builder(project, "pre-javac-generated-bytecode")
.addContentTypes(
DefaultContentType.CLASSES, DefaultContentType.RESOURCES)
.addScope(Scope.PROJECT)
.setFileCollection(
scope.getVariantData().getAllPreJavacGeneratedBytecode())
.build());
scope.getTransformManager()
.addStream(
OriginalStream.builder(project, "post-javac-generated-bytecode")
.addContentTypes(
DefaultContentType.CLASSES, DefaultContentType.RESOURCES)
.addScope(Scope.PROJECT)
.setFileCollection(
scope.getVariantData().getAllPostJavacGeneratedBytecode())
.build());
}
复制代码
setJavaCompileTask
其主要是将我们创建的JavacTask依赖与我们之前创建的锚点编译任务
public static void setJavaCompilerTask(
@NonNull AndroidTask<? extends Task> javaCompilerTask,
@NonNull TaskFactory tasks,
@NonNull VariantScope scope) {
scope.getCompileTask().dependsOn(tasks, javaCompilerTask);
}
复制代码
createPostCompilationTasks
该任务主要是用于将.class文件转成成dex文件任务的创建,具体过程添加到对应代码注释中
public void createPostCompilationTasks(...){
TransformManager transformManager = variantScope.getTransformManager();
...
//获取所有外部的transform
List<Transform> customTransforms = extension.getTransforms();
List<List<Object>> customTransformsDependencies = extension.getTransformsDependencies();
//循环调用transrom,添加到transformmanager
for (int i = 0, count = customTransforms.size(); i < count; i++) {
Transform transform = customTransforms.get(i);
List<Object> deps = customTransformsDependencies.get(i);
transformManager.addTransform(tasks, variantScope, transform)
.ifPresent(t -> {
if (!deps.isEmpty()) {
t.dependsOn(tasks, deps);
}
if (transform.getScopes().isEmpty()) { variantScope.getAssembleTask().dependsOn(tasks, t);
}
});
}
//对处理的transform进行性能记录
for (String jar : getAdvancedProfilingTransforms(projectOptions)) {
if (variantScope.getVariantConfiguration().getBuildType().isDebuggable()
&& variantData.getType().equals(VariantType.DEFAULT)
&& jar != null) {
transformManager.addTransform(tasks, variantScope, new CustomClassTransform(jar));
}
}
...
//获取Dex类型
DexingType dexingType = variantScope.getDexingType();
//如果设备支持native multi-dex,就转到使用native的
if (dexingType == DexingType.LEGACY_MULTIDEX) {
if (variantScope.getVariantConfiguration().isMultiDexEnabled()
&& variantScope
.getVariantConfiguration()
.getMinSdkVersionWithTargetDeviceApi()
.getFeatureLevel()
>= 21) {
dexingType = DexingType.NATIVE_MULTIDEX;
}
}
//创建multiDex任务存储数组
Optional<AndroidTask<TransformTask>> multiDexClassListTask;
//创建处理所有输入的文件夹和jar到一个单独的jar,主要是合并第三方库class文件
...
JarMergingTransform jarMergingTransform =
new JarMergingTransform(TransformManager.SCOPE_FULL_PROJECT);
...
//根据情况创建MainDex或MultiDex的transForm
if (usingIncrementalDexing(variantScope)) {
multiDexTransform = new MainDexListTransform(
variantScope,
extension.getDexOptions());
} else {
multiDexTransform = new MultiDexTransform(variantScope, extension.getDexOptions());
}
//添加multiDexTransform
multiDexClassListTask =
transformManager.addTransform(tasks, variantScope, multiDexTransform);
multiDexClassListTask.ifPresent(variantScope::addColdSwapBuildTask);
//创建对应的multiDex任务
if (usingIncrementalDexing(variantScope)) {
createNewDexTasks(tasks, variantScope, multiDexClassListTask.orElse(null), dexingType);
} else {
createDexTasks(tasks, variantScope, multiDexClassListTask.orElse(null), dexingType);
}
}
复制代码
createDexTasks
createSplitTasks
该任务主要用于创建输出不同架构apk的任务,当我们需要根据不同的架构输出不同的apk时,就是有其处理的。
其包含了两部分工作,
- 创建对应的资源,
- 创建abi
public void createSplitTasks(@NonNull TaskFactory tasks, @NonNull VariantScope variantScope) {
PackagingScope packagingScope = new DefaultGradlePackagingScope(variantScope);
createSplitResourcesTasks(tasks, variantScope, packagingScope);
createSplitAbiTasks(tasks, variantScope, packagingScope);
}
复制代码
createPackageingTask
该任务主要创建最终的apk,根据配置,如果apk配置了签名,可进行zipaligin。
public void createPackingTask(...){
//对应的渠道信息
ApkVariantData variantData = (ApkVariantData) variantScope.getVariantData();
//是否签名
boolean signedApk = variantData.isSigned();
//判断是否api>23,可支持InstantRunPatch,即调试时候不用全量更新APK
InstantRunPatchingPolicy patchingPolicy =
variantScope.getInstantRunBuildContext().getPatchingPolicy();
//获取对应Mainfest文件的类型,InstanRun,还是Merge
VariantScope.TaskOutputType manifestType =
variantScope.getInstantRunBuildContext().isInInstantRunMode()
? INSTANT_RUN_MERGED_MANIFESTS
: MERGED_MANIFESTS;
//获取判断是全量apk还是拆分架构apk,设置不同的生成目录
File outputDirectory =
splitsArePossible ? variantScope.getFullApkPackagesOutputDirectory(): finalApkLocation;
//创建对应的任务PackageApplication
AndroidTask<PackageApplication> packageApp =androidTasks.create(tasks,
new PackageApplication.StandardConfigAction(...));
//更加是否是InstantRunMode创建不同的资源处理任务
if(InstanceRunMode){
//判断是否是MULTI_APK_SEPARATE_RESOURCES
//是
packageInstantRunResources = androidTasks.create(tasks,
new InstantRunResourcesApkBuilder.ConfigAction(...));
//否
packageInstantRunResources = androidTasks.create(tasks,
new PackageApplication.InstantRunResourcesConfigAction(...));
}
//packageApp任务依赖上面创建的任务
packageApp.dependsOn(tasks, packageInstantRunResources);
//package依赖签名,即先签名后合成apk
CoreSigningConfig signingConfig = packagingScope.getSigningConfig();
//noinspection VariableNotUsedInsideIf - we use the whole packaging scope below.
if (signingConfig != null) {
packageApp.dependsOn(tasks, getValidateSigningTask(tasks, packagingScope));
}
//依赖资源压缩剔除任务
packageApp.optionalDependsOn(tasks,
variantScope.getJavacTask(),
variantData.packageSplitResourcesTask,
variantData.packageSplitAbiTask);
//配置完成后,将packageApp设置为渠道的对应任务
variantScope.setPackageApplicationTask(packageApp);
// 将assemble任务依赖与packageAPP
variantScope.getAssembleTask().dependsOn(tasks, packageApp.getName());
...
//在上面创建的Install和UnInstall锚点任务创建真正的任务
if (signedApk) {
AndroidTask<InstallVariantTask> installTask = androidTasks.create(
tasks, new InstallVariantTask.ConfigAction(variantScope));
installTask.dependsOn(tasks, variantScope.getAssembleTask());
}
final AndroidTask<UninstallTask> uninstallTask = androidTasks.create(
tasks, new UninstallTask.ConfigAction(variantScope));
tasks.named(UNINSTALL_ALL, uninstallAll -> uninstallAll.dependsOn(uninstallTask.getName()));
}
复制代码
上面我们梳理了createPackingApp的大致流程,接下来我们看packageApp这个任务,内部的逻辑。
PackageApplication
StandardConfigAction
InstantRunResourcesConfigAction
负责配置任务,只将resource和assets下目录文件输出到apk中
PackageApplication类中,没看看具体的方法,我们查看其父类
其中有doFullTaskAction和doIncrementalTaskAction的实现。其就是Task调用@TaskAction的方法的具体实现
这是因为他们都是IncrementalTask
@TaskAction
void taskAction(IncrementalTaskInputs inputs) throws Exception {
if (!isIncremental() || !inputs.isIncremental()) {
getProject().getLogger().info("Unable do incremental execution: full task run");
doFullTaskAction();
return;
}
doIncrementalTaskAction(getChangedInputs(inputs));
}
复制代码
doFullTaskAction
其主要工作是处理资源
- 获取合并后的resoure文件
- 其通过BuildOutputs加载对应的文件,创建对应的资源集合。
- 然后通过splitFullAction进行遍历并行处理
其内部使用的线程池
@Override
protected void doFullTaskAction() throws IOException {
Collection<BuildOutput> mergedResources =
BuildOutputs.load(getTaskInputType(), resourceFiles);
outputScope.parallelForEachOutput(
mergedResources, getTaskInputType(), getTaskOutputType(), this::splitFullAction);
outputScope.save(getTaskOutputType(), outputDirectory);
}
复制代码
其中doIncrementalTaskAction与doFullTaskAction类似,其最终都会调用doTask。其区doFullTaskAction的区别其是增量处理文件,而doFullTaskAction会删除之前的。我们查看doFullTaskAction的splitFullAction
splitFullAction
publis File splitFullAction(...){
if (incrementalDirForSplit.exists()) {
FileUtils.deleteDirectoryContents(incrementalDirForSplit);
} else {
FileUtils.mkdirs(incrementalDirForSplit);
}
...
File outputFile = getOutputFiles().get(apkData);
FileUtils.deleteIfExists(outputFile);
//更新要打包到apk的文件
ImmutableMap<RelativeFile, FileStatus> updatedDex =
IncrementalRelativeFileSets.fromZipsAndDirectories(getDexFolders());
ImmutableMap<RelativeFile, FileStatus> updatedJavaResources = getJavaResourcesChanges();
ImmutableMap<RelativeFile, FileStatus> updatedAssets =
IncrementalRelativeFileSets.fromZipsAndDirectories(assets.getFiles());
ImmutableMap<RelativeFile, FileStatus> updatedAndroidResources =
IncrementalRelativeFileSets.fromZipsAndDirectories(androidResources);
ImmutableMap<RelativeFile, FileStatus> updatedJniResources =
IncrementalRelativeFileSets.fromZipsAndDirectories(getJniFolders());
Collection<BuildOutput> manifestOutputs = BuildOutputs.load(manifestType, manifests);
//doTask处理
doTask(apkData,...);
...
}
复制代码
doTask
其将上面处理后的文件,打包合成apk
private void doTask(...){
//合并到apk的java资源,不是class,例如MATAINfo
ImmutableMap.Builder<RelativeFile, FileStatus> javaResourcesForApk =
ImmutableMap.builder();
将处理的changedJavaResources加入
javaResourcesForApk.putAll(changedJavaResources);
...
//要打包的apk的dex资源
final ImmutableMap<RelativeFile, FileStatus> dexFilesToPackage = changedDex;
//查找对应的Manifest文件
BuildOutput manifestForSplit =
OutputScope.getOutput(manifestOutputs, manifestType, apkData);
//创建对应的packager
try(IncrementalPackager packager =
new IncrementalPackagerBuilder()
...
build()
)
/*
* 将上面打包使用的文件保存在缓存中
*/
Stream.concat(dexFilesToPackage.keySet().stream(),
Stream.concat(
changedJavaResources.keySet().stream(),
Stream.concat(
changedAndroidResources.keySet().stream(),
changedNLibs.keySet().stream())))
.map(RelativeFile::getBase)
.filter(File::isFile)
.distinct()
.forEach(
(File f) -> {
try {
cacheByPath.add(f);
} catch (IOException e) {
throw new IOExceptionWrapper(e);
}
});
}
复制代码
IncrementalPackager
上面创建的IncrementalPackager进行合并apk最后操作,其资源来自aapt的输出文件,java资源文件,dex文件,以及jni文件
其内部通过ApkCreator创建apk文件
// Creates or updates APKs based on provided entries.
public interface ApkCreator extends Closeable {
...
}
public IncrementalPackager(@NonNull ApkCreatorFactory.CreationData creationData,
@NonNull File intermediateDir, @NonNull ApkCreatorFactory factory,
@NonNull Set<String> acceptedAbis, boolean jniDebugMode)
throws PackagerException, IOException {
if (!intermediateDir.isDirectory()) {
throw new IllegalArgumentException(
"!intermediateDir.isDirectory(): " + intermediateDir);
}
checkOutputFile(creationData.getApkPath());
//创建 IncrementalPackager会创建ApkCreator
mApkCreator = factory.make(creationData);
mDexRenamer = new DexIncrementalRenameManager(intermediateDir);
mAbiPredicate = new NativeLibraryAbiPredicate(acceptedAbis, jniDebugMode);
}
复制代码
Apk的对应实现为ApkZFileCreateor,使用ZipOption生成Apk,在其构造方法中。
ApkZFileCreator(
@Nonnull ApkCreatorFactory.CreationData creationData,
@Nonnull ZFileOptions options)
throws IOException {
switch (creationData.getNativeLibrariesPackagingMode()) {
case COMPRESSED:
noCompressPredicate = creationData.getNoCompressPredicate();
break;
case UNCOMPRESSED_AND_ALIGNED:
noCompressPredicate =
creationData.getNoCompressPredicate().or(
name -> name.endsWith(NATIVE_LIBRARIES_SUFFIX));
options.setAlignmentRule(
AlignmentRules.compose(SO_RULE, options.getAlignmentRule()));
break;
default:
throw new AssertionError();
}
zip = ZFiles.apk(
creationData.getApkPath(),
options,
creationData.getPrivateKey(),
creationData.getCertificate(),
creationData.isV1SigningEnabled(),
creationData.isV2SigningEnabled(),
creationData.getBuiltBy(),
creationData.getCreatedBy(),
creationData.getMinSdkVersion());
closed = false;
}
复制代码
ZFile生成Apk文件
其根据给定的文件合成apk
public static ZFile apk(...){
ZFile zfile = apk(f, options);
...
return zfile
}
/**
* Creates a new zip file configured as an apk, based on a given file.
*
* @param f the file, if this path does not represent an existing path, will create a
* {@link ZFile} based on an non-existing path (a zip will be created when
* {@link ZFile#close()} is invoked)
* @param options the options to create the {@link ZFile}
* @return the zip file
* @throws IOException failed to create the zip file
*/
public static ZFile apk(@Nonnull File f, @Nonnull ZFileOptions options) throws IOException {
options.setAlignmentRule(
AlignmentRules.compose(options.getAlignmentRule(), APK_DEFAULT_RULE));
return new ZFile(f, options);
}
复制代码
到这里我们分析了创建Apk的任务的创建,即其如何合成apk的大致流程。然后我们与官方的提供的apk流程图对比,就很清晰直观的了解其流程了
Apk打包流程
createLinkTasks
其比较简单,就是更新我们之前创建的锚点LinkTask
public void createLintTasks(TaskFactory tasks, final VariantScope scope) {
if (!isLintVariant(scope)) {
return;
}
androidTasks.create(tasks, new LintPerVariantTask.ConfigAction(scope));
}
复制代码
aapt资源文件生成
createApkProcessResTask
其生成R文件和其他res下的文件处理是由其发起的。
其内部调用createProcessResTask方法,先创建对应的文件
public void createApkProcessResTask(
@NonNull TaskFactory tasks,
@NonNull VariantScope scope) {
createProcessResTask(
tasks,
scope,
() ->
//创建对应的文件:intermediates/symbols...
new File(
globalScope.getIntermediatesDir(),
"symbols/"
+ scope.getVariantData()
.getVariantConfiguration()
.getDirName()),
scope.getProcessResourcePackageOutputDirectory(),
MergeType.MERGE,
scope.getGlobalScope().getProjectBaseName());
}
复制代码
我们打开对应的build中,会发现一个以symbo开头的文件
createProcessResTask
其内部就会合成我们上面对应的文件
public AndroidTask<ProcessAndroidResources> createProcessResTask(...){
....
//这是对应主app的r文本文件
File symbolTableWithPackageName =
FileUtils.join(
globalScope.getIntermediatesDir(),
FD_RES,
"symbol-table-with-package",
scope.getVariantConfiguration().getDirName(),
"package-aware-r.txt");
//然后创建对应的处理任务
AndroidTask<ProcessAndroidResources> processAndroidResources =
androidTasks.create(
tasks,
createProcessAndroidResourcesConfigAction(
scope,
symbolLocation,
symbolTableWithPackageName,
resPackageOutputFolder,
useAaptToGenerateLegacyMultidexMainDexProguardRules,
mergeType,
baseName));
//后边是将该任务的输出添加
scope.addTaskOutput(
VariantScope.TaskOutputType.PROCESSED_RES, resPackageOutputFolder, taskName);
scope.addTaskOutput(
VariantScope.TaskOutputType.SYMBOL_LIST,
new File(symbolLocation.get(), FN_RESOURCE_TEXT),
taskName);
// Synthetic output for AARs (see SymbolTableWithPackageNameTransform), and created in
// process resources for local subprojects.
//这里我们看到了我们上面文件的名称
scope.addTaskOutput(
VariantScope.TaskOutputType.SYMBOL_LIST_WITH_PACKAGE_NAME,
symbolTableWithPackageName,
taskName);
scope.setProcessResourcesTask(processAndroidResources);
scope.getSourceGenTask().optionalDependsOn(tasks, processAndroidResources);
return processAndroidResources;
}
复制代码
阅读为对应的代码其主要是创建对应的任务,然后将对应的任务输出添加到对应varientScope中。
接下来我们查看createProcessAndroidResourcesConfigAction
createProcessAndroidResourcesConfigAction
其主要是创建对应的任务,其对应的类为ProcessAndroidResources,与上面的PackageApp类似,其也是IncrementTask子类,我们直接看对应的TaskAction的实现方法,其内部会调用invokeApaptForSplit进行R资源的生成。
protected void doFullTaskAction(){
...
// aapt不存在,则创建
try (Aapt aapt = bypassAapt ? null : makeAapt()) {
List<ApkData> apkDataList = new ArrayList<>(splitsToGenerate);
//处理split下的
for (ApkData apkData : splitsToGenerate) {
if (apkData.requiresAapt()) {
boolean codeGen =(apkData.getType() == OutputFile.OutputType.MAIN
|| apkData.getFilter(OutputFile.FilterType.DENSITY) == null);
if (codeGen) {
apkDataList.remove(apkData);
invokeAaptForSplit(manifestsOutputs,
libraryInfoList,
packageIdFileSet,
splitList,
featureResourcePackages,
apkData,
odeGen,
aapt);
break;
}
}
}
//处理全部的
for (ApkData apkData : apkDataList) {
if (apkData.requiresAapt()) {
executor.execute(
() -> {
invokeAaptForSplit(
manifestsOutputs,
libraryInfoList,
packageIdFileSet,
splitList,
featureResourcePackages,
apkData,
false,
aapt);
return null;
});
}
}
...
}
复制代码
invokeAaptForSplit
其主要配置后需要处理的资源文件,然后调用AndroidBuild处理,AndroidBuild内部使用aapt2处理,下面我们将其内部拆分成基本进行分析
配置对应资源文件
//根据featureResourcePackage创建对应BuildOut集合,将对应的资源与输出匹配
for (File featurePackage : featureResourcePackages) {
Collection<BuildOutput> splitOutputs =
BuildOutputs.load(VariantScope.TaskOutputType.PROCESSED_RES, featurePackage);
if (!splitOutputs.isEmpty()) {
featurePackagesBuilder.add(Iterables.getOnlyElement(splitOutputs).getOutputFile());
}
}
//创建res输出文件通用路径,其为临时文件,后续合并后就删除了。
//intermediates/res-main.ap_
File resOutBaseNameFile =
new File(
//intermediates
resPackageOutputFolder,
FN_RES_BASE
+ RES_QUALIFIER_SEP
+ apkData.getFullName()
+ SdkConstants.DOT_RES);
//创建对应manifest输出的BuildOutput
BuildOutput manifestOutput =
OutputScope.getOutput(manifestsOutputs, taskInputType, apkData);
//其他文件
String packageForR = null;
File srcOut = null;
File symbolOutputDir = null;
File proguardOutputFile = null;
File mainDexListProguardOutputFile = null;
复制代码
后面的步骤与整个流程关系不大,默认是不生成code,且InstanceRun流程不是我们本文关注的重点,且是使用的aapt所以这些逻辑就不用查看
if (generateCode) {
...
}
if (buildContext.isInInstantRunMode(){
...
}
if (bypassAapt) {
...
}
复制代码
创建AaptPackageConfig
接下来即创建aapt对应的配置,使其根据我们的配置进行处理。
AaptPackageConfig.Builder config =
new AaptPackageConfig.Builder()
.setManifestFile(manifestFile)
.setOptions(DslAdaptersKt.convert(aaptOptions))
.setResourceDir(getInputResourcesDir().getSingleFile())
.setLibrarySymbolTableFiles(
generateCode
? dependencySymbolTableFiles
: ImmutableSet.of())
.setCustomPackageForR(packageForR)
.setSymbolOutputDir(symbolOutputDir)
.setSourceOutputDir(srcOut)
.setResourceOutputApk(resOutBaseNameFile)
.setProguardOutputFile(proguardOutputFile)
.setMainDexListProguardOutputFile(mainDexListProguardOutputFile)
.setVariantType(getType())
.setDebuggable(getDebuggable())
.setPseudoLocalize(getPseudoLocalesEnabled())
.setResourceConfigs(
splitList.getFilters(SplitList.RESOURCE_CONFIGS))
.setSplits(getSplits(splitList))
.setPreferredDensity(preferredDensity)
.setPackageId(packageId)
.setDependentFeatures(featurePackagesBuilder.build())
.setListResourceFiles(aaptGeneration == AaptGeneration.AAPT_V2);
getBuilder().processResources(aapt, config);
复制代码
其中aapt是在调用该方法是,如果为空,就会创建,其实在doFullTaskAction中调用创建的
try (Aapt aapt = bypassAapt ? null : makeAapt()) {
...
}
...
invokdeAaptForSplit(...)
复制代码
其通过AaptGradleFactory创建
private Aapt makeAapt(){
AndroidBuilder builder = getBuilder();
...
return AaptGradleFactory.make(
aaptGeneration,
builder,
processOutputHandler,
fileCache,
true,
FileUtils.mkdirs(new File(getIncrementalFolder(), "aapt-temp")),
aaptOptions.getCruncherProcesses());
}
复制代码
分析完aapt创建我们继续回到上面,看起配置了那些,上面的代码设置的比较多,我简单列出一些主要的
- 设置Manifest文件:setManifestFile
- 设置ResourceDir: setResourceDir
- 设置库的r文件表: setLibrarySymbolTableFiles
- 设置R文件字符串内容:setCustomPackageForR
- 设置资源生成工具版本:setListResourceFiles(aaptGeneration == AaptGeneration.AAPT_V2)
调用AndroidBuild处理资源
在查看R文件生成和资源处理前,我们看一下对应的symbole下的package_aware_r.txt,首先是主工程的
其第一行是包名,然后每一行一次是:资源类型 name
com.example.test
anim abc_fade_in
anim abc_fade_out
anim abc_grow_fade_in_from_bottom
anim abc_popup_enter
anim abc_popup_exit
anim abc_shrink_fade_out_from_bottom
anim abc_slide_in_bottom
anim abc_slide_in_top
anim abc_slide_out_bottom
anim abc_slide_out_top
attr counterTextColor
id tv_default_contact_wxchat
复制代码
然后我们在看lib的
int dimen mtrl_snackbar_margin 0x0
int[] styleable ViewBackgroundHelper { 0x10100d4, 0x0, 0x0 }
...
复制代码
看完对应的文本对其有一定的认知后,我们查看AndroidBuild的processResource
/**
*处理 resource同时生成R文件,或其他包资源
*
*/
public void processResources(){
try {
aapt.link(aaptConfig).get();
} catch (Exception e) {
throw new ProcessException("Failed to execute aapt", e);
}
//加载宿主中的即app工程中r文本文件
File mainRTxt = new File(aaptConfig.getSymbolOutputDir(), "R.txt");
SymbolTable mainSymbols =
mainRTxt.isFile()
? SymbolIo.readFromAapt(mainRTxt, mainPackageName)
: SymbolTable.builder().tablePackage(mainPackageNam
// 加载我们依赖的库中的R.txt文件
Set<SymbolTable> depSymbolTables =
SymbolUtils.loadDependenciesSymbolTables(
aaptConfig.getLibrarySymbolTableFiles(), mainPa
boolean finalIds = true;
//判断是否是Library,如果是,生成的id不是final的
if (aaptConfig.getVariantType() == VariantType.LIBRARY) {
finalIds = false;
//调用RGeneration生成对应的R.java文件
RGeneration.generateRForLibraries(mainSymbols, depSymbolTables, sourceOut, finalIds);
}
复制代码
- 对与Aapt的link方法,我们看官方文档说明
在链接阶段,AAPT2 会合并在编译阶段生成的所有中间文件(如资源表、二进制 XML 文件和处理过的 PNG 文件)
对与aapt在何时压缩处理png文件,我们后面在分析。
- 其中我们看到对于library其生成的id不是final的,这也从代码层级解释可为什么在单独的组件中不能在switch中使用R.id.xx原因
RGeneration生成R.java
我们直接看起代码,然后总结其主要步骤
public static void generateRForLibraryies() {
//创建Map存储要写入的SymboleTable,即对应的r.txt
Map<String, SymbolTable> toWrite = new HashMap<>();
//遍历调用方法传入的资源集合,放入toWraiteMap,这里默认
// mian以及完成,因此过滤掉
for (SymbolTable st : libraries) {
if (st.getTablePackage().equals(main.getTablePackage())) {
continue;
SymbolTable existing = toWrite.get(st.getTablePackage());
if (existing != null) {
toWrite.put(st.getTablePackage(), existing.merge(st));
} else {
toWrite.put(st.getTablePackage(), st);
}
}
//遍历上面的每一个集合的key,将其都放入mainPackage下
//因为依赖关系重复的资源id,如果资源不存在会自动剔除
/**
* 例如 Library A有两个版本1,2;其中1含有资源X,2不含有
* Library B 依赖 A的 1版本 因此其symbole X会包含继承自A的资源X,但是其资源里面
* 并没有资源X
* 因为Library A 2 中不含有资源X,因此其symboleTable也不会有资源X
* 如果应用或Library同时依赖B和Library A 2版本,因此依赖解决方案,
* 其会自动依赖高版本的2,这导致资源X存在B中的symboleTabe,但是资源里面并不存在
* 最终因为资源不存在其在合成主的symbol table 是并不会存在
for (String pkg : new HashSet<>(toWrite.keySet())) {
SymbolTable st = toWrite.get(pkg);
st = main.filter(st).rename(st.getTablePackage());
toWrite.put(pkg, st);
}
//最后生成R.java文件
toWrite.values().forEach(st -> SymbolIo.exportToJava(st, out, finalIds));
}
复制代码
通过分析代码,其主要分为三个步骤
- 创建并填充toWriteMap
- 合并生成主symboleTable,移除不存在的资源的symbol
- 生成R.java文件
到这里我们分析了资源文件R如何生成,以及资源处理后如何链接合并的。
PNG压缩
AaptOptions
我们查看对应的代码发现是否开启png压缩的配置以及过时,有 BuildType.isCrunchPngs控制
//This is replaced by {@link BuildType#isCrunchPngs()}.
@Deprecated
public boolean getCruncherEnabled() {
// Simulate true if unset. This is not really correct, but changing it to be a tri-state
// nullable Boolean is potentially a breaking change if the getter was being used by build
// scripts or third party plugins.
return cruncherEnabled == null ? true : cruncherEnabled;
}
复制代码
最新的BuildType.isCrunchPng,最终会在负责合并资源的MergeRource中使用
public void execute(@NonNull MergeResources mergeResourcesTask){
...
mergeResourcesTask.crunchPng = scope.isCrunchPngs();
...
}
复制代码
其中scope是VariantScopeImpl
public boolean isCrunchPngs() {
Boolean buildTypeOverride = getVariantConfiguration().getBuildType().isCrunchPngs();
}
复制代码
因此压缩是在MergeResource这个Task中发起的。而起内部使用的为aapt
protected void doFullTaskAction(){
processResources ? makeAapt(
aaptGeneration,
getBuilder(),
fileCache,
crunchPng,//压缩
variantScope,
getAaptTempDir(),
mergingLog)
}
复制代码
其跟上面分析R资源文件生成类似,也是通过AaptGradleFactory创建的
public static Aapt makeAapt()
{
return AaptGradleFactory.make(
aaptGeneration,
...
crunchPng,
intermediateDir,
scope.getGlobalScope().getExtension().getAaptOptions().getCruncherProcesses());
}
复制代码
我们发现这份只是在AAptV1中使用了
public static Aapt make(...){
switch (aaptGeneration) {
case AAPT_V1:
return new AaptV1(
builder.getProcessExecutor(),
teeOutputHandler,
buildTools,
new FilteringLogger(builder.getLogger()),
crunchPng ? AaptV1.PngProcessMode.ALL : AaptV1.PngProcessMode.NO_CRUNCH,
cruncherProcesses);
case AAPT_V2:
return new OutOfProcessAaptV2(
builder.getProcessExecutor(),
teeOutputHandler,
buildTools,
intermediateDir,
new FilteringLogger(builder.getLogger()));
case AAPT_V2_JNI:
return new AaptV2Jni(
intermediateDir,
WaitableExecutor.useGlobalSharedThreadPool(),
teeOutputHandler,
fileCache);
case AAPT_V2_DAEMON_MODE:
return new QueueableAapt2(
teeOutputHandler,
buildTools,
intermediateDir,
new FilteringLogger(builder.getLogger()),
0 /* use default */);
....
}
复制代码
我们知道Aapt1已经弃用,那其就不压缩png了吗?
我们先查看Aapt1中处理其压缩的类是那个
QueudCruncher
public AaptV1() {
this.cruncher =
QueuedCruncher.builder()
.executablePath(getAaptExecutablePath())
.logger(logger)
.numberOfProcesses(cruncherProcesses)
.build();
if (cruncher != null) {
cruncherKey = cruncher.start();
} else {
cruncherKey = null;
}
}
复制代码
其真正处理是有QueudCruncher,而起为QueuedResourceProcessor子类,且start也是父类的,而其创建任务都是尤其父类完成
我们查看父类构造方法
QueueResourceProcessor
proteeted QueuedResourceProcessor() {
f (processesNumber > 0) {
processToUse = processesNumber;
} else {
processToUse = DEFAULT_NUMBER_DAEMON_PROCESSES;
}
processingRequests =
new WorkQueue<>(
logger, queueThreadContext, "queued-resource-processor", processToUse, 0);
}
复制代码
�其会根据processNumbler创建对应的processingRequests.
在Aapt1下,其真正处理工作是QueudCruncher的compile方法。其最最上层资源处理ResourceProcessor的方法,当调用start就会调用生成其处理需要的key,其调用是在Aapt1中的compile方法
public ListenableFuture<File> compile(@NonNull CompileResourceRequest request){
...
futureResult = cruncher.compile(cruncherKey, request, null);
...
}
复制代码
在cruncher.compile中,创建的job会使用我们父类构造创建的processingRequests,到这里是否压缩的选项就传递下去了,我们看cruncher的compile方法
public ListenableFuture<File> compile(...){
final Job<AaptProcess> aaptProcessJob =
new AaptQueueThreadContext.QueuedJob({
new Task<AaptProcess>() {
@Override
public void run(
@NonNull Job<AaptProcess> job,
@NonNull JobContext<AaptProcess> context)
throws IOException {
AaptProcess aapt = context.getPayload();
if (aapt == null) {
logger.error(
null,
"Thread(%1$s) has a null payload",
Thread.currentThread().getName());
return;
}
aapt.crunch(request.getInput(), outputFile, job);
}
....
processingRequests.push(aaptProcessJob);
})
}
复制代码
查看代码,发现其会将任务加入其队列,然后进行每一个任务时,判断是否需要压缩,真正的压缩还是有aapt的curnch完成。当调用start方法就是开启任务,任务具体执行有WorkQueue操作,其run方法会处理其里面的job.
上面分析完Aapt1我们在看aapt2,我们发现Aapt2QueuedResourceProcessor,其跟QueuedCruncher一样都是QueuedResourceProcessor的子类,因此推断其应该具有压缩功能。但其并没有接受对应的参数。该选项没有什么作用,估计后续应该会修复
该参数起作用在Aapt1中主要是判断如果不需要压缩,在compile中就会返回copyFile,而不是创建对应的job。
if (!processMode.shouldProcess(request.getInput())) {
return copyFile(request);
}
//上面分析的开启压缩的方法
try {
futureResult = cruncher.compile(cruncherKey, request, null);
}
...
复制代码
Aapt2压缩�
Aapp2默认创建类为QueueableAapt2,我们看起方法是否有过滤
try {
futureResult = aapt.compile(requestKey, request, processOutputHandler);
} catch (ResourceCompilationException e) {
throw new Aapt2Exception(
String.format("Failed to compile file %s", request.getInput()), e);
}
复制代码
最终会调用到AaptProcess中的方法,其与Aapt1不同,其最终调用的仍然是compile,而不是crunch
到这里并没有发现其是如何压缩的,这是因为其是通过拼接对应的命令行实现的。
我们以Aapt,简单分析其如何构建的命令
Aapt命令构建
在上面AaptGradleFactory创建aapt是会传入TargetInfo,其内部持有了buildInfo,其会传递给对应的Aapt实例
TargetInfo target = builder.getTargetInfo();
BuildToolInfo buildTools = target.getBuildTools();
...
return new QueueableAapt2(
teeOutputHandler,
buildTools,
intermediateDir,
new FilteringLogger(builder.getLogger()),
0 /* use default */);
...
复制代码
BuildTools
其对象创建是会添加对接aapt2命令
BuildToolInfo buildToolInfo = BuildToolInfo.modifiedLayout(
buildToolRevision,
mTreeLocation,
new File(hostTools, FN_AAPT),
new File(hostTools, FN_AIDL),
new File(mTreeLocation, "prebuilts/sdk/tools/dx"),
new File(mTreeLocation, "prebuilts/sdk/tools/lib/dx.jar"),
new File(hostTools, FN_RENDERSCRIPT),
new File(mTreeLocation, "prebuilts/sdk/renderscript/include"),
new File(mTreeLocation, "prebuilts/sdk/renderscript/clang-include"),
new File(hostTools, FN_BCC_COMPAT),
new File(hostTools, "arm-linux-androideabi-ld"),
new File(hostTools, "aarch64-linux-android-ld"),
new File(hostTools, "i686-linux-android-ld"),
new File(hostTools, "x86_64-linux-android-ld"),
new File(hostTools, "mipsel-linux-android-ld"),
new File(hostTools, FN_ZIPALIGN),
new File(hostTools, FN_AAPT2));
return new TargetInfo(androidTarget, buildToolInfo);
public static final String FN_AAPT2 =
"aapt2" + ext(".exe", "");
复制代码
在创建对应的Aapt2对象是,会使用其获取Aapt2的执行路径
public QueueableAapt2(
@Nullable ProcessOutputHandler processOutputHandler,
@NonNull BuildToolInfo buildToolInfo,
@NonNull File intermediateDir,
@NonNull ILogger logger,
int numberOfProcesses) {
(
processOutputHandler,
getAapt2ExecutablePath(buildToolInfo),
intermediateDir,
logger,
numberOfProcesses);
}
复制代码
在创建上面最终执行的AaptProcess会将该路径设置进去
//in AaptueuThreadContext.java
@Override
public boolean creation(@NonNull Thread t) throws IOException, InterruptedException {
try {
AaptProcess aaptProcess = new AaptProcess.Builder(aaptLocation, logger).start();
boolean ready = aaptProcess.waitForReadyOrFail();
if (ready) {
aaptProcesses.put(t.getName(), aaptProcess);
}
return ready;
} catch (InterruptedException e) {
logger.error(e, "Cannot start slave process");
throw e;
}
}
复制代码
这样aaptProcess就将对应的命令添加在其后,即可调用我们下载的Sdk目录下的aapt工具
我们在对应的方法的错误注解也可以看到,就不在分析了。
Aapt2工具压缩Png
我们直接查看SDK下的aapt工具源码,在其Png.cpp中查看对应方法,其方法是analyze_image,具体内部逻辑就不分析了。
analyze_image(logger, *info, grayScaleTolerance, rgbPalette, alphaPalette,
&paletteEntries, &hasTransparency, &colorType, outRows){
}
复制代码
到这里PNG压缩的整个流程就分析完了,同城也分析了aapt调用的整体流程。其中其压缩底层使用的是libpng这个库。
在aapt2/compile可找到PngCrunch.cpp,在其里面可找到libpng
在Android.bp中也可看到
static_libs: [
"libandroidfw",
"libutils",
"liblog",
"libcutils",
"libexpat",
"libziparchive",
"libpng",
"libbase",
"libprotobuf-cpp-full",
"libz",
"libbuildversion",
"libidmap2_policies",
],
stl: "libc++_static",
复制代码
总结
本文梳理了AGP内部的整体流程,着重分析了任务创建,即主要创建了什么任务,即这些任务做了什么,结合官方Apk打包流程图,清晰的理解了其流程。同时也分析了R.java文件的生成,解释了为什么组件化中,不可在swtich中使用。最后简单分析了Aapt如何压缩png的流程,即aapt如何被使用的简单流程。