美文网首页Android开发经验谈Android开发Android知识
Android Gradle Plugin 源码解析(下)

Android Gradle Plugin 源码解析(下)

作者: df556ada620a | 来源:发表于2018-12-03 21:42 被阅读13次

    上半部分请看上一篇文章。

    下面是下半部分:

    Application 的编译任务

    我们继续查看createTasksForVariantData的最后一行,taskManager.createTasksForVariantData,发现 createTasksForVariantData 是抽象方法,这里的 taskManager 具体实现是 ApplicationTaskManager,查看 ApplicationTaskManager 的 createTasksForVariantData 方法

     /**
     * Creates the tasks for a given BaseVariantData.
     */
     @Override
     public void createTasksForVariantData(
     @NonNull final TaskFactory tasks,
     @NonNull final BaseVariantData<? extends BaseVariantOutputData> variantData) {
     assert variantData instanceof ApplicationVariantData;
     final VariantScope variantScope = variantData.getScope();
     //create sourceGenTask, resGenTask, assetGenTask
     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)
     // Add a task to create the res values
     // Add a task to compile renderscript files.
     // Add a task to merge the resource folders
     // Add a task to merge the asset folders
     // Add a task to create the BuildConfig class
     // Add a task to process the Android Resources and generate source files
     // Add a task to process the java resources 
     // Add a task to process this aidl file
     // Add a task to process shader source
     // Add NDK tasks
     // Add external native build tasks
     // Add a task to merge the jni libs folders
     // Add a compile task
     // Add data binding tasks if enabled
     // create packaging task 
     // create the lint tasks.
     ...
     }
    
    

    代码实在太长了,我只留下了每段代码的注释,注释也已经非常清楚了,这个主要就是生成 variantData 的一系列像 compileXXX、generateXXX、processXXX、mergeXXX的任务,这一系列 task 就是构建一个可运行的完整APK的所需的所有task。下面介绍在编译dex中的过程,涉及的几个task。

    Dex的编译过程

     // 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 =
     // 创建 javac 任务
     createJavacTask(tasks, variantScope);
     if (jackOptions.isEnabled()) {
     AndroidTask<TransformTask> jackTask =
     createJackTask(tasks, variantScope, true /*compileJavaSource*/);
     setJavaCompilerTask(jackTask, tasks, variantScope);
     } else {
     ...
     addJavacClassesStream(variantScope);
     setJavaCompilerTask(javacTask, tasks, variantScope);
     getAndroidTasks()
     .create(
     tasks,
     // 创建 AndroidJarTask ,生成classes.jar
     new AndroidJarTask.JarClassesConfigAction(variantScope));
     createPostCompilationTasks(tasks, variantScope);
     }
     });
    
    

    我们直接查看 Add a compile task 注释下的代码,在执行 createPostCompilationTasks 之前,先创建了 javac 任务,任务名称为 compileXXXJavaWithJavac ,该任务是将 java 源文件编译成 class 文件,具体实现是在 JavaCompileConfigAction 类中。创建 javac 任务之后,接着创建了 AndroidJarTask 任务,该任务是将 class 文件整合输出 jar 包,具体实现就是在 AndroidJarTask 类中。

    紧接着我们来看一下 createPostCompilationTasks 的方法

     /**
     * Creates the post-compilation tasks for the given Variant.
     *
     * These tasks create the dex file from the .class files, plus optional intermediary steps like
     * proguard and jacoco
     *
     */
     public void createPostCompilationTasks(
     @NonNull TaskFactory tasks,
     @NonNull final VariantScope variantScope) {
     checkNotNull(variantScope.getJavacTask());
     variantScope.getInstantRunBuildContext().setInstantRunMode(
     getIncrementalMode(variantScope.getVariantConfiguration()) != IncrementalMode.NONE);
     final BaseVariantData<? extends BaseVariantOutputData> variantData = variantScope.getVariantData();
     final GradleVariantConfiguration config = variantData.getVariantConfiguration();
     TransformManager transformManager = variantScope.getTransformManager();
     ...
     boolean isMinifyEnabled = isMinifyEnabled(variantScope);
     boolean isMultiDexEnabled = config.isMultiDexEnabled();
     // Switch to native multidex if possible when using instant run.
     boolean isLegacyMultiDexMode = isLegacyMultidexMode(variantScope);
     AndroidConfig extension = variantScope.getGlobalScope().getExtension();
     // ----- External Transforms -----
     // apply all the external transforms.
     ...
     // ----- Minify next -----
     if (isMinifyEnabled) {
     boolean outputToJarFile = isMultiDexEnabled && isLegacyMultiDexMode;
     // 内部会判断是否使用 proguard 来创建 proguard 任务和 shrinkResources 任务
     createMinifyTransform(tasks, variantScope, outputToJarFile);
     }
     // ----- 10x support
     ...
     // ----- Multi-Dex support
     Optional<AndroidTask<TransformTask>> multiDexClassListTask;
     // non Library test are running as native multi-dex
     if (isMultiDexEnabled && isLegacyMultiDexMode) {
     ...
     } else {
     multiDexClassListTask = Optional.empty();
     }
     // create dex transform
     // 从 extension 中获取 dexOptions 项的配置
     DefaultDexOptions dexOptions = DefaultDexOptions.copyOf(extension.getDexOptions());
     ...
     // 创建 DexTransform
     DexTransform dexTransform = new DexTransform(
     dexOptions,
     config.getBuildType().isDebuggable(),
     isMultiDexEnabled,
     isMultiDexEnabled && isLegacyMultiDexMode ? variantScope.getMainDexListFile() : null,
     variantScope.getPreDexOutputDir(),
     variantScope.getGlobalScope().getAndroidBuilder(),
     getLogger(),
     variantScope.getInstantRunBuildContext(),
     AndroidGradleOptions.getBuildCache(variantScope.getGlobalScope().getProject()));
     // 创建 dexTask
     Optional<AndroidTask<TransformTask>> dexTask =
     transformManager.addTransform(tasks, variantScope, dexTransform);
     // need to manually make dex task depend on MultiDexTransform since there's no stream
     // consumption making this automatic
     dexTask.ifPresent(t -> {
     t.optionalDependsOn(tasks, multiDexClassListTask.orElse(null));
     variantScope.addColdSwapBuildTask(t);
     });
     ...
     }
    
    

    为了讲述主流程,我将一些 mutiDex 和 instantRun 判断的源码省略了,这里我们关注非mutiDex和非instantRun的情况。我们看到,如果我们设置了 minifyEnabled 为 true,那么这里就会去创建 createMinifyTransform ,如果use proguard,这里会创建 progruad 的任务和 shrinkResources 的任务。后面将创建 dexTask, 这个是 transfromTask 类型的任务,我们先来看下 transFromTask 类

    /**
     * A task running a transform.
     */
    @ParallelizableTask
    public class TransformTask extends StreamBasedTask implements Context {
     private Transform transform;
     ...
     public Transform getTransform() {
     return transform;
     }
     ...
     @TaskAction
     void transform(final IncrementalTaskInputs incrementalTaskInputs)
     throws IOException, TransformException, InterruptedException {
     ...
     recorder.record(
     ExecutionType.TASK_TRANSFORM,
     executionInfo,
     getProject().getPath(),
     getVariantName(),
     new Recorder.Block<Void>() {
     @Override
     public Void call() throws Exception {
     transform.transform(
     new TransformInvocationBuilder(TransformTask.this)
     .addInputs(consumedInputs.getValue())
     .addReferencedInputs(referencedInputs.getValue())
     .addSecondaryInputs(changedSecondaryInputs.getValue())
     .addOutputProvider(
     outputStream != null
     ? outputStream.asOutput()
     : null)
     .setIncrementalMode(isIncremental.getValue())
     .build());
     return null;
     }
     });
     }
    }
    
    

    我们知道,自定义任务中,在任务执行阶段会去执行被 @TaskAction 注解的方法,这里也就是执行 transfrom 方法,而 transfrom 方法中最后又会调用到 transform 的 transfrom 方法,在我们 dexTask 中传入的 transfrom 是DexTransfrom,那我们就去看下 DexTransfrom 的 transfrom 具体实现

    public class DexTransform extends Transform {
     @Override
     public void transform(@NonNull TransformInvocation transformInvocation)
     throws TransformException, IOException, InterruptedException {
     ...
     try {
     // if only one scope or no per-scope dexing, just do a single pass that
     // runs dx on everything.
     if ((jarInputs.size() + directoryInputs.size()) == 1
     || !dexOptions.getPreDexLibraries()) {
     // since there is only one dex file, we can merge all the scopes into the full
     // application one.
     File outputDir = outputProvider.getContentLocation("main",
     getOutputTypes(),
     TransformManager.SCOPE_FULL_PROJECT,
     Format.DIRECTORY);
     FileUtils.mkdirs(outputDir);
     // first delete the output folder where the final dex file(s) will be.
     FileUtils.cleanOutputDir(outputDir);
     // gather the inputs. This mode is always non incremental, so just
     // gather the top level folders/jars
     final List<File> inputFiles =
     Stream.concat(
     jarInputs.stream().map(JarInput::getFile),
     directoryInputs.stream().map(DirectoryInput::getFile))
     .collect(Collectors.toList());
     // 通过 AndroidBuilder 转化为 byte
     androidBuilder.convertByteCode(
     inputFiles,
     outputDir,
     multiDex,
     mainDexListFile,
     dexOptions,
     outputHandler);
     for (File file : Files.fileTreeTraverser().breadthFirstTraversal(outputDir)) {
     if (file.isFile()) {
     instantRunBuildContext.addChangedFile(FileType.DEX, file);
     }
     }
     } else {
     ...
     }
    
    

    最后执行到androidBuilder.convertByteCode

     /**
     * Converts the bytecode to Dalvik format
     * @param inputs the input files
     * @param outDexFolder the location of the output folder
     * @param dexOptions dex options
     * @throws IOException
     * @throws InterruptedException
     * @throws ProcessException
     */
     public void convertByteCode(
     @NonNull Collection<File> inputs,
     @NonNull File outDexFolder,
     boolean multidex,
     @Nullable File mainDexList,
     @NonNull DexOptions dexOptions,
     @NonNull ProcessOutputHandler processOutputHandler)
     throws IOException, InterruptedException, ProcessException {checkNotNull(inputs, "inputs cannot be null.");
     checkNotNull(outDexFolder, "outDexFolder cannot be null.");
     checkNotNull(dexOptions, "dexOptions cannot be null.");
     checkArgument(outDexFolder.isDirectory(), "outDexFolder must be a folder");
     checkState(mTargetInfo != null,
     "Cannot call convertByteCode() before setTargetInfo() is called.");
     ImmutableList.Builder<File> verifiedInputs = ImmutableList.builder();
     for (File input : inputs) {
     if (checkLibraryClassesJar(input)) {
     verifiedInputs.add(input);
     }
     }
     //创建 DexProcessBuilder
     DexProcessBuilder builder = new DexProcessBuilder(outDexFolder);
     builder.setVerbose(mVerboseExec)
     .setMultiDex(multidex)
     .setMainDexList(mainDexList)
     .addInputs(verifiedInputs.build());
     runDexer(builder, dexOptions, processOutputHandler);
     }
    
    

    创建了 DexProcessBuilder ,随后执行到了 runDexer 方法中

    public void runDexer(
     @NonNull final DexProcessBuilder builder,
     @NonNull final DexOptions dexOptions,
     @NonNull final ProcessOutputHandler processOutputHandler)
     throws ProcessException, IOException, InterruptedException {
     initDexExecutorService(dexOptions);
     if (dexOptions.getAdditionalParameters().contains("--no-optimize")) {
     mLogger.warning(DefaultDexOptions.OPTIMIZE_WARNING);
     }
     if (shouldDexInProcess(dexOptions)) {
     dexInProcess(builder, dexOptions, processOutputHandler);
     } else {
     dexOutOfProcess(builder, dexOptions, processOutputHandler);
     }
     }
    
    

    进入到dexInProcess方法

    private void dexInProcess(
     @NonNull final DexProcessBuilder builder,
     @NonNull final DexOptions dexOptions,
     @NonNull final ProcessOutputHandler outputHandler)
     throws IOException, ProcessException {
     final String submission = Joiner.on(',').join(builder.getInputs());
     mLogger.verbose("Dexing in-process : %1$s", submission);
     try {
     sDexExecutorService.submit(() -> {
     Stopwatch stopwatch = Stopwatch.createStarted();
     ProcessResult result = DexWrapper.run(builder, dexOptions, outputHandler);
     result.assertNormalExitValue();
     mLogger.verbose("Dexing %1$s took %2$s.", submission, stopwatch.toString());
     return null;
     }).get();
     } catch (Exception e) {
     throw new ProcessException(e);
     }
     }
    /**
     * Wrapper around the real dx classes.
     */
    public class DexWrapper {
     /**
     * Runs the dex command.
     *
     * @return the integer return code of com.android.dx.command.dexer.Main.run()
     */
     public static ProcessResult run(
     @NonNull DexProcessBuilder processBuilder,
     @NonNull DexOptions dexOptions,
     @NonNull ProcessOutputHandler outputHandler) throws IOException, ProcessException {
     ProcessOutput output = outputHandler.createOutput();
     int res;
     try {
     DxContext dxContext = new DxContext(output.getStandardOutput(), output.getErrorOutput());
     // 构建 Main.Arguments 参数
     Main.Arguments args = buildArguments(processBuilder, dexOptions, dxContext);
     res = new Main(dxContext).run(args);
     } finally {
     output.close();
     }
     outputHandler.handleOutput(output);
     return new DexProcessResult(res);
     }
     ...
    }
    
    

    buildArguments方法通过传入的DexProcessBuilder、dexOptions、dxContext构建 arguments,后面使用的args的参数fileNames,outName,jarOutput都是从DexProcessBuilder来的,然后执行Main的run方法

    package com.android.dx.command.dexer;
    ...
    /**
     * Main class for the class file translator.
     */
    public class Main {
     /**
     * Run and return a result code.
     * @param arguments the data + parameters for the conversion
     * @return 0 if success > 0 otherwise.
     */
     public int run(Arguments arguments) throws IOException {
     // Reset the error count to start fresh.
     errors.set(0);
     // empty the list, so that tools that load dx and keep it around
     // for multiple runs don't reuse older buffers.
     libraryDexBuffers.clear();
     args = arguments;
     args.makeOptionsObjects(context);
     OutputStream humanOutRaw = null;
     if (args.humanOutName != null) {
     humanOutRaw = openOutput(args.humanOutName);
     humanOutWriter = new OutputStreamWriter(humanOutRaw);
     }
     try {
     if (args.multiDex) {
     return runMultiDex();
     } else {
     return runMonoDex();
     }
     } finally {
     closeOutput(humanOutRaw);
     }
     }
    }
    
    

    这里我们关注非multiDex的情况,即执行了runMonoDex的方法

    private int runMonoDex() throws IOException {
     ...
     // 内部会创建dexFile,并填充class
     if (!processAllFiles()) {
     return 1;
     }
     if (args.incremental && !anyFilesProcessed) {
     return 0; // this was a no-op incremental build
     }
     // this array is null if no classes were defined
     byte[] outArray = null;
     if (!outputDex.isEmpty() || (args.humanOutName != null)) {
     // 内部通过Dex类toDex 方法将 class 文件转化dex byte[]
     outArray = writeDex(outputDex);
     if (outArray == null) {
     return 2;
     }
     }
     if (args.incremental) {
     outArray = mergeIncremental(outArray, incrementalOutFile);
     }
     outArray = mergeLibraryDexBuffers(outArray);
     if (args.jarOutput) {
     // Effectively free up the (often massive) DexFile memory.
     outputDex = null;
     if (outArray != null) {
     // 输出的文件名为 classes.dex
     outputResources.put(DexFormat.DEX_IN_JAR_NAME, outArray);
     }
     if (!createJar(args.outName)) {
     return 3;
     }
     } else if (outArray != null && args.outName != null) {
     OutputStream out = openOutput(args.outName);
     out.write(outArray);
     closeOutput(out);
     }
     return 0;
     }
    
    

    上面的代码中,填充class以及dex流转换,内部流程较为复杂,就不再继续深入,简单做下总结:

    1.通过执行 processAllFiles ,内部创建 DexFile 也就是outputDex,并且填充 class 文件

    2.通过 writeDex 方法,将 outputDex 传入,方法内部执行的是 outputDex.toDex 方法,将 outputDex 内部填充的 class 转化为 dex 的 byte[] 返回

    3.最后将 byte[] 数组创建 classes.dex 输出

    最后

    Android Gradle Plugin源码繁多,以上文章只是对整体流程的简单梳理,其中简要介绍了构建变体任务的解析和添加 ,最后对编译dex流程做了简单分析。个人精力有限,这里的源码解析也只是九牛一毛,如有纰漏,欢迎大家拍砖,希望这篇文章能帮助到想了解 Android Gradle Plguin 原理的同学。

    喜欢的话请帮忙转发让更多需要的人看到哦。更多Android进阶技术,面试资料整理分享,职业生涯规划,产品,思维,行业观察,谈天说地。可以加Android架构师群;701740775。备注简书,免费获取

    相关文章

      网友评论

        本文标题:Android Gradle Plugin 源码解析(下)

        本文链接:https://www.haomeiwen.com/subject/rggucqtx.html