See the previous article for the first half.
Here’s the second half:
Compile tasks for Application
We continue to view the createTasksForVariantData last line, taskManager. CreateTasksForVariantData, found createTasksForVariantData is abstract method, The taskManager concrete implementation is ApplicationTaskManager, check ApplicationTaskManager createTasksForVariantData method
/**
* 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.
...
}Copy the code
The code is so long that I only left comments for each section of the code and the comments are clear. This is all about generating variantData tasks like compileXXX, generateXXX, processXXX and mergeXXX. This list of tasks is all you need to build a full, runnable APK. The following describes the process involved in compiling dex.
Dex compilation process
// 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 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); Getandroidjartask ().create(tasks, // Create AndroidJarTask, Generate classes. The jar new AndroidJarTask. JarClassesConfigAction (variantScope)); createPostCompilationTasks(tasks, variantScope); }});Copy the code
We see directly Add a compile task comments under the code, before performing createPostCompilationTasks, first create the javac task, task name for compileXXXJavaWithJavac, The task is to compile a Java source file into a class file, implemented in the JavaCompileConfigAction class. After creating javac task, then create AndroidJarTask task, this task is to integrate the class file output JAR package, the specific implementation is in AndroidJarTask class.
Then we look at createPostCompilationTasks method
/** * 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; 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 // Obtain the dexOptions configuration from extension DefaultDexOptions dexOptions = DefaultDexOptions.copyOf(extension.getDexOptions()); . 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())); / / create 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); }); . }Copy the code
To illustrate the main process, I have omitted some of the source code for mutiDex and instantRun judgments, but here we focus on non-Mutidex and non-instantrun cases. We see that if we set minifyEnabled to True, then we’re going to create the createMinifyTransform. If use ProGuard, Tasks for Progruad and shrinkResources are created. We’re going to create a dexTask, which is a transfromTask type task, so let’s look at the transfromTask class
/** * 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; }}); }}Copy the code
We know that in a custom task, the @taskAction annotated method will be executed during the task execution phase, so in this case, the transfrom method will be executed, and then the transform transfrom method will be called at the end, The transfrom passed in to our dexTask is DexTransfrom, so let’s look at the transFROM implementation of DexTransfrom
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()); / / by AndroidBuilder into 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 { ... }Copy the code
Finally to 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 builder = new DexProcessBuilder(outDexFolder); builder.setVerbose(mVerboseExec) .setMultiDex(multidex) .setMainDexList(mainDexList) .addInputs(verifiedInputs.build()); runDexer(builder, dexOptions, processOutputHandler); }Copy the code
The DexProcessBuilder is created and then executed in the runDexer method
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); }}Copy the code
Enter the dexInProcess method
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()); // Build 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); }... }Copy the code
The buildArguments method builds arguments from the DexProcessBuilder, dexOptions, dxContext passed in, followed by the args parameters fileNames, outName, JarOutput comes from DexProcessBuilder and executes Main’s run method
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); }}}Copy the code
Here we focus on the non-multidex case, where the runMonoDex method is executed
private int runMonoDex() throws IOException { ... // Create a dexFile internally and fill it with 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 ! 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) {// The output file name is 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; }Copy the code
In the above code, the class and dex flow conversion are filled. The internal process is complicated, so we will not go further and simply summarize:
1. Internally create a DexFile (outputDex) by executing processAllFiles and populate the class file
2. Use the writeDex method to pass in the outputDex. The method implements the Outputdex. toDex method and converts the classes filled in the outputDex into byte[] of the dex
3. Finally create classes.dex from byte[] array
The last
Android Gradle Plugin source code is numerous, the above article is just a simple review of the overall process, which briefly introduces the construction of variant task analysis and add, and finally the compilation of DEX process to do a simple analysis. Personal energy is limited, here the source code analysis is only a drop in the bucket, if there is a mistake, welcome everyone pat brick, I hope this article can help to understand the principle of Android Gradle Plguin students.
If you like, please help forward so that more people in need to see oh. More Advanced Android technology, interview materials sorting and sharing, career planning, products, thinking, industry observation, chat. You can add Android architect group; 701740775. Note brief book, free of charge