天天看點

Android Gradle Plugin 源碼解析(下)最後

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

下面是下半部分:

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。備注csdn,免費擷取

繼續閱讀