在如今这个时代,仅仅会简单的页面开发已经很难找到好工作了。
所以在实习的业余时间,我开始学习Android的Gradle构建流程,并且想将此心路历程整理为博客,方便加深理解。
在本文,我将给大家介绍几点:
IncrementalTask
是如何实现增量的?—— IncrementalTask
AndroidManifest
是如何被合并的?—— ProcessManifest
Resources
如何被处理的?—— MergeResources
BuildConfig
是如何生成的?—— GenerateBuildConfig
PS:本文基于Gradle 3.2.1
版本
首先,我们在上一篇文章中讲解了,在AbstractAppPlugin中初始化了了ApplicationTaskManager。而ApplicationTaskManager中创建了许许多多的Task,包含了整个构建流程。从下文中我们可以简单地查看有哪些Task。
@Override public void createTasksForVariantScope(@NonNull final VariantScope variantScope) { BaseVariantData variantData = variantScope.getVariantData(); createAnchorTasks(variantScope); createCheckManifestTask(variantScope); handleMicroApp(variantScope); // Create all current streams (dependencies mostly at this point) createDependencyStreams(variantScope); // Add a task to publish the applicationId. createApplicationIdWriterTask(variantScope); taskFactory.create(new MainApkListPersistence.ConfigAction(variantScope)); createBuildArtifactReportTask(variantScope); // Add a task to process the manifest(s) createMergeApkManifestsTask(variantScope); // Add a task to create the res values createGenerateResValuesTask(variantScope); // Add a task to compile renderscript files. createRenderscriptTask(variantScope); // Add a task to merge the resource folders createMergeResourcesTask( variantScope, true, Sets.immutableEnumSet(MergeResources.Flag.PROCESS_VECTOR_DRAWABLES)); // Add tasks to compile shader createShaderTask(variantScope); // Add a task to merge the asset folders createMergeAssetsTask(variantScope); // Add a task to create the BuildConfig class createBuildConfigTask(variantScope); // Add a task to process the Android Resources and generate source files createApkProcessResTask(variantScope); // Add a task to process the java resources createProcessJavaResTask(variantScope); createAidlTask(variantScope); // Add NDK tasks createNdkTasks(variantScope); variantScope.setNdkBuildable(getNdkBuildable(variantData)); // Add external native build tasks createExternalNativeBuildJsonGenerators(variantScope); createExternalNativeBuildTasks(variantScope); // Add a task to merge the jni libs folders createMergeJniLibFoldersTasks(variantScope); // Add feature related tasks if necessary if (variantScope.getType().isBaseModule()) { // Base feature specific tasks. taskFactory.create(new FeatureSetMetadataWriterTask.ConfigAction(variantScope)); if (extension.getDataBinding().isEnabled()) { // Create a task that will package the manifest ids(the R file packages) of all // features into a file. This file's path is passed into the Data Binding annotation // processor which uses it to known about all available features. // // <p>see: {@link TaskManager#setDataBindingAnnotationProcessorParams(VariantScope)} taskFactory.create( new DataBindingExportFeatureApplicationIdsTask.ConfigAction(variantScope)); } } else { // Non-base feature specific task. // Task will produce artifacts consumed by the base feature taskFactory.create(new FeatureSplitDeclarationWriterTask.ConfigAction(variantScope)); if (extension.getDataBinding().isEnabled()) { // Create a task that will package necessary information about the feature into a // file which is passed into the Data Binding annotation processor. taskFactory.create(new DataBindingExportFeatureInfoTask.ConfigAction(variantScope)); } taskFactory.create(new MergeConsumerProguardFilesConfigAction(variantScope)); } // Add data binding tasks if enabled createDataBindingTasksIfNecessary(variantScope, MergeType.MERGE); // Add a compile task createCompileTask(variantScope); if (variantScope.getType().isBaseModule()) { CheckMultiApkLibrariesTask checkMultiApkLibrariesTask = taskFactory.create(new CheckMultiApkLibrariesTask.ConfigAction(variantScope)); // variantScope.setMergeJavaResourcesTask() is called in createCompileTask() above. // We set the merge java resources task to depend on this check, because merging java // resources is the first place an error could be thrown if there are duplicate // libraries. variantScope .getTaskContainer() .getMergeJavaResourcesTask() .dependsOn(checkMultiApkLibrariesTask); } createStripNativeLibraryTask(taskFactory, variantScope); if (variantScope.getVariantData().getMultiOutputPolicy().equals(MultiOutputPolicy.SPLITS)) { if (extension.getBuildToolsRevision().getMajor() < 21) { throw new RuntimeException( "Pure splits can only be used with buildtools 21 and later"); } createSplitTasks(variantScope); } BuildInfoWriterTask buildInfoWriterTask = createInstantRunPackagingTasks(variantScope); createPackagingTask(variantScope, buildInfoWriterTask); // Create the lint tasks, if enabled createLintTasks(variantScope); taskFactory.create(new FeatureSplitTransitiveDepsWriterTask.ConfigAction(variantScope)); createDynamicBundleTask(variantScope); } 复制代码
上述大部分的Task继承于IncrementalTask。所以说,如果想分析部分的Task,首先需要了解IncrementalTask的主要逻辑。了解了这个Task的逻辑之后,你就能知道代码应该从哪里看起了。
public abstract class IncrementalTask extends AndroidBuilderTask { public static final String MARKER_NAME = "build_was_incremental"; private File incrementalFolder; public void setIncrementalFolder(File incrementalFolder) { this.incrementalFolder = incrementalFolder; } @OutputDirectory @Optional public File getIncrementalFolder() { return incrementalFolder; } //是否增量 @Internal protected boolean isIncremental() { return false; } //全量构建的时候走这个方法 protected abstract void doFullTaskAction() throws Exception; //增量构建的时候,走这个方法 protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) throws Exception { // do nothing. } //task的入口 @TaskAction void taskAction(IncrementalTaskInputs inputs) throws Exception { //如果不是增量Task,或者输入不是增量,则走全量构建的方法 —— doFullTaskAction if (!isIncremental() || !inputs.isIncremental()) { getProject().getLogger().info("Unable do incremental execution: full task run"); doFullTaskAction(); return; } // 否则,走增量方法 doIncrementalTaskAction(getChangedInputs(inputs)); } //增量构建时,获取改变的inputs private Map<File, FileStatus> getChangedInputs(IncrementalTaskInputs inputs) { final Map<File, FileStatus> changedInputs = Maps.newHashMap(); //遍历过期的inputs,将其加入map inputs.outOfDate( change -> { FileStatus status = change.isAdded() ? FileStatus.NEW : FileStatus.CHANGED; changedInputs.put(change.getFile(), status); }); //遍历被移除的inputs,将其加入map inputs.removed(change -> changedInputs.put(change.getFile(), FileStatus.REMOVED)); return changedInputs; } } 复制代码
上面的逻辑很简单,总结一下就是:
1. 走全量构建时,走doFullTaskAction方法 2. 走增量构建时,走doIncrementalTaskAction,同时计算change的inputs。 复制代码
了解了IncrementalTask,我们就可以分析一下其他简单的Task了。
这个Task用于合并各个AndroidManifest,最终继承于IncrementalTask,而这个方法并没有覆写isIncremental方法,所以说是不支持增量的,于是我们只需要看doFullTaskAction方法。另外,AGP中,许许多多的Task中的成员变量,基本都是利用TaskConfigAction(一般都是Task中的静态内部类)进行配置的,这个Task也不例外。
public static class ConfigAction implements TaskConfigAction<MergeManifests> 复制代码
好了,我们看看doFullTaskAction方法
@Override protected void doFullTaskAction() throws IOException { // read the output of the compatible screen manifest. BuildElements compatibleScreenManifests = ExistingBuildElements.from( InternalArtifactType.COMPATIBLE_SCREEN_MANIFEST, compatibleScreensManifest); //省略了一些check @Nullable BuildOutput compatibleScreenManifestForSplit; ImmutableList.Builder<BuildOutput> mergedManifestOutputs = ImmutableList.builder(); //...省略了InstantRun逻辑 // FIX ME : multi threading. // TODO : LOAD the APK_LIST FILE ..... //这里遍历不同variant产生个Apk集合 for (ApkData apkData : outputScope.getApkDatas()) { compatibleScreenManifestForSplit = compatibleScreenManifests.element(apkData); File manifestOutputFile = FileUtils.join( getManifestOutputDirectory(), apkData.getDirName(), SdkConstants.ANDROID_MANIFEST_XML); //...省略了InstantRun逻辑 //MergingReport包含了Manifest合并的结果 MergingReport mergingReport = getBuilder() //这里包含了主要的Merge逻辑 .mergeManifestsForApplication( getMainManifest(), getManifestOverlays(), computeFullProviderList(compatibleScreenManifestForSplit), getNavigationFiles(), getFeatureName(), moduleMetadata == null ? getPackageOverride() : moduleMetadata.getApplicationId(), moduleMetadata == null ? apkData.getVersionCode() : Integer.parseInt(moduleMetadata.getVersionCode()), moduleMetadata == null ? apkData.getVersionName() : moduleMetadata.getVersionName(), getMinSdkVersion(), getTargetSdkVersion(), getMaxSdkVersion(), manifestOutputFile.getAbsolutePath(), // no aapt friendly merged manifest file necessary for applications. null /* aaptFriendlyManifestOutputFile */, instantRunManifestOutputFile.getAbsolutePath(), ManifestMerger2.MergeType.APPLICATION, variantConfiguration.getManifestPlaceholders(), getOptionalFeatures(), getReportFile()); //获取MergingReport中MERGE种类的XmlDocument XmlDocument mergedXmlDocument = mergingReport.getMergedXmlDocument(MergingReport.MergedManifestKind.MERGED); //从xml document中提取出各个属性 ImmutableMap<String, String> properties = mergedXmlDocument != null ? ImmutableMap.of( "packageId", mergedXmlDocument.getPackageName(), "split", mergedXmlDocument.getSplitName(), SdkConstants.ATTR_MIN_SDK_VERSION, mergedXmlDocument.getMinSdkVersion()) : ImmutableMap.of(); //将属性添加到输出里 mergedManifestOutputs.add( new BuildOutput( InternalArtifactType.MERGED_MANIFESTS, apkData, manifestOutputFile, properties)); //...省略了InstantRun逻辑 } new BuildElements(mergedManifestOutputs.build()).save(getManifestOutputDirectory()); //...省略了InstantRun逻辑 } 复制代码
根据我的注释,我们可以分为以下几步:
1. 首先会遍历ApkDatas,每一个variant对应一个ApkData 2. 进行Manifest的Merge逻辑,将结果保存在MergingReport中 3. 在MergingReport中,在其一个map中获取属性为MERGED的XmlDocument 4. 从XmlDocument中提取各个属性,然后作为一个BuildOutput对象存放在一个集合中 5. 将这个集合构建为BuildElement,save方法是将对象序列化为一个Json文件保存到Manifest的输出文件夹中。 复制代码
在这几步中,我最想了解的就是Manifest的是如何merge的,于是我们需要追踪mergeManifestsForApplication方法。
public MergingReport mergeManifestsForApplication( ....) { try { //Invoker主要是用于存放Manifest合并所需要的所有数据,在其构造方法中会检查必要的MainManifest。 Invoker manifestMergerInvoker = ManifestMerger2.newMerger(mainManifest, mLogger, mergeType) .setPlaceHolderValues(placeHolders) .addFlavorAndBuildTypeManifests( manifestOverlays.toArray(new File[manifestOverlays.size()])) .addManifestProviders(dependencies) .addNavigationFiles(navigationFiles) .withFeatures( optionalFeatures.toArray( new Invoker.Feature[optionalFeatures.size()])) .setMergeReportFile(reportFile) .setFeatureName(featureName); //... //这些属性可以直接覆盖ManifestSystemProperty中对应的属性 setInjectableValues(manifestMergerInvoker, packageOverride, versionCode, versionName, minSdkVersion, targetSdkVersion, maxSdkVersion); //调用merge方法,这里就开始merge了,很关键~~!! MergingReport mergingReport = manifestMergerInvoker.merge(); mLogger.verbose("Merging result: %1$s", mergingReport.getResult()); //根据merge的结果进行不同的处理,例如成功就直接写入文件了 switch (mergingReport.getResult()) { case WARNING: //... case SUCCESS: //.... break; case ERROR: //... default: //... } return mergingReport; } catch (ManifestMerger2.MergeFailureException e) { // TODO: unacceptable. throw new RuntimeException(e); } } 复制代码
逻辑也不是很复杂,创建一个Invoker保存所有Merge要用到的数据,之后再调用Merge方法进行合并。
public MergingReport merge() throws MergeFailureException { // provide some free placeholders values. ImmutableMap<ManifestSystemProperty, Object> systemProperties = mSystemProperties.build(); if (systemProperties.containsKey(ManifestSystemProperty.PACKAGE)) { // if the package is provided, make it available for placeholder replacement. mPlaceholders.put(PACKAGE_NAME, systemProperties.get(ManifestSystemProperty.PACKAGE)); // as well as applicationId since package system property overrides everything // but not when output is a library since only the final (application) // application Id should be used to replace libraries "applicationId" placeholders. if (mMergeType != MergeType.LIBRARY) { mPlaceholders.put(APPLICATION_ID, systemProperties.get(ManifestSystemProperty.PACKAGE)); } } FileStreamProvider fileStreamProvider = mFileStreamProvider != null ? mFileStreamProvider : new FileStreamProvider(); ManifestMerger2 manifestMerger = new ManifestMerger2( mLogger, mMainManifestFile, mLibraryFilesBuilder.build(), mFlavorsAndBuildTypeFiles.build(), mFeaturesBuilder.build(), mPlaceholders.build(), new MapBasedKeyBasedValueResolver<ManifestSystemProperty>( systemProperties), mMergeType, mDocumentType, Optional.fromNullable(mReportFile), mFeatureName, fileStreamProvider, mNavigationFilesBuilder.build()); return manifestMerger.merge(); } 复制代码
设置一些placeholder(占位符),之后用于替换,然后生成ManifestMerger2最终进行merge。
merge里面的代码较多,我们将其分为几个部分:
// load the main manifest file to do some checking along the way. LoadedManifestInfo loadedMainManifestInfo = load( new ManifestInfo( mManifestFile.getName(), mManifestFile, mDocumentType, Optional.<String>absent() /* mainManifestPackageName */), selectors, mergingReportBuilder); 复制代码
// load all the libraries xml files early to have a list of all possible node:selector // values. List<LoadedManifestInfo> loadedLibraryDocuments = loadLibraries( selectors, mergingReportBuilder, mainPackageAttribute.isPresent() ? mainPackageAttribute.get().getValue() : null); 复制代码
// perform system property injection performSystemPropertiesInjection(mergingReportBuilder, loadedMainManifestInfo.getXmlDocument()); 复制代码
Optional<XmlDocument> xmlDocumentOptional = Optional.absent(); for (File inputFile : mFlavorsAndBuildTypeFiles) { mLogger.verbose("Merging flavors and build manifest %s \n", inputFile.getPath()); LoadedManifestInfo overlayDocument = load( new ManifestInfo(null, inputFile, XmlDocument.Type.OVERLAY, Optional.of(mainPackageAttribute.get().getValue())), selectors, mergingReportBuilder); // 检查是否定义了package Optional<XmlAttribute> packageAttribute = overlayDocument.getXmlDocument().getPackage(); // 如果两个文件都定义了package,他们的package应该相同,不同则出错 if (loadedMainManifestInfo.getOriginalPackageName().isPresent() && packageAttribute.isPresent() && !loadedMainManifestInfo.getOriginalPackageName().get().equals( packageAttribute.get().getValue())) { String message = mMergeType == MergeType.APPLICATION ? String.format( "Overlay manifest:package attribute declared at %1$s value=(%2$s)\n" + "\thas a different value=(%3$s) " + "declared in main manifest at %4$s\n" + "\tSuggestion: remove the overlay declaration at %5$s " + "\tand place it in the build.gradle:\n" + "\t\tflavorName {\n" + "\t\t\tapplicationId = \"%2$s\"\n" + "\t\t}", packageAttribute.get().printPosition(), packageAttribute.get().getValue(), mainPackageAttribute.get().getValue(), mainPackageAttribute.get().printPosition(), packageAttribute.get().getSourceFile().print(true)) : String.format( "Overlay manifest:package attribute declared at %1$s value=(%2$s)\n" + "\thas a different value=(%3$s) " + "declared in main manifest at %4$s", packageAttribute.get().printPosition(), packageAttribute.get().getValue(), mainPackageAttribute.get().getValue(), mainPackageAttribute.get().printPosition()); mergingReportBuilder.addMessage( overlayDocument.getXmlDocument().getSourceFile(), MergingReport.Record.Severity.ERROR, message); return mergingReportBuilder.build(); } //... } 复制代码
if (mMergeType == MergeType.LIBRARY) { // extract the package name... String mainManifestPackageName = loadedMainManifestInfo.getXmlDocument().getRootNode() .getXml().getAttribute("package"); // save it in the selector instance. if (!Strings.isNullOrEmpty(mainManifestPackageName)) { xmlDocumentOptional.get().getRootNode().getXml() .setAttribute("package", mainManifestPackageName); } } 复制代码
for (LoadedManifestInfo libraryDocument : loadedLibraryDocuments) { mLogger.verbose("Merging library manifest " + libraryDocument.getLocation()); xmlDocumentOptional = merge( xmlDocumentOptional, libraryDocument, mergingReportBuilder); if (!xmlDocumentOptional.isPresent()) { return mergingReportBuilder.build(); } } 复制代码
if (!mOptionalFeatures.contains(Invoker.Feature.NO_PLACEHOLDER_REPLACEMENT)) { // do one last placeholder substitution, this is useful as we don't stop the build // when a library failed a placeholder substitution, but the element might have // been overridden so the problem was transient. However, with the final document // ready, all placeholders values must have been provided. MergingReport.Record.Severity severity = mMergeType == MergeType.LIBRARY ? MergingReport.Record.Severity.INFO : MergingReport.Record.Severity.ERROR; performPlaceHolderSubstitution( loadedMainManifestInfo, xmlDocumentOptional.get(), mergingReportBuilder, severity); if (mergingReportBuilder.hasErrors()) { return mergingReportBuilder.build(); } } 复制代码
至此,一个完整的Manifest合并流程就结束了。
查看MergeResources这个Task,我们发现继承于IncrementalTask,同时该Task覆写了isIncremental,返回true。
所以说,我们需要查看doFullTaskAction方法以及doIncrementalTaskAction方法。
这个方法步骤很多,我们查看主要步骤就可以了。
File destinationDir = getOutputDir(); FileUtils.cleanOutputDir(destinationDir); if (dataBindingLayoutInfoOutFolder != null) { FileUtils.deleteDirectoryContents(dataBindingLayoutInfoOutFolder); } 复制代码
List<ResourceSet> resourceSets = getConfiguredResourceSets(preprocessor); 复制代码
这个方法会返回一个ResourceSet集合,里面主要是自己的res、library的res以及generated的res
// create a new merger and populate it with the sets. ResourceMerger merger = new ResourceMerger(minSdk.get()); MergingLog mergingLog = null; if (blameLogFolder != null) { FileUtils.cleanOutputDir(blameLogFolder); mergingLog = new MergingLog(blameLogFolder); } //ResourceCompilationService根据aaptGeneration判断,使用AAPT或者AAPT2等 try (ResourceCompilationService resourceCompiler = getResourceProcessor(....)) { for (ResourceSet resourceSet : resourceSets) { resourceSet.loadFromFiles(getILogger()); //填充merger merger.addDataSet(resourceSet); } 复制代码
MergedResourceWriter writer = new MergedResourceWriter( workerExecutorFacade, destinationDir, getPublicFile(), mergingLog, preprocessor, resourceCompiler, getIncrementalFolder(), dataBindingLayoutProcessor, mergedNotCompiledResourcesOutputDirectory, pseudoLocalesEnabled, getCrunchPng()); 复制代码
merger.mergeData(writer, false /*doCleanUp*/); 复制代码
@NonNull private final ConcurrentLinkedQueue<CompileResourceRequest> mCompileResourceRequests = new ConcurrentLinkedQueue<>(); 复制代码
那么问题来了,为什么会使用Concurrent包中的队列来保证并发性呢?—— 在removeItem中有这样一段注释
/* * There are two reasons to skip this: 1. we save an IO operation by * deleting a file that will be overwritten. 2. if we did delete the file, * we would have to be careful about concurrency to make sure we would be * deleting the *old* file and not the overwritten version. */ 复制代码
我们要确保删除的文件的文件是老版本的文件,而不是覆盖的新版本的文件。
while (!mCompileResourceRequests.isEmpty()) { CompileResourceRequest request = mCompileResourceRequests.poll(); try { //...处理DataBinding相关逻辑 //mResourceCompiler是个接口,实现其接口的类为Aapt2相关的类 //调用submitCompile方法,利用Aapt2命令编译资源文件 mResourceCompiler.submitCompile( new CompileResourceRequest( fileToCompile, request.getOutputDirectory(), request.getInputDirectoryName(), pseudoLocalesEnabled, crunchPng, ImmutableMap.of(), request.getInputFile())); //编译完成之后就将文件放入这个map中 mCompiledFileMap.put( fileToCompile.getAbsolutePath(), mResourceCompiler.compileOutputFor(request).getAbsolutePath()); } catch (ResourceCompilationException | IOException e) { throw MergingException.wrapException(e) .withFile(request.getInputFile()) .build(); } } //...后面释放一些资源,然后调用mCompiledFileMap.store方法将map存储下来 复制代码
至此,MergeResources的过程就结束了。
我们经常使用BuildConfig.java这个类,来获取一些Gradle的配置属性,那么你一定很好奇BuildConfig这个类是如何生成的吧?
GenerateBuildConfig这个类就是一个专门用于生成BuildConfig的Task。
首先,我们查看@TaskAction注解标注的方法,这是Task的入口。
@TaskAction void generate() throws IOException { //在packageName改变的时候,一定要删除输出文件夹,否则会有两个类 File destinationDir = getSourceOutputDir(); FileUtils.cleanOutputDir(destinationDir); //创建BuildConfigGenerator,配置PackageName和输出文件夹 BuildConfigGenerator generator = new BuildConfigGenerator( getSourceOutputDir(), getBuildConfigPackageName()); // 利用Generator添加BuildConfig里面的Field 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()) + '"') //上面那些都是基本的属性,getItems()里面添加的就是自定义属性 .addItems(getItems()); List<String> flavors = getFlavorNamesWithDimensionNames(); int count = flavors.size(); if (count > 1) { for (int i = 0; i < count; i += 2) { //添加flavor generator.addField( "String", "FLAVOR_" + flavors.get(i + 1), '"' + flavors.get(i) + '"'); } } //最后生成一个类 generator.generate(); } 复制代码
逻辑很简单,接下来我们看看getItems()里面会获取到哪些属性。这个items最初是由VariantConfiguration.getBuildConfigItems赋值的:
@NonNull public List<Object> getBuildConfigItems() { List<Object> fullList = Lists.newArrayList(); // keep track of the names already added. This is because we show where the items // come from so we cannot just put everything a map and let the new ones override the // old ones. Set<String> usedFieldNames = Sets.newHashSet(); //添加Variant特定的Field Collection<ClassField> list = mBuildConfigFields.values(); if (!list.isEmpty()) { fullList.add("Fields from the variant"); fillFieldList(fullList, usedFieldNames, list); } //添加Key为Filed的属性 list = mBuildType.getBuildConfigFields().values(); if (!list.isEmpty()) { fullList.add("Fields from build type: " + mBuildType.getName()); fillFieldList(fullList, usedFieldNames, list); } //添加每个Flavor中key为Field的属性 for (F flavor : mFlavors) { list = flavor.getBuildConfigFields().values(); if (!list.isEmpty()) { fullList.add("Fields from product flavor: " + flavor.getName()); fillFieldList(fullList, usedFieldNames, list); } } //添加默认Config中key为Field的属性 list = mDefaultConfig.getBuildConfigFields().values(); if (!list.isEmpty()) { fullList.add("Fields from default config."); fillFieldList(fullList, usedFieldNames, list); } return fullList; } 复制代码
所以说我们在Gradle中定义BuildConfigField,就是这么生成的。
buildConfigField("String","HAHA","\"haahahah\"") 复制代码
之后再将其属性写入BuildConfig.java中就可以了。
本文为大家讲解了如何去分析各种AGP的Task,如果大家想查看其他的AGP Task源码,则需要自己去分析啦。如果有问题,可以直接在评论中提出哈。
最后吐槽:AGP源码很容易大改,着实让学习者摸不清头脑。