上半部分请看上一篇文章。
下面是下半部分:
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。备注简书,免费获取
网友评论