Gradle构建之Android Gradle Plugin

背景

Gradle是一个开源的自动化构建工具,其设计非常灵活可适用不同平台。针对不同平台其通过编写不同的插件去实现,其本身架构不做处理。保持了很好的扩展性和灵活性。 针对android平台其使用的是android gradle plugin工具.

准备工作

要分析其整个流程,我们首先就要准备对应的源码。这里有两种方式,一种是使用上一篇的思路直接下载gradle源码,其包含了AGP. 今天我们介绍第二种方案,只下载AGP.

AGP源码下载

  1. 使用Android Studio新建一个项目,删除app下除了build.gradle的其他文件
  2. 修改app下的build.gradle,移除其他配置,改成下面方式
plugins {
    id 'java'
}
sourceCompatibility = 1.8

dependencies {
    implementation gradleApi()
    implementation "com.android.tools.build:gradle:3.0.0"
}
复制代码
  1. 修改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版本分析。
AGP_1.jpg
这里我们可以看到多个Plugin,其中有两个AppPlugin和LibraryPlguin,因为我们开发app,其目录下使用的是appplugin,所以我们下面就依次为入口,分析apk的整体构建流程。

APK构建

apk构建源码流程图

AGP流程.jpg

通过打包流程的梳理,其大致分为三个简单

  • 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

该方法用于生成最终的Dex文件,细节就不进行分析了

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

其主要负责打包Apk

StandardConfigAction

负责配置任务,执行标准的打包,将包含的文件输出到apk中

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

其主要工作是处理资源

  1. 获取合并后的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打包流程

打包流程 (1).png

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开头的文件
r.jpg

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);
}
复制代码
  1. 对与Aapt的link方法,我们看官方文档说明

在链接阶段,AAPT2 会合并在编译阶段生成的所有中间文件(如资源表、二进制 XML 文件和处理过的 PNG 文件)

对与aapt在何时压缩处理png文件,我们后面在分析。

  1. 其中我们看到对于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压缩

最后我们看一下aapt如何压缩文件

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如何被使用的简单流程。

猜你喜欢

转载自juejin.im/post/7033740375449665567