Android 編譯流程解析02-相關Task解析

簡介

上篇文章中,我們介紹了AppPlugin的初始化,在其父類BasePlugin的apply()中,會調用ApplicationTaskManagercreateTasksForVariantScope(),方法內初始化了很多的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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章