簡介
上篇文章中,我們介紹了AppPlugin的初始化,在其父類BasePlugin的apply()
中,會調用ApplicationTaskManager
的createTasksForVariantScope()
,方法內初始化了很多的Task,這裏,我們來詳細看一下。
createAidlTask
最終實現是在AidlCompile.java
,用來生成Aidl java文件
public AndroidTask<AidlCompile> createAidlTask(@NonNull TaskFactory tasks, @NonNull VariantScope scope) {
AndroidTask<AidlCompile> aidlCompileTask = androidTasks
.create(tasks, new AidlCompile.ConfigAction(scope));
scope.setAidlCompileTask(aidlCompileTask);
scope.getSourceGenTask().dependsOn(tasks, aidlCompileTask);
aidlCompileTask.dependsOn(tasks, scope.getPreBuildTask());
return aidlCompileTask;
}
AndroidTask是個包裝類,真正實現類是AidlCompile ,我們來看下AidlCompile,它繼承自IncrementalTask。
Task我們需要找到@TaskAction註解,執行的入口
@TaskAction
void taskAction(IncrementalTaskInputs inputs) throws Exception {
if (!isIncremental() || !inputs.isIncremental()) {
getProject().getLogger().info("Unable do incremental execution: full task run");
//全量編譯
doFullTaskAction();
return;
}
//增量編譯
doIncrementalTaskAction(getChangedInputs(inputs));
}
可以看到,全量編譯的情況下,會調用doFullTaskAction()
而AidlCompile是不支持增量編譯的。
@Override
@Internal
protected boolean isIncremental() {
// TODO fix once dep file parsing is resolved.
return false;
}
我們接着來看doFullTaskAction()
@Override
protected void doFullTaskAction() throws IOException {
// this is full run, clean the previous output
//獲取IADL相關的文件夾
File destinationDir = getSourceOutputDir();
File parcelableDir = getPackagedDir();
FileUtils.cleanOutputDir(destinationDir);
if (parcelableDir != null) {
FileUtils.cleanOutputDir(parcelableDir);
}
//創建DepFileProcessor對象
DepFileProcessor processor = new DepFileProcessor();
try {
//編譯所有AIDL文件
compileAllFiles(processor);
} catch (Exception e) {
throw new RuntimeException(e);
}
List<DependencyData> dataList = processor.getDependencyDataList();
DependencyDataStore store = new DependencyDataStore();
store.addData(dataList);
try {
//保存文件
store.saveTo(new File(getIncrementalFolder(), DEPENDENCY_STORE));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
我們再來看下compileAllFiles()
,最終會調用AndroidBuilder的compileAllAidlFiles()
public void compileAllAidlFiles(
@NonNull Collection<File> sourceFolders,
@NonNull File sourceOutputDir,
@Nullable File packagedOutputDir,
@Nullable Collection<String> packageWhiteList,
@NonNull Collection<File> importFolders,
@Nullable DependencyFileProcessor dependencyFileProcessor,
@NonNull ProcessOutputHandler processOutputHandler)
throws IOException, InterruptedException, ProcessException {
//...
AidlProcessor processor = new AidlProcessor(
aidl,
target.getPath(IAndroidTarget.ANDROID_AIDL),
fullImportList,
sourceOutputDir,
packagedOutputDir,
packageWhiteList,
dependencyFileProcessor != null ?
dependencyFileProcessor : DependencyFileProcessor.NO_OP,
mProcessExecutor,
processOutputHandler);
//創建DirectoryWalker,並傳入processor,調用walk方法
for (File dir : sourceFolders) {
DirectoryWalker.builder()
.root(dir.toPath())
.extensions("aidl")
.action(processor)
.build()
.walk();
}
}
這裏會創建DirectoryWalker,傳入processor,調用walk方法。
這裏來看walk方法
public DirectoryWalker walk() throws IOException {
//...
Set<FileVisitOption> options =
Sets.newEnumSet(Arrays.asList(FileVisitOption.FOLLOW_LINKS), FileVisitOption.class);
Files.walkFileTree(
root,
options,
Integer.MAX_VALUE,
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs)
throws IOException {
if (!shouldSkipPath(path)) {
//action就是processor,即調用processor的call方法
action.call(root, path);
}
return FileVisitResult.CONTINUE;
}
});
return this;
}
可以看到,walk方法裏會調用action.call方法,action就是processor
來看processor的call方法
@Override
public void call(@NonNull Path startDir, @NonNull Path path) throws IOException {
ProcessInfoBuilder builder = new ProcessInfoBuilder();
builder.setExecutable(mAidlExecutable);
//-p -o 這些是AIDL工具相關指令
//-p:進行預處理
//-o:設定執行完畢後輸出的文件夾
//-I:指定工具的輸入
//-d:依賴哪些文件
builder.addArgs("-p" + mFrameworkLocation);
builder.addArgs("-o" + mSourceOutputDir.getAbsolutePath());
// add all the library aidl folders to access parcelables that are in libraries
for (File f : mImportFolders) {
builder.addArgs("-I" + f.getAbsolutePath());
}
// create a temp file for the dependency
File depFile = File.createTempFile("aidl", ".d");
builder.addArgs("-d" + depFile.getAbsolutePath());
builder.addArgs(path.toAbsolutePath().toString());
//執行指令
ProcessResult result = mProcessExecutor.execute(
builder.createProcess(), mProcessOutputHandler);
//...
}
可以看到,這裏會去執行aidl的指令,將.aidl
文件生成.java
文件
1.通過Gradle的compileDebugAidl命令,可以單獨調用這個Task,從而生成aidl對應的java文件
2.在\SDK\build-tools\29.0.3
中,我們可以找到aidl.exe
文件
3.通過gradlew aidl -p
我們可以查看aidl支持的指令
createBuildConfigTask
最終實現是在GenerateBuildConfig.java
,用於生成BuildConfig文件
首先,我們先找到@TaskAction註解
@TaskAction
void generate() throws IOException {
// must clear the folder in case the packagename changed, otherwise,
// there'll be two classes.
File destinationDir = getSourceOutputDir();
FileUtils.cleanOutputDir(destinationDir);
BuildConfigGenerator generator = new BuildConfigGenerator(
getSourceOutputDir(),
getBuildConfigPackageName());
//添加屬性
generator
.addField(
"boolean",
"DEBUG",
isDebuggable() ? "Boolean.parseBoolean(\"true\")" : "false")
.addField("String", "APPLICATION_ID", '"' + appPackageName.get() + '"')
.addField("String", "BUILD_TYPE", '"' + getBuildTypeName() + '"')
.addField("String", "FLAVOR", '"' + getFlavorName() + '"')
.addField("int", "VERSION_CODE", Integer.toString(getVersionCode()))
.addField(
"String", "VERSION_NAME", '"' + Strings.nullToEmpty(getVersionName()) + '"')
.addItems(getItems());
List<String> flavors = getFlavorNamesWithDimensionNames();
int count = flavors.size();
if (count > 1) {
for (int i = 0; i < count; i += 2) {
generator.addField(
"String", "FLAVOR_" + flavors.get(i + 1), '"' + flavors.get(i) + '"');
}
}
//調用generate方法生成java文件
generator.generate();
}
來看下generate方法
/**
* Generates the BuildConfig class.
*/
public void generate() throws IOException {
File pkgFolder = getFolderPath();
if (!pkgFolder.isDirectory()) {
if (!pkgFolder.mkdirs()) {
throw new RuntimeException("Failed to create " + pkgFolder.getAbsolutePath());
}
}
File buildConfigJava = new File(pkgFolder, BUILD_CONFIG_NAME);
Closer closer = Closer.create();
try {
FileOutputStream fos = closer.register(new FileOutputStream(buildConfigJava));
OutputStreamWriter out = closer.register(new OutputStreamWriter(fos, Charsets.UTF_8));
JavaWriter writer = closer.register(new JavaWriter(out));
writer.emitJavadoc("Automatically generated file. DO NOT MODIFY")
.emitPackage(mBuildConfigPackageName)
.beginType("BuildConfig", "class", PUBLIC_FINAL);
for (ClassField field : mFields) {
emitClassField(writer, field);
}
for (Object item : mItems) {
if (item instanceof ClassField) {
emitClassField(writer, (ClassField) item);
} else if (item instanceof String) {
writer.emitSingleLineComment((String) item);
}
}
writer.endType();
} catch (Throwable e) {
throw closer.rethrow(e);
} finally {
closer.close();
}
}
可以看到,最終調用了JavaWriter來生成Java文件。
createMergeResourcesTask
最終實現是在MergeResources.java
,用來合併資源,同樣,我們先來找到@TaskAction,這在它的父類中。
@TaskAction
void taskAction(IncrementalTaskInputs inputs) throws Exception {
if (!isIncremental() || !inputs.isIncremental()) {
getProject().getLogger().info("Unable do incremental execution: full task run");
//調用doFullTaskAction();
doFullTaskAction();
return;
}
doIncrementalTaskAction(getChangedInputs(inputs));
}
可以看到,這裏調用了doFullTaskAction();
@Override
protected void doFullTaskAction() throws IOException, ExecutionException, JAXBException {
ResourcePreprocessor preprocessor = getPreprocessor();
// this is full run, clean the previous output
File destinationDir = getOutputDir();
FileUtils.cleanOutputDir(destinationDir);
//獲取資源集合
List<ResourceSet> resourceSets = getConfiguredResourceSets(preprocessor);
// create a new merger and populate it with the sets.
//創建一個Merger對象
ResourceMerger merger = new ResourceMerger(minSdk);
MergingLog mergingLog =
getBlameLogFolder() != null ? new MergingLog(getBlameLogFolder()) : null;
try (QueueableResourceCompiler resourceCompiler =
processResources
? makeAapt(
aaptGeneration,
getBuilder(),
fileCache,
crunchPng,
variantScope,
getAaptTempDir(),
mergingLog)
: QueueableResourceCompiler.NONE) {
//遍歷resourceSets,添加到Merger中
for (ResourceSet resourceSet : resourceSets) {
resourceSet.loadFromFiles(getILogger());
merger.addDataSet(resourceSet);
}
//創建MergerResourceWriter對象
MergedResourceWriter writer =
new MergedResourceWriter(
workerExecutorFacade,
destinationDir,
getPublicFile(),
mergingLog,
preprocessor,
resourceCompiler,
getIncrementalFolder(),
dataBindingLayoutProcessor,
mergedNotCompiledResourcesOutputDirectory,
pseudoLocalesEnabled,
getCrunchPng());
//調用mergeData方法,最終會調用MergedResourceWriter.write寫入文件
merger.mergeData(writer, false /*doCleanUp*/);
if (dataBindingLayoutProcessor != null) {
dataBindingLayoutProcessor.end();
}
// No exception? Write the known state.
merger.writeBlobTo(getIncrementalFolder(), writer, false);
} catch (MergingException e) {
System.out.println(e.getMessage());
merger.cleanBlob(getIncrementalFolder());
throw new ResourceException(e.getMessage(), e);
} finally {
cleanup();
}
}
我們再來看下mergeData方法
public void mergeData(@NonNull MergeConsumer<I> consumer, boolean doCleanUp)
throws MergingException {
consumer.start(mFactory);
try {
// get all the items keys.
Set<String> dataItemKeys = Sets.newHashSet();
//遍歷容器
for (S dataSet : mDataSets) {
// quick check on duplicates in the resource set.
//對重名內容進行檢查
dataSet.checkItems();
ListMultimap<String, I> map = dataSet.getDataMap();
//將key添加到dataItemKeys
dataItemKeys.addAll(map.keySet());
}
// loop on all the data items.
//遍歷dataItemKeys,即所有的資源
for (String dataItemKey : dataItemKeys) {
if (requiresMerge(dataItemKey)) {
// get all the available items, from the lower priority, to the higher
// priority
List<I> items = Lists.newArrayListWithExpectedSize(mDataSets.size());
for (S dataSet : mDataSets) {
// look for the resource key in the set
ListMultimap<String, I> itemMap = dataSet.getDataMap();
if (itemMap.containsKey(dataItemKey)) {
List<I> setItems = itemMap.get(dataItemKey);
items.addAll(setItems);
}
}
mergeItems(dataItemKey, items, consumer);
continue;
}
// for each items, look in the data sets, starting from the end of the list.
I previouslyWritten = null;
I toWrite = null;
/*
* We are looking for what to write/delete: the last non deleted item, and the
* previously written one.
*/
boolean foundIgnoredItem = false;
setLoop: for (int i = mDataSets.size() - 1 ; i >= 0 ; i--) {
S dataSet = mDataSets.get(i);
// look for the resource key in the set
ListMultimap<String, I> itemMap = dataSet.getDataMap();
if (!itemMap.containsKey(dataItemKey)) {
continue;
}
List<I> items = itemMap.get(dataItemKey);
if (items.isEmpty()) {
continue;
}
// The list can contain at max 2 items. One touched and one deleted.
// More than one deleted means there was more than one which isn't possible
// More than one touched means there is more than one and this isn't possible.
for (int ii = items.size() - 1 ; ii >= 0 ; ii--) {
I item = items.get(ii);
if (consumer.ignoreItemInMerge(item)) {
foundIgnoredItem = true;
continue;
}
if (item.isWritten()) {
assert previouslyWritten == null;
previouslyWritten = item;
}
if (toWrite == null && !item.isRemoved()) {
toWrite = item;
}
if (toWrite != null && previouslyWritten != null) {
break setLoop;
}
}
}
// done searching, we should at least have something, unless we only
// found items that are not meant to be written (attr inside declare styleable)
assert foundIgnoredItem || previouslyWritten != null || toWrite != null;
if (toWrite != null && !filterAccept(toWrite)) {
toWrite = null;
}
//noinspection ConstantConditions
if (previouslyWritten == null && toWrite == null) {
continue;
}
// now need to handle, the type of each (single res file, multi res file), whether
// they are the same object or not, whether the previously written object was
// deleted.
if (toWrite == null) {
// nothing to write? delete only then.
assert previouslyWritten.isRemoved();
consumer.removeItem(previouslyWritten, null /*replacedBy*/);
} else if (previouslyWritten == null || previouslyWritten == toWrite) {
// easy one: new or updated res
//添加到consumer中,consumer就是我們傳入的對象MergedResourceWriter
consumer.addItem(toWrite);
} else {
// replacement of a resource by another.
// force write the new value
toWrite.setTouched();
consumer.addItem(toWrite);
// and remove the old one
consumer.removeItem(previouslyWritten, toWrite);
}
}
} finally {
//調用MergedResourceWriter的end方法
consumer.end();
}
if (doCleanUp) {
// reset all states. We can't just reset the toWrite and previouslyWritten objects
// since overlayed items might have been touched as well.
// Should also clean (remove) objects that are removed.
postMergeCleanUp();
}
}
我們再來看下consumer.end()
在consumer.end()
中,會先調用父類的postWriteAction();
@Override
protected void postWriteAction() throws ConsumerException {
/*
* Create a temporary directory where merged XML files are placed before being processed
* by the resource compiler.
*/
File tmpDir = new File(mTemporaryDirectory, "merged.dir");
try {
FileUtils.cleanOutputDir(tmpDir);
} catch (IOException e) {
throw new ConsumerException(e);
}
//遍歷mValuesResMap這個集合
// now write the values files.
for (String key : mValuesResMap.keySet()) {
// the key is the qualifier.
// check if we have to write the file due to deleted values.
// also remove it from that list anyway (to detect empty qualifiers later).
boolean mustWriteFile = mQualifierWithDeletedValues.remove(key);
// get the list of items to write
List<ResourceItem> items = mValuesResMap.get(key);
// now check if we really have to write it
if (!mustWriteFile) {
for (ResourceItem item : items) {
if (item.isTouched()) {
mustWriteFile = true;
break;
}
}
}
if (mustWriteFile) {
/*
* We will write the file to a temporary directory. If the folder name is "values",
* we will write the XML file to "<tmpdir>/values/values.xml". If the folder name
* is "values-XXX" we will write the XML file to
* "<tmpdir/values-XXX/values-XXX.xml".
*
* Then, we will issue a compile operation or copy the file if aapt does not require
* compilation of this file.
*/
try {
String folderName = key.isEmpty() ?
ResourceFolderType.VALUES.getName() :
ResourceFolderType.VALUES.getName() + RES_QUALIFIER_SEP + key;
File valuesFolder = new File(tmpDir, folderName);
// Name of the file is the same as the folder as AAPT gets confused with name
// collision when not normalizing folders name.
File outFile = new File(valuesFolder, folderName + DOT_XML);
FileUtils.mkdirs(valuesFolder);
DocumentBuilder builder = mFactory.newDocumentBuilder();
Document document = builder.newDocument();
final String publicTag = ResourceType.PUBLIC.getName();
List<Node> publicNodes = null;
Node rootNode = document.createElement(TAG_RESOURCES);
document.appendChild(rootNode);
Collections.sort(items);
for (ResourceItem item : items) {
Node nodeValue = item.getValue();
if (nodeValue != null && publicTag.equals(nodeValue.getNodeName())) {
if (publicNodes == null) {
publicNodes = Lists.newArrayList();
}
publicNodes.add(nodeValue);
continue;
}
// add a carriage return so that the nodes are not all on the same line.
// also add an indent of 4 spaces.
rootNode.appendChild(document.createTextNode("\n "));
ResourceFile source = item.getSource();
Node adoptedNode = NodeUtils.adoptNode(document, nodeValue);
if (source != null) {
XmlUtils.attachSourceFile(
adoptedNode, new SourceFile(source.getFile()));
}
rootNode.appendChild(adoptedNode);
}
// finish with a carriage return
rootNode.appendChild(document.createTextNode("\n"));
final String content;
Map<SourcePosition, SourceFilePosition> blame =
mMergingLog == null ? null : Maps.newLinkedHashMap();
if (blame != null) {
content = XmlUtils.toXml(document, blame);
} else {
content = XmlUtils.toXml(document);
}
Files.write(content, outFile, Charsets.UTF_8);
//創建CompileResourceRequest
CompileResourceRequest request =
new CompileResourceRequest(
outFile,
getRootFolder(),
folderName,
pseudoLocalesEnabled,
crunchPng);
// If we are going to shrink resources, the resource shrinker needs to have the
// final merged uncompiled file.
if (notCompiledOutputDirectory != null) {
File typeDir = new File(notCompiledOutputDirectory, folderName);
FileUtils.mkdirs(typeDir);
FileUtils.copyFileToDirectory(outFile, typeDir);
}
if (blame != null) {
mMergingLog.logSource(
new SourceFile(mResourceCompiler.compileOutputFor(request)), blame);
mMergingLog.logSource(new SourceFile(outFile), blame);
}
//調用compile,傳入request對象
mResourceCompiler.compile(request).get();
if (publicNodes != null && mPublicFile != null) {
// Generate public.txt:
int size = publicNodes.size();
StringBuilder sb = new StringBuilder(size * 80);
for (Node node : publicNodes) {
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
String name = element.getAttribute(ATTR_NAME);
String type = element.getAttribute(ATTR_TYPE);
if (!name.isEmpty() && !type.isEmpty()) {
String flattenedName = name.replace('.', '_');
sb.append(type).append(' ').append(flattenedName).append('\n');
}
}
}
File parentFile = mPublicFile.getParentFile();
if (!parentFile.exists()) {
boolean mkdirs = parentFile.mkdirs();
if (!mkdirs) {
throw new IOException("Could not create " + parentFile);
}
}
String text = sb.toString();
Files.write(text, mPublicFile, Charsets.UTF_8);
}
} catch (Exception e) {
throw new ConsumerException(e);
}
}
}
// now remove empty values files.
for (String key : mQualifierWithDeletedValues) {
String folderName = key != null && !key.isEmpty() ?
ResourceFolderType.VALUES.getName() + RES_QUALIFIER_SEP + key :
ResourceFolderType.VALUES.getName();
if (notCompiledOutputDirectory != null) {
removeOutFile(
FileUtils.join(
notCompiledOutputDirectory, folderName, folderName + DOT_XML));
}
// Remove the intermediate (compiled) values file.
removeOutFile(
mResourceCompiler.compileOutputFor(
new CompileResourceRequest(
FileUtils.join(
getRootFolder(), folderName, folderName + DOT_XML),
getRootFolder(),
folderName)));
}
}
@Override
public void end() throws ConsumerException {
// Make sure all PNGs are generated first.
//調用父類的end方法,內部會調用 postWriteAction();
super.end();
// now perform all the databinding, PNG crunching (AAPT1) and resources compilation (AAPT2).
Map<Future, String> outstandingRequests = new HashMap<>();
try {
File tmpDir = new File(mTemporaryDirectory, "stripped.dir");
try {
FileUtils.cleanOutputDir(tmpDir);
} catch (IOException e) {
throw new ConsumerException(e);
}
while (!mCompileResourceRequests.isEmpty()) {
CompileResourceRequest request = mCompileResourceRequests.poll();
try {
Future<File> result;
File fileToCompile = request.getInput();
if (mMergingLog != null) {
mMergingLog.logCopy(
request.getInput(), mResourceCompiler.compileOutputFor(request));
}
if (dataBindingExpressionRemover != null
&& request.getFolderName().startsWith("layout")
&& request.getInput().getName().endsWith(".xml")) {
// Try to strip the layout. If stripping modified the file (there was data
// binding in the layout), compile the stripped layout into merged resources
// folder. Otherwise, compile into merged resources folder normally.
File strippedLayoutFolder = new File(tmpDir, request.getFolderName());
File strippedLayout =
new File(strippedLayoutFolder, request.getInput().getName());
boolean removedDataBinding =
dataBindingExpressionRemover.processSingleFile(
request.getInput(), strippedLayout);
if (removedDataBinding) {
// Remember in case AAPT compile or link fails.
if (mMergingLog != null) {
mMergingLog.logCopy(request.getInput(), strippedLayout);
}
fileToCompile = strippedLayout;
}
}
// Currently the resource shrinker and unit tests that use resources need
// the final merged, but uncompiled file.
if (notCompiledOutputDirectory != null) {
File typeDir =
new File(notCompiledOutputDirectory, request.getFolderName());
FileUtils.mkdirs(typeDir);
FileUtils.copyFileToDirectory(fileToCompile, typeDir);
}
//調用compile,傳入request
result =
mResourceCompiler.compile(
//創建Request
new CompileResourceRequest(
fileToCompile,
request.getOutput(),
request.getFolderName(),
pseudoLocalesEnabled,
crunchPng));
outstandingRequests.put(result, request.getInput().getAbsolutePath());
// adding to the mCompiling seems unnecessary at this point, the end() call will
// take care of waiting for all requests to be processed.
mCompiling.add(result);
} catch (ResourceCompilationException | IOException e) {
throw MergingException.wrapException(e).withFile(request.getInput()).build();
}
}
} catch (Exception e) {
throw new ConsumerException(e);
}
// now wait for all PNGs to be actually crunched.This seems to only be necessary to
// propagate exception at this point. There should be a simpler way to do this.
try {
Future<File> first;
while ((first = mCompiling.pollFirst()) != null) {
File outFile = first.get();
mCompiledFileMap.put(outstandingRequests.get(first), outFile.getAbsolutePath());
}
} catch (InterruptedException|ExecutionException e) {
throw new ConsumerException(e);
}
if (mMergingLog != null) {
try {
mMergingLog.write();
} catch (IOException e) {
throw new ConsumerException(e);
}
mMergingLog = null;
}
mValuesResMap = null;
mQualifierWithDeletedValues = null;
mFactory = null;
try (FileWriter fw = new FileWriter(mCompiledFileMapFile)) {
mCompiledFileMap.store(fw, null);
} catch (IOException e) {
throw new ConsumerException(e);
}
}
我們可以看到,這裏都調用了mResourceCompiler.compile
方法,那麼mResourceCompiler
@NonNull
private static Aapt makeAapt(
@NonNull AaptGeneration aaptGeneration,
@NonNull AndroidBuilder builder,
@Nullable FileCache fileCache,
boolean crunchPng,
@NonNull VariantScope scope,
@NonNull File intermediateDir,
@Nullable MergingLog blameLog)
throws IOException {
return AaptGradleFactory.make(
aaptGeneration,
builder,
blameLog != null
? new ParsingProcessOutputHandler(
new ToolOutputParser(
aaptGeneration == AaptGeneration.AAPT_V1
? new AaptOutputParser()
: new Aapt2OutputParser(),
builder.getLogger()),
new MergingLogRewriter(blameLog::find, builder.getErrorReporter()))
: new LoggedProcessOutputHandler(
new AaptGradleFactory.FilteringLogger(builder.getLogger())),
fileCache,
crunchPng,
intermediateDir,
scope.getGlobalScope().getExtension().getAaptOptions().getCruncherProcesses());
}
AaptGradleFactory.make(),生成相應的Aapt,現在我們用的是AAPT_V2_DAEMON_MODE
@NonNull
public static Aapt make(
@NonNull AaptGeneration aaptGeneration,
@NonNull AndroidBuilder builder,
@Nullable ProcessOutputHandler outputHandler,
@Nullable FileCache fileCache,
boolean crunchPng,
@NonNull File intermediateDir,
int cruncherProcesses)
throws IOException {
TargetInfo target = builder.getTargetInfo();
Preconditions.checkNotNull(target, "target == null");
BuildToolInfo buildTools = target.getBuildTools();
ProcessOutputHandler teeOutputHandler =
new TeeProcessOutputHandler(
outputHandler,
new LoggedProcessOutputHandler(new FilteringLogger(builder.getLogger())));
switch (aaptGeneration) {
case AAPT_V1:
return new AaptV1(
builder.getProcessExecutor(),
teeOutputHandler,
buildTools,
new FilteringLogger(builder.getLogger()),
crunchPng ? AaptV1.PngProcessMode.ALL : AaptV1.PngProcessMode.NO_CRUNCH,
cruncherProcesses);
case AAPT_V2:
return new OutOfProcessAaptV2(
builder.getProcessExecutor(),
teeOutputHandler,
buildTools,
intermediateDir,
new FilteringLogger(builder.getLogger()));
case AAPT_V2_JNI:
return new AaptV2Jni(
intermediateDir,
WaitableExecutor.useGlobalSharedThreadPool(),
teeOutputHandler,
fileCache);
case AAPT_V2_DAEMON_MODE:
return new QueueableAapt2(
teeOutputHandler,
buildTools,
intermediateDir,
new FilteringLogger(builder.getLogger()),
0 /* use default */);
default:
throw new IllegalArgumentException("unknown aapt generation" + aaptGeneration);
}
}
所以mResourceCompiler
就是QueueableAapt2,我們來看QueueableAapt2的compile方法
@NonNull
@Override
public Future<File> compile(@NonNull CompileResourceRequest request) throws Exception {
// TODO(imorlowska): move verification to CompileResourceRequest.
Preconditions.checkArgument(
request.getInput().isFile(),
"Input file needs to be a normal file.\nInput file: %s",
request.getInput().getAbsolutePath());
Preconditions.checkArgument(
request.getOutput().isDirectory(),
"Output for resource compilation needs to be a directory.\nOutput: %s",
request.getOutput().getAbsolutePath());
SettableFuture<File> actualResult = SettableFuture.create();
ListenableFuture<File> futureResult;
try {
//調用Aapt2QueuedResourceProcessor的compile方法
futureResult = aapt.compile(requestKey, request, processOutputHandler);
} catch (ResourceCompilationException e) {
throw new Aapt2Exception(
String.format("Failed to compile file %s", request.getInput()), e);
}
futureResult.addListener(
() -> {
try {
actualResult.set(futureResult.get());
} catch (InterruptedException e) {
Thread.interrupted();
actualResult.setException(e);
} catch (ExecutionException e) {
actualResult.setException(e);
}
},
executor);
return actualResult;
}
調用Aapt2QueuedResourceProcessor的compile方法
@Override
public ListenableFuture<File> compile(
int key,
@NonNull CompileResourceRequest request,
@Nullable ProcessOutputHandler processOutputHandler)
throws ResourceCompilationException {
SettableFuture<File> result = SettableFuture.create();
try {
final Job<AaptProcess> aaptProcessJob =
new AaptQueueThreadContext.QueuedJob(
key,
"Compiling " + request.getInput().getName(),
new Task<AaptProcess>() {
@Override
public void run(
@NonNull Job<AaptProcess> job,
@NonNull JobContext<AaptProcess> context)
throws IOException {
AaptProcess aapt = context.getPayload();
if (aapt == null) {
logger.error(
null /* throwable */,
"Thread(%1$s) has a null payload",
Thread.currentThread().getName());
return;
}
//調用AaptProcess 的compile方法
aapt.compile(request, job, processOutputHandler);
}
@Override
public void finished() {
result.set(
new File(
request.getOutput(),
Aapt2RenamingConventions.compilationRename(
request.getInput())));
}
@Override
public void error(Throwable e) {
result.setException(e);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("from", request.getInput().getAbsolutePath())
.add("to", request.getOutput().getAbsolutePath())
.toString();
}
},
result);
synchronized (outstandingJobs) {
outstandingJobs.computeIfAbsent(key, k -> new ConcurrentLinkedQueue<>());
}
processingRequests.push(aaptProcessJob);
} catch (InterruptedException e) {
// Restore the interrupted status
Thread.currentThread().interrupt();
throw new ResourceCompilationException(e);
}
return result;
}
調用AaptProcess 的compile方法
public void compile(
@NonNull CompileResourceRequest request,
@NonNull Job<AaptProcess> job,
@Nullable ProcessOutputHandler processOutputHandler)
throws IOException {
if (!mReady.get()) {
throw new RuntimeException(
String.format(
"AAPT2 process not ready to receive commands. Please make sure the "
+ "build tools (located at %s) are not corrupted. Check the "
+ "logs for details.",
mAaptLocation));
}
NotifierProcessOutput notifier =
new NotifierProcessOutput(job, mProcessOutputFacade, mLogger, processOutputHandler);
mProcessOutputFacade.setNotifier(notifier);
mWriter.write('c');
mWriter.write('\n');
//生成命令行,比如拼接 -o 輸出目錄 等
mWriter.write(joiner.join(AaptV2CommandBuilder.makeCompile(request)));
// Finish the request
mWriter.write('\n');
mWriter.write('\n');
mWriter.flush();
processCount++;
mLogger.verbose(
"AAPT2 processed(%1$d) %2$s job:%3$s",
hashCode(), request.getInput().getName(), job.toString());
}
我們可以通過
mergeFreeDebugResources
來單獨執行這個Task
資源文件會生成R.java文件和.flat文件
appt2
mergeFreeDebugResources實際是用的appt2來執行資源的合併。
可通過aapt2來編譯生成.flat文件,R.java文件和生成apk文件
//生成.flat文件
aapt2 compile -o app\build app\src\main\res\values\strings.xml
//將res目錄下的資源生成.flat文件並打包成 build.zip
aapt2 compile -o app\build\build.zip --dir app\src\main\res
//將build.zip manifest 鏈接 生成 build.apk
aapt2 link -o build.apk -I C:\Developer\Android\SDK\build-tools\29.0.3\android.jar build/values_strings.arsc.flat --manifest src/main/AndroidManifest.xml -v
具體詳見
aapt2 工具介紹
AAPT2 | Android 開發者 | Android Developers
createApkProcessResTask
最終實現在ProcessAndroidResources.java
,用來aapt2 link,也是繼承自IncrementalTask
,直接看doFullTaskAction
@Override
protected void doFullTaskAction() throws IOException, ExecutionException {
WaitableExecutor executor = WaitableExecutor.useGlobalSharedThreadPool();
List<ApkData> splitsToGenerate =
getApksToGenerate(outputScope, supportedAbis, buildTargetAbi, buildTargetDensity);
for (ApkData apkData : outputScope.getApkDatas()) {
if (!splitsToGenerate.contains(apkData)) {
getLogger()
.log(
LogLevel.DEBUG,
"With ABI " + buildTargetAbi + ", disabled " + apkData);
apkData.disable();
}
}
Collection<BuildOutput> manifestsOutputs = BuildOutputs.load(taskInputType, manifestFiles);
final Set<File> packageIdFileSet =
packageIdsFiles != null
? packageIdsFiles.getArtifactFiles().getAsFileTree().getFiles()
: null;
final Set<File> featureResourcePackages = this.featureResourcePackages.getFiles();
SplitList splitList = isLibrary ? SplitList.EMPTY : SplitList.load(splitListInput);
Set<File> libraryInfoList = symbolListsWithPackageNames.getFiles();
try (Aapt aapt = bypassAapt ? null : makeAapt()) {
// do a first pass at the list so we generate the code synchronously since it's required
// by the full splits asynchronous processing below.
List<ApkData> apkDataList = new ArrayList<>(splitsToGenerate);
for (ApkData apkData : splitsToGenerate) {
if (apkData.requiresAapt()) {
boolean codeGen =
(apkData.getType() == OutputFile.OutputType.MAIN
|| apkData.getFilter(OutputFile.FilterType.DENSITY) == null);
if (codeGen) {
apkDataList.remove(apkData);
invokeAaptForSplit(
manifestsOutputs,
libraryInfoList,
packageIdFileSet,
splitList,
featureResourcePackages,
apkData,
codeGen,
aapt);
break;
}
}
}
// now all remaining splits will be generated asynchronously.
for (ApkData apkData : apkDataList) {
if (apkData.requiresAapt()) {
executor.execute(
() -> {
invokeAaptForSplit(
manifestsOutputs,
libraryInfoList,
packageIdFileSet,
splitList,
featureResourcePackages,
apkData,
false,
aapt);
return null;
});
}
}
List<WaitableExecutor.TaskResult<Void>> taskResults = executor.waitForAllTasks();
taskResults.forEach(
taskResult -> {
if (taskResult.getException() != null) {
throw new BuildException(
taskResult.getException().getMessage(),
taskResult.getException());
}
});
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
if (multiOutputPolicy == MultiOutputPolicy.SPLITS) {
// now populate the pure splits list in the SplitScope (this should eventually move
// to the SplitDiscoveryTask.
outputScope.deleteAllEntries(
VariantScope.TaskOutputType.DENSITY_OR_LANGUAGE_SPLIT_PROCESSED_RES);
splitList.forEach(
(filterType, filterValues) -> {
// only for densities and languages.
if (filterType != VariantOutput.FilterType.DENSITY
&& filterType != VariantOutput.FilterType.LANGUAGE) {
return;
}
filterValues.forEach(
filterValue -> {
ApkData configurationApkData =
outputFactory.addConfigurationSplit(
filterType,
filterValue,
"" /* replaced later */);
configurationApkData.setVersionCode(
variantScope
.getVariantConfiguration()
.getVersionCode());
configurationApkData.setVersionName(
variantScope
.getVariantConfiguration()
.getVersionName());
// call user's script for the newly discovered resources split.
variantScope
.getVariantData()
.variantOutputFactory
.create(configurationApkData);
// in case we generated pure splits, we may have more than one
// resource AP_ in the output directory. reconcile with the
// splits list and save it for downstream tasks.
File packagedResForSplit =
findPackagedResForSplit(
resPackageOutputFolder, configurationApkData);
if (packagedResForSplit != null) {
configurationApkData.setOutputFileName(
packagedResForSplit.getName());
outputScope.addOutputForSplit(
VariantScope.TaskOutputType
.DENSITY_OR_LANGUAGE_SPLIT_PROCESSED_RES,
configurationApkData,
packagedResForSplit);
} else {
getLogger()
.warn(
"Cannot find output for "
+ configurationApkData);
}
});
});
}
// and save the metadata file.
outputScope.save(
ImmutableList.of(
VariantScope.TaskOutputType.DENSITY_OR_LANGUAGE_SPLIT_PROCESSED_RES,
VariantScope.TaskOutputType.PROCESSED_RES),
resPackageOutputFolder);
}
可以通過processFreeDebugResources來單獨執行這個Task
CompileTask
這個用來編譯java代碼,生成Dex文件,我們來看下
private void addCompileTask(@NonNull TaskFactory tasks, @NonNull VariantScope variantScope) {
//...
//添加Javac編譯到TransformManager
addJavacClassesStream(variantScope);
//設置編譯Java Task
setJavaCompilerTask(javacTask, tasks, variantScope);
//將Class文件轉化爲Dex文件,可加上可選的中間步驟,如混淆器和jacoco
createPostCompilationTasks(tasks, variantScope);
}
至此,我們就把一些重要的task分析完畢了,下一篇文章我們來嘗試下手動編譯生成apk。