在如今这个时代,仅仅会简单的页面开发已经很难找到好工作了。
所以在实习的业余时间,我开始学习Android的Gradle构建流程,并且想将此心路历程整理为博客,方便加深理解。
简述
在本文,我将给大家介绍几点:
-
IncrementalTask
是如何实现增量的?—— IncrementalTask -
AndroidManifest
是如何被合并的?—— ProcessManifest -
Resources
如何被处理的?—— MergeResources -
BuildConfig
是如何生成的?—— GenerateBuildConfig
PS:本文基于Gradle 3.2.1
版本
ApplicationTaskManager
首先,我们在上一篇文章中讲解了,在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);
}
IncrementalTask
上述大部分的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了。
ProcessManifest
这个Task用于合并各个AndroidManifest,最终继承于IncrementalTask,而这个方法并没有覆写isIncremental方法,所以说是不支持增量的,于是我们只需要看doFullTaskAction方法。另外,AGP中,许许多多的Task中的成员变量,基本都是利用TaskConfigAction(一般都是Task中的静态内部类)进行配置的,这个Task也不例外。
public static class ConfigAction implements TaskConfigAction<MergeManifests>
好了,我们看看doFullTaskAction方法
ProcessManifest.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方法。
AndroidBuilder.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方法进行合并。
Invoker.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。
ManifestMerger2.merge
merge里面的代码较多,我们将其分为几个部分:
- 加载Main Manifest
// 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);
- 进行一些检查,例如是否设置了package attribute
- 加载library的xml
// 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);
- 执行Manifest系统属性注入:例如version、version_name之类的
// perform system property injection
performSystemPropertiesInjection(mergingReportBuilder,
loadedMainManifestInfo.getXmlDocument());
- 遍历mFlavorsAndBuildTypeFiles,进行一些check
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();
}
//...
}
- merge library的时候,强制将mainManifest的package注入进去
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);
}
}
- Merge library的Manifest
for (LoadedManifestInfo libraryDocument : loadedLibraryDocuments) {
mLogger.verbose("Merging library manifest " + libraryDocument.getLocation());
xmlDocumentOptional = merge(
xmlDocumentOptional, libraryDocument, mergingReportBuilder);
if (!xmlDocumentOptional.isPresent()) {
return mergingReportBuilder.build();
}
}
- 替换Manifest中的PlaceHolder
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(finalMergedDocument)再进行一些处理,这里我们就不关心了
- 最终生成合并后的AndroidManifest.xml文件
至此,一个完整的Manifest合并流程就结束了。
MergeResources
查看MergeResources这个Task,我们发现继承于IncrementalTask,同时该Task覆写了isIncremental,返回true。
所以说,我们需要查看doFullTaskAction方法以及doIncrementalTaskAction方法。
MergeResources.doFullTaskAction
这个方法步骤很多,我们查看主要步骤就可以了。
- 清理之前的输出文件夹
File destinationDir = getOutputDir();
FileUtils.cleanOutputDir(destinationDir);
if (dataBindingLayoutInfoOutFolder != null) {
FileUtils.deleteDirectoryContents(dataBindingLayoutInfoOutFolder);
}
- 获取资源文件
List<ResourceSet> resourceSets = getConfiguredResourceSets(preprocessor);
这个方法会返回一个ResourceSet集合,里面主要是自己的res、library的res以及generated的res
- 创建ResourceMerger,并且用刚刚获取的ResourceSet集合进行填充
// 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,这个类负责编译资源文件以及从layout文件中剥离Databinding的东西
MergedResourceWriter writer =
new MergedResourceWriter(
workerExecutorFacade,
destinationDir,
getPublicFile(),
mergingLog,
preprocessor,
resourceCompiler,
getIncrementalFolder(),
dataBindingLayoutProcessor,
mergedNotCompiledResourcesOutputDirectory,
pseudoLocalesEnabled,
getCrunchPng());
- 合并资源
merger.mergeData(writer, false /*doCleanUp*/);
- 将MergedResourceWriter传入该方法后,调用MergedResourceWriter的start、ignoreItemInMerge、removeItem、addItem和end方法对资源进行处理,上述方法中的Item代表这一个资源文件(res目录下的每个文件,可能是图片资源,也可能是xml文件)。每一个Item会在MergedResourceWriter中转化为一个CompileResourceRequest,然后添加到mCompileResourceRequests这个集合中,该集合定义如下:
@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.
*/
我们要确保删除的文件的文件是老版本的文件,而不是覆盖的新版本的文件。
- 在MergedResourceWriter的start方法中,初始化了一些变量。而在end方法中,则是处理mCompileResourceRequests了。关键地方如下:
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的过程就结束了。
GenerateBuildConfig
我们经常使用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源码很容易大改,着实让学习者摸不清头脑。