gradle编译打包过程分析之ProcessAndroidResources

2022-02-24 07:57:09 浏览数 (1)

引入源码

首先,如何查看gradle源码,我们在项目里依赖com.android.tools.build:gradle即可,如下:

代码语言:javascript复制
compile gradleApi()
compile 'com.android.tools.build:gradle:2.3.3'

sync gradle后就可以看到相关的源码了

分析

我们要了解的是apk的打包过程,实际上是gradle的一个插件application

代码语言:javascript复制
apply plugin: 'com.android.application'

所以我们在gradle的源码下找到AppPligin,其部分源码如下:

代码语言:javascript复制
public class AppPlugin extends BasePlugin implements Plugin<Project> {
    @Inject
    public AppPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
        super(instantiator, registry);
    }

    ...

    @NonNull
    @Override
    protected TaskManager createTaskManager(
            @NonNull Project project,
            @NonNull AndroidBuilder androidBuilder,
            @NonNull DataBindingBuilder dataBindingBuilder,
            @NonNull AndroidConfig androidConfig,
            @NonNull SdkHandler sdkHandler,
            @NonNull NdkHandler ndkHandler,
            @NonNull DependencyManager dependencyManager,
            @NonNull ToolingModelBuilderRegistry toolingRegistry,
            @NonNull Recorder recorder) {
        return new ApplicationTaskManager(
                project,
                androidBuilder,
                dataBindingBuilder,
                androidConfig,
                sdkHandler,
                ndkHandler,
                dependencyManager,
                toolingRegistry,
                recorder);
    }

    @Override
    public void apply(@NonNull Project project) {
        super.apply(project);
    }

    ...
}

这里我们关注createTaskManager函数,可以看到它返回了一个ApplicationTaskManager对象,这个类的部分源码如下:

代码语言:javascript复制
public class ApplicationTaskManager extends TaskManager {
    ...

    @Override
    public void createTasksForVariantData(
            @NonNull final TaskFactory tasks,
            @NonNull final BaseVariantData<? extends BaseVariantOutputData> variantData) {
        assert variantData instanceof ApplicationVariantData;

        final VariantScope variantScope = variantData.getScope();

        createAnchorTasks(tasks, variantScope);
        createCheckManifestTask(tasks, variantScope);

        handleMicroApp(tasks, variantScope);

        // Create all current streams (dependencies mostly at this point)
        createDependencyStreams(tasks, variantScope);

        // Add a task to process the manifest(s)
        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_MANIFEST_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> createMergeAppManifestsTask(tasks, variantScope));

        // Add a task to create the res values
        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_GENERATE_RES_VALUES_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> createGenerateResValuesTask(tasks, variantScope));

        // Add a task to compile renderscript files.
        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_CREATE_RENDERSCRIPT_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> createRenderscriptTask(tasks, variantScope));

        // Add a task to merge the resource folders
        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_RESOURCES_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                (Recorder.VoidBlock) () -> createMergeResourcesTask(tasks, variantScope));

        // Add a task to merge the asset folders
        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_ASSETS_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> createMergeAssetsTask(tasks, variantScope));

        // Add a task to create the BuildConfig class
        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_BUILD_CONFIG_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> createBuildConfigTask(tasks, variantScope));

        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_PROCESS_RES_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> {
                    // Add a task to process the Android Resources and generate source files
                    createApkProcessResTask(tasks, variantScope);

                    // Add a task to process the java resources
                    createProcessJavaResTasks(tasks, variantScope);
                });

        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_AIDL_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> createAidlTask(tasks, variantScope));

        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_SHADER_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> createShaderTask(tasks, variantScope));

        // Add NDK tasks
        if (!isComponentModelPlugin) {
            recorder.record(
                    ExecutionType.APP_TASK_MANAGER_CREATE_NDK_TASK,
                    project.getPath(),
                    variantScope.getFullVariantName(),
                    () -> createNdkTasks(tasks, variantScope));
        } else {
            if (variantData.compileTask != null) {
                variantData.compileTask.dependsOn(getNdkBuildable(variantData));
            } else {
                variantScope.getCompileTask().dependsOn(tasks, getNdkBuildable(variantData));
            }
        }
        variantScope.setNdkBuildable(getNdkBuildable(variantData));

        // Add external native build tasks

        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_EXTERNAL_NATIVE_BUILD_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> {
                    createExternalNativeBuildJsonGenerators(variantScope);
                    createExternalNativeBuildTasks(tasks, variantScope);
                });

        // Add a task to merge the jni libs folders
        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_MERGE_JNILIBS_FOLDERS_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> createMergeJniLibFoldersTasks(tasks, variantScope));

        // Add a compile task
        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_COMPILE_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> {
                    CoreJackOptions jackOptions =
                            variantData.getVariantConfiguration().getJackOptions();
                    // create data binding merge task before the javac task so that it can
                    // parse jars before any consumer
                    createDataBindingMergeArtifactsTaskIfNecessary(tasks, variantScope);
                    AndroidTask<? extends JavaCompile> javacTask =
                            createJavacTask(tasks, variantScope);
                    if (jackOptions.isEnabled()) {
                        AndroidTask<TransformTask> jackTask =
                                createJackTask(tasks, variantScope, true /*compileJavaSource*/);
                        setJavaCompilerTask(jackTask, tasks, variantScope);
                    } else {
                        // Prevent the use of java 1.8 without jack, which would otherwise cause an
                        // internal javac error.
                        if (variantScope
                                .getGlobalScope()
                                .getExtension()
                                .getCompileOptions()
                                .getTargetCompatibility()
                                .isJava8Compatible()) {
                            // Only warn for users of retrolambda and dexguard
                            if (project.getPlugins().hasPlugin("me.tatarka.retrolambda")
                                    || project.getPlugins().hasPlugin("dexguard")) {
                                getLogger()
                                        .warn(
                                                "Jack is disabled, but one of the plugins you "
                                                          "are using supports Java 8 language "
                                                          "features.");
                            } else {
                                androidBuilder
                                        .getErrorReporter()
                                        .handleSyncError(
                                                variantScope
                                                        .getVariantConfiguration()
                                                        .getFullName(),
                                                SyncIssue
                                                        .TYPE_JACK_REQUIRED_FOR_JAVA_8_LANGUAGE_FEATURES,
                                                "Jack is required to support java 8 language "
                                                          "features. Either enable Jack or remove "
                                                          "sourceCompatibility "
                                                          "JavaVersion.VERSION_1_8.");
                            }
                        }
                        addJavacClassesStream(variantScope);
                        setJavaCompilerTask(javacTask, tasks, variantScope);
                        getAndroidTasks()
                                .create(
                                        tasks,
                                        new AndroidJarTask.JarClassesConfigAction(variantScope));
                        createPostCompilationTasks(tasks, variantScope);
                    }
                });

        // Add data binding tasks if enabled
        createDataBindingTasksIfNecessary(tasks, variantScope);

        createStripNativeLibraryTask(tasks, variantScope);

        if (variantData
                .getSplitHandlingPolicy()
                .equals(SplitHandlingPolicy.RELEASE_21_AND_AFTER_POLICY)) {
            if (getExtension().getBuildToolsRevision().getMajor() < 21) {
                throw new RuntimeException(
                        "Pure splits can only be used with buildtools 21 and later");
            }

            recorder.record(
                    ExecutionType.APP_TASK_MANAGER_CREATE_SPLIT_TASK,
                    project.getPath(),
                    variantScope.getFullVariantName(),
                    () -> createSplitTasks(tasks, variantScope));
        }

        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_PACKAGING_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> {
                    @Nullable
                    AndroidTask<BuildInfoWriterTask> fullBuildInfoGeneratorTask =
                            createInstantRunPackagingTasks(tasks, variantScope);
                    createPackagingTask(
                            tasks, variantScope, true /*publishApk*/, fullBuildInfoGeneratorTask);
                });

        // create the lint tasks.
        recorder.record(
                ExecutionType.APP_TASK_MANAGER_CREATE_LINT_TASK,
                project.getPath(),
                variantScope.getFullVariantName(),
                () -> createLintTasks(tasks, variantScope));
    }

    ...
}

在其createTasksForVariantData函数中,我们可以看到整个编译打包流程的所有task(不包括附加的),这里其实就概括了整个打包的流程,如下:

  • MERGE_MANIFEST
  • GENERATE_RES_VALUES
  • CREATE_RENDERSCRIPT
  • MERGE_RESOURCES
  • MERGE_ASSETS
  • BUILD_CONFIG
  • PROCESS_RES
  • AIDL
  • SHADER
  • NDK
  • EXTERNAL_NATIVE_BUILD
  • MERGE_JNILIBS_FOLDERS
  • COMPILE
  • SPLIT(这个是分包,只在21以上系统才会执行)
  • PACKAGING
  • LINT

基本上根据名字就知道在做什么,这里就不一个个细说了,我们重点关注PROCESS_RES这个task:

代码语言:javascript复制
recorder.record(
        ExecutionType.APP_TASK_MANAGER_CREATE_PROCESS_RES_TASK,
        project.getPath(),
        variantScope.getFullVariantName(),
        () -> {
            // Add a task to process the Android Resources and generate source files
            createApkProcessResTask(tasks, variantScope);

            // Add a task to process the java resources
            createProcessJavaResTasks(tasks, variantScope);
        });

它执行了两个小的task,我们来看createApkProcessResTask这个函数,它是ApplicationTaskManager的父类TaskManager的一个函数,代码如下:

代码语言:javascript复制
public void createApkProcessResTask(
        @NonNull TaskFactory tasks,
        @NonNull VariantScope scope) {
    createProcessResTask(
            tasks,
            scope,
            new File(globalScope.getIntermediatesDir(),
                    "symbols/"   scope.getVariantData().getVariantConfiguration().getDirName()),
            true);
}

public void createProcessResTask(
        @NonNull TaskFactory tasks,
        @NonNull VariantScope scope,
        @Nullable File symbolLocation,
        boolean generateResourcePackage) {
    ...

    // loop on all outputs. The only difference will be the name of the task, and location
    // of the generated data.
    for (BaseVariantOutputData vod : variantData.getOutputs()) {
        final VariantOutputScope variantOutputScope = vod.getScope();

        variantOutputScope.setProcessResourcesTask(androidTasks.create(tasks,
                new ProcessAndroidResources.ConfigAction(variantOutputScope, symbolLocation,
                        generateResourcePackage,
                        useAaptToGenerateLegacyMultidexMainDexProguardRules)));

        // always depend on merge res,
        variantOutputScope.getProcessResourcesTask().dependsOn(tasks,
                scope.getMergeResourcesTask());
        if (scope.getDataBindingProcessLayoutsTask() != null) {
            variantOutputScope.getProcessResourcesTask().dependsOn(tasks,
                    scope.getDataBindingProcessLayoutsTask().getName());
        }
        variantOutputScope
                .getProcessResourcesTask()
                .dependsOn(tasks, variantOutputScope.getManifestProcessorTask());

        if (vod.getMainOutputFile().getFilter(DENSITY) == null) {
            scope.setGenerateRClassTask(variantOutputScope.getProcessResourcesTask());
            scope.getSourceGenTask().optionalDependsOn(
                    tasks,
                    variantOutputScope.getProcessResourcesTask());
        }

    }
}

这里先是用到了ProcessAndroidResources的一个子类ConfigAction。

接着下面的代码则是规定了这个task的依赖规则,比如

代码语言:javascript复制
variantOutputScope.getProcessResourcesTask().dependsOn(tasks,
                scope.getMergeResourcesTask());

必须依赖MergeResources,即MergeResources这个task执行后才能执行。

让我们回到ProcessAndroidResources,它的子类ConfigAction部分源码如下:

代码语言:javascript复制
public static class ConfigAction implements TaskConfigAction<ProcessAndroidResources> {
    ...
    
    @Override
    public void execute(@NonNull ProcessAndroidResources processResources) {
        ...

        if (variantOutputData.getMainOutputFile()
                .getFilter(OutputFile.DENSITY) == null
                && variantData.generateRClassTask == null) {
            ...

            processResources
                    .setSourceOutputDir(scope.getVariantScope().getRClassSourceOutputDir());
            processResources.setTextSymbolOutputDir(symbolLocation);

            if (config.getBuildType().isMinifyEnabled()) {
                if (config.getBuildType().isShrinkResources() && config.getJackOptions().isEnabled()) {
                    LoggingUtil.displayWarning(Logging.getLogger(getClass()),
                            scope.getGlobalScope().getProject(),
                            "shrinkResources does not yet work with useJack=true");
                }
                processResources.setProguardOutputFile(
                        scope.getVariantScope().getProcessAndroidResourcesProguardOutputFile());

            } 
            ...
        }

        ConventionMappingHelper.map(processResources, "manifestFile", new Callable<File>() {
            @Override
            public File call() throws Exception {
                return variantOutputData.manifestProcessorTask.getOutputFile();
            }
        });

        ...
    }
}

在它的execute函数中可以看到设置了一些信息,比如各种文件的输出路径,这里我们拿SourceOutputDir来举例:

代码语言:javascript复制
processResources.setSourceOutputDir(scope.getVariantScope().getRClassSourceOutputDir());

getRClassSourceOutputDir函数是抽象类VariantScope的一个抽象方法,它的实现是在VariantScopeImpl中,代码如下:

代码语言:javascript复制
@Override
@NonNull
public File getRClassSourceOutputDir() {
    return new File(globalScope.getGeneratedDir(),
            "source/r/"   getVariantConfiguration().getDirName());
}

其中

  • globalScope.getGeneratedDir()就是[project]/app/build/generated/目录
  • getVariantConfiguration().getDirName()得到的是BuildType及Flavors(如果有),如debug/或baidu/debug/
  • getRClassSourceOutputDir函数得到的路径就是“[project]/app/build/generated/source/r/debug/”

关注过build/目录的同学应该知道,“[project]/app/build/generated/source/r/debug/”下在相应的包名目录下是R.java文件

那么这个路径在哪里使用,如何生成R.java的?

回到execute函数中,processResources实际上就是ProcessAndroidResources的一个对象,既然有setSourceOutputDir函数,那么也有个对应的get函数。

这个get函数则在ProcessAndroidResources的doFullTaskAction函数中被调用,这个函数部分源码如下:

代码语言:javascript复制
protected void doFullTaskAction() throws IOException {
    // we have to clean the source folder output in case the package name changed.
    File srcOut = getSourceOutputDir();
    ...
    try {
        ...
        AaptPackageConfig.Builder config =
                new AaptPackageConfig.Builder()
                        .setManifestFile(manifestFileToPackage)
                        .setOptions(getAaptOptions())
                        .setResourceDir(getResDir())
                        .setLibraries(getAndroidDependencies())
                        .setCustomPackageForR(getPackageForR())
                        .setSymbolOutputDir(getTextSymbolOutputDir())
                        .setSourceOutputDir(srcOut)
                        .setResourceOutputApk(resOutBaseNameFile)
                        .setProguardOutputFile(getProguardOutputFile())
                        .setMainDexListProguardOutputFile(getMainDexListProguardOutputFile())
                        .setVariantType(getType())
                        .setDebuggable(getDebuggable())
                        .setPseudoLocalize(getPseudoLocalesEnabled())
                        .setResourceConfigs(getResourceConfigs())
                        .setSplits(getSplits())
                        .setPreferredDensity(preferredDensity)
                        .setBaseFeature(getBaseFeature())
                        .setPreviousFeatures(getPreviousFeatures());

        builder.processResources(aapt, config, getEnforceUniquePackageName());

        ...

    } catch (IOException | InterruptedException | ProcessException e) {
        throw new RuntimeException(e);
    }
}

将这些信息又封装到一个AaptPackageConfig.Builder对象中,最后调用了一个processResources函数。

这个processResources是AndroidBuilder的一个函数,部分源码如下:

代码语言:javascript复制
public void processResources(
        @NonNull Aapt aapt,
        @NonNull AaptPackageConfig.Builder aaptConfigBuilder,
        boolean enforceUniquePackageName)
        throws IOException, InterruptedException, ProcessException {

    checkState(mTargetInfo != null,
            "Cannot call processResources() before setTargetInfo() is called.");

    aaptConfigBuilder.setBuildToolInfo(mTargetInfo.getBuildTools());
    aaptConfigBuilder.setAndroidTarget(mTargetInfo.getTarget());
    aaptConfigBuilder.setLogger(mLogger);

    AaptPackageConfig aaptConfig = aaptConfigBuilder.build();

    try {
        aapt.link(aaptConfig).get();
    } catch (Exception e) {
        throw new ProcessException("Failed to execute aapt", e);
    }

    ...
}

执行了aapt.link(),aapt是一个Aapt对象,Aapt是一个抽象类,link方法是在AbstractAapt中实现的,源码如下:

代码语言:javascript复制
public ListenableFuture<Void> link(@NonNull AaptPackageConfig config)
        throws AaptException {
    validatePackageConfig(config);
    return makeValidatedPackage(config);
}

首先调用validatePackageConfig函数检查参数是否正确,然后执行了makeValidatedPackage函数。

在AbstractAapt中makeValidatedPackage是抽象方法,它的实现在AbstractProcessExecutionAapt类中,源码如下:

代码语言:javascript复制
protected ListenableFuture<Void> makeValidatedPackage(@NonNull AaptPackageConfig config)
        throws AaptException {
    ProcessInfoBuilder builder = makePackageProcessBuilder(config);

    final ProcessInfo processInfo = builder.createProcess();
    ListenableFuture<ProcessResult> execResult = mProcessExecutor.submit(processInfo,
            mProcessOutputHandler);

    final SettableFuture<Void> result = SettableFuture.create();
    Futures.addCallback(execResult, new FutureCallback<ProcessResult>() {
        ...
    });

    return result;
}

创建了一个ProcessInfoBuilder对象,然后执行并得到结果,那么重点就是这个ProcessInfoBuilder对象里,来看看makePackageProcessBuilder这个函数。

同样这个函数也是抽象函数,有两个类对它进行了实现AaptV1和OutOfProcessAaptV2,很明显这与当前android sdk下的aapt版本有关。

两个方法大致类似,我们只看AaptV1的,源码如下:

代码语言:javascript复制
protected ProcessInfoBuilder makePackageProcessBuilder(@NonNull AaptPackageConfig config)
        throws AaptException {
    ProcessInfoBuilder builder = new ProcessInfoBuilder();

    ...

    // outputs
    if (config.getSourceOutputDir() != null) {
        builder.addArgs("-m");
        builder.addArgs(
                "-J", FileUtils.toExportableSystemDependentPath(config.getSourceOutputDir()));
    }

    if (config.getResourceOutputApk() != null) {
        builder.addArgs("-F", config.getResourceOutputApk().getAbsolutePath());
    }

    ...

    // Add the feature-split configuration if needed.
    if (config.getBaseFeature() != null) {
        builder.addArgs("--feature-of", config.getBaseFeature().getAbsolutePath());
        // --feature-after requires --feature-of to be set so these are only parsed if base
        // feature was set.
        for (File previousFeature : config.getPreviousFeatures()) {
            builder.addArgs("--feature-after", previousFeature.getAbsolutePath());
        }
    }

    return builder;
}

可以看到这个函数实际上是组合了一条aapt命令,添加了各种参数,其中我们关注的getSourceOutputDir则是“-J”这个参数的值。

查看aapt的说明:

代码语言:javascript复制
Modifiers:

   -a  print Android-specific data (resources, manifest) when listing

   -c  specify which configurations to include.  The default is all

       configurations.  The value of the parameter should be a comma

       separated list of configuration values.  Locales should be specified

       as either a language or language-region pair.  Some examples:

            en

            port,en

            port,land,en_US

   -d  one or more device assets to include, separated by commas

   -f  force overwrite of existing files

   -g  specify a pixel tolerance to force images to grayscale, default 0

   -j  specify a jar or zip file containing classes to include

   -k  junk path of file(s) added

   -m  make package directories under location specified by -J

   -u  update existing packages (add new, replace older, remove deleted files)

   -v  verbose output

   -x  create extending (non-application) resource IDs

   -z  require localization of resource attributes marked with

       localization="suggested"

   -A  additional directory in which to find raw asset files

   -G  A file to output proguard options into.

   -D  A file to output proguard options for the main dex into.

   -F  specify the apk file to output

   -I  add an existing package to base include set

   -J  specify where to output R.java resource constant definitions

   -M  specify full path to AndroidManifest.xml to include in zip

   -P  specify where to output public resource definitions

   -S  directory in which to find resources.  Multiple directories will be scanned

可以看到-J这个参数的含义是设置R.java文件的输出路径,这样我们就找到了源头。

在看其他代码,可以发现同样是为aapt命令添加一些运行参数,比如asrc文件的输出路径等

然后回到之前,执行这条命令,就完成了这个task。

总结

总结一下,在processResources这个过程中实际上是执行了一个aapt命令对资源文件进行编译,同时生成R文件等一些相关文件。

0 人点赞