Android 探索之 Task 分析(四)
本文将会分析如下几个task
generateReleaseBuildConfig
prepareLintJar
mainApkListPersistenceRelease
generateReleaseResValues
generateReleaseResources
mergeReleaseResources
7.generateReleaseBuildConfig
该task主要作用是生成BuildConfig
文件,主要代码逻辑是,配置阶段代码GenerateBuildConfig.ConfigAction->execute()
,主要就是读取gradle文件里面的一些信息(appPackageName、versionName、versionCode等)
源码分析
//com.android.build.gradle.tasks.GenerateBuildConfig
@Override
public void execute(@NonNull GenerateBuildConfig generateBuildConfigTask) {
BaseVariantData variantData = scope.getVariantData();
variantData.generateBuildConfigTask = generateBuildConfigTask;
final GradleVariantConfiguration variantConfiguration =
variantData.getVariantConfiguration();
generateBuildConfigTask.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
generateBuildConfigTask.setVariantName(scope.getVariantConfiguration().getFullName());
generateBuildConfigTask.buildConfigPackageName =
TaskInputHelper.memoize(variantConfiguration::getOriginalApplicationId);
generateBuildConfigTask.appPackageName =
TaskInputHelper.memoize(variantConfiguration::getApplicationId);
generateBuildConfigTask.versionName =
TaskInputHelper.memoize(variantConfiguration::getVersionName);
generateBuildConfigTask.versionCode =
TaskInputHelper.memoize(variantConfiguration::getVersionCode);
generateBuildConfigTask.debuggable =
TaskInputHelper.memoize(
() -> variantConfiguration.getBuildType().isDebuggable());
generateBuildConfigTask.buildTypeName = variantConfiguration.getBuildType().getName();
// no need to memoize, variant configuration does that already.
generateBuildConfigTask.flavorName = variantConfiguration::getFlavorName;
// 获取flavorDimensions
generateBuildConfigTask.flavorNamesWithDimensionNames =
TaskInputHelper.memoize(variantConfiguration::getFlavorNamesWithDimensionNames);
// 获取自定义的buildConfigField
generateBuildConfigTask.items =
TaskInputHelper.memoize(variantConfiguration::getBuildConfigItems);
generateBuildConfigTask.setSourceOutputDir(scope.getBuildConfigSourceOutputDir());
}
执行阶段,根据配置阶段读取的gradle里面关于module的信息,然后生成BuildConfig
文件。
@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);
// 1、BuildConfig文件生成器
BuildConfigGenerator generator = new BuildConfigGenerator(
getSourceOutputDir(),
getBuildConfigPackageName());
// Hack (see IDEA-100046): We want to avoid reporting "condition is always true"
// from the data flow inspection, so use a non-constant value. However, that defeats
// the purpose of this flag (when not in debug mode, if (BuildConfig.DEBUG && ...) will
// be completely removed by the compiler), so as a hack we do it only for the case
// where debug is true, which is the most likely scenario while the user is looking
// at source code.
//map.put(PH_DEBUG, Boolean.toString(mDebug));
// 2、生成config文件的固定信息(DEBUG、APPLICATION_ID、BUILD_TYPE、FLAVOR、VERSION_CODE、VERSION_NAME)
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());
// 得到flavors 数组,然后生成响应的属性到BuildConfig文件中
List 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) + '"');
}
}
generator.generate();
}
根据代码和输出信息,清楚的知道此task是读取gradle里面的config信息,然后生成BuildConfig.java
文件。生成信息类似如下
输入和输出
-----task begin-------->
project: project ':app_driver'
name: generateReleaseBuildConfig
group: null
description: null
conv: [:]
inputs:
outputs:
/Users/dongkai/Code/XiWeiLogistics/app_driver/build/generated/source/buildConfig/release
<------task end -------
生成文件为BuildConfig.java
8.prepareLintJar
准备lint,拷贝 lint jar 包到指定位置,由于lint开启时,需要手动指定 lint的jar包,此任务就是复制对应的lint jar包到特定目录下
源码分析
// com.android.build.gradle.internal.tasks.PrepareLintJar
@TaskAction
public void prepare() throws IOException {
// there could be more than one files if the dependency is on a sub-projects that
// publishes its compile dependencies. Rather than query getSingleFile and fail with
// a weird message, do a manual check
Set<File> files = lintChecks.getFiles();
if (files.size() > 1) {
throw new RuntimeException(
"Found more than one jar in the '"
+ VariantDependencies.CONFIG_NAME_LINTCHECKS
+ "' configuration. Only one file is supported. If using a separate Gradle project, make sure compilation dependencies are using compileOnly");
}
if (files.isEmpty()) {
if (outputLintJar.isFile()) {
FileUtils.delete(outputLintJar);
}
} else {
FileUtils.mkdirs(outputLintJar.getParentFile());
Files.copy(Iterables.getOnlyElement(files), outputLintJar);
}
}
输入和输出
-----task begin-------->
project: project ':app_driver'
name: prepareLintJar
group: null
description: null
conv: [:]
inputs:
outputs:
/Users/dongkai/Code/XiWeiLogistics/app_driver/build/intermediates/lint/lint.jar
<------task end -------
9.mainApkListPersistenceRelease
将 apkData列表内容写入 apk-list.json
源码分析
// com.android.build.gradle.tasks.MainApkListPersistence
@TaskAction
fun fullTaskAction() {
FileUtils.deleteIfExists(outputFile)
val apkDataList = ExistingBuildElements.persistApkList(apkData)
FileUtils.createFile(outputFile, apkDataList)
}
输入和输出
-----task begin-------->
project: project ':app_driver'
name: mainApkListPersistenceRelease
group: null
description: null
conv: [:]
inputs:
outputs:
/Users/dongkai/Code/XiWeiLogistics/app_driver/build/intermediates/splits-support/release/apk-list/apk-list.gson
<------task end -------
variant中的apkDatas
json内容类似如下
[
{
"type": "FULL_SPLIT",
"splits": [],
"versionCode": 1,
"versionName": "1.0",
"enabled": true,
"filterName": "universal",
"outputFile": "app-universal-release-unsigned.apk",
"fullName": "universalRelease",
"baseName": "universal-release"
},
{
"type": "FULL_SPLIT",
"splits": [
{
"filterType": "DENSITY",
"value": "mdpi"
}
],
"versionCode": 1,
"versionName": "1.0",
"enabled": true,
"filterName": "mdpi",
"outputFile": "app-mdpi-release-unsigned.apk",
"fullName": "mdpiRelease",
"baseName": "mdpi-release"
},
{
"type": "FULL_SPLIT",
"splits": [
{
"filterType": "DENSITY",
"value": "hdpi"
}
],
"versionCode": 1,
"versionName": "1.0",
"enabled": true,
"filterName": "hdpi",
"outputFile": "app-hdpi-release-unsigned.apk",
"fullName": "hdpiRelease",
"baseName": "hdpi-release"
},
{
"type": "FULL_SPLIT",
"splits": [
{
"filterType": "DENSITY",
"value": "xhdpi"
}
],
"versionCode": 1,
"versionName": "1.0",
"enabled": true,
"filterName": "xhdpi",
"outputFile": "app-xhdpi-release-unsigned.apk",
"fullName": "xhdpiRelease",
"baseName": "xhdpi-release"
}
]
10.generateReleaseResValues
该任务就是把我们在gradle里面配置的resValue读取到,然后在app/build/generated/res/resValues/release
目录下生成generate.xml
文件,该文件里面的内容,可直接在xml文件和代码中使用,方便做一些动态化的配置。
源码分析
// com.android.build.gradle.tasks.GenerateResValues
@Override
public void execute(@NonNull GenerateResValues generateResValuesTask) {
scope.getVariantData().generateResValuesTask = generateResValuesTask;
final GradleVariantConfiguration variantConfiguration =
scope.getVariantData().getVariantConfiguration();
generateResValuesTask.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
generateResValuesTask.setVariantName(variantConfiguration.getFullName());
generateResValuesTask.items =
TaskInputHelper.memoize(variantConfiguration::getResValues);
generateResValuesTask.setResOutputDir(scope.getGeneratedResOutputDir());
}
// 主要生成逻辑
@TaskAction
void generate() throws IOException, ParserConfigurationException {
// 1、输出路径,就是我们output中的目录
File folder = getResOutputDir();
// 2、这里的getItems()就是获得我们在代码中设置的resValues
List<Object> resolvedItems = getItems();
if (resolvedItems.isEmpty()) {
FileUtils.cleanOutputDir(folder);
} else {
// 3、在resolvedItems不等于空的时候,通过ResValueGenerator生成我们的generated.xml文件
ResValueGenerator generator = new ResValueGenerator(folder);
generator.addItems(getItems());
generator.generate();
}
}
输入和输出
-----task begin-------->
project: project ':app_driver'
name: generateReleaseResValues
group: null
description: null
conv: [:]
inputs:
outputs:
/Users/dongkai/Code/XiWeiLogistics/app_driver/build/generated/res/resValues/release
<------task end -------
使用方法示例
//1 .在build.gradle 中增加如下代码,resValue
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// 添加代码
resValue "string", "AppName", "app_release"
}
// 添加代码
debug {
resValue "string", "AppName", "app_debug"
}
}
//2.执行过generateReleaseResValues task以后,会在app/build/generated/res/resValues/release目录下生成一个generated.xml文件
//文件内容类似如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Automatically generated file. DO NOT MODIFY -->
<!-- Values from build type: release -->
<string name="AppName" translatable="false">app_release</string>
</resources>
//3.生成的内容我们可以直接在xml中使用(android:text="@string/AppName"),也可以在代码中使用(getResources().getString(R.string.AppName);)。
// 也可以通过product Flavor 配置
11.generateReleaseResources
该task为锚点task,保证此前的task执行完以后,才会执行后续的task,为后续的Resource处理做准备
源码分析
输入和输出
-----task begin-------->
project: project ':app_driver'
name: generateReleaseResources
group: null
description: null
conv: [:]
inputs:
outputs:
<------task end -------
12.mergeReleaseResources
mergeReleaseResources
的任务是把依赖的库和工程中的资源进行merge操作。
源码分析
//com.android.build.gradle.tasks.MergeResources
//主要逻辑代码MergeResources.java->doFullTaskAction()方法
@Override
protected void doFullTaskAction() throws IOException, JAXBException {
// 1、得到ResourcePreprocessor子类对象
ResourcePreprocessor preprocessor = getPreprocessor();
// 2、得到task的output目录
// this is full run, clean the previous outputs
File destinationDir = getOutputDir();
FileUtils.cleanOutputDir(destinationDir);
if (dataBindingLayoutInfoOutFolder != null) {
FileUtils.deleteDirectoryContents(dataBindingLayoutInfoOutFolder);
}
// 3、得到inputs文件目录集合
List<ResourceSet> resourceSets = getConfiguredResourceSets(preprocessor);
// 4、生成ResourceMerger 对象
// create a new merger and populate it with the sets.
ResourceMerger merger = new ResourceMerger(minSdk);
MergingLog mergingLog = null;
// 5、mergeingLog 记录
if (blameLogFolder != null) {
FileUtils.cleanOutputDir(blameLogFolder);
mergingLog = new MergingLog(blameLogFolder);
}
// 6、resourceCompiler,实际是Appt工具对象
try (QueueableResourceCompiler resourceCompiler =
getResourceProcessor(
aaptGeneration,
getBuilder(),
crunchPng,
variantScope,
getAaptTempDir(),
mergingLog,
flags,
processResources)) {
for (ResourceSet resourceSet : resourceSets) {
resourceSet.loadFromFiles(getILogger());
merger.addDataSet(resourceSet);
}
MergedResourceWriter writer =
new MergedResourceWriter(
workerExecutorFacade,
destinationDir,
getPublicFile(),
mergingLog,
preprocessor,
resourceCompiler,
getIncrementalFolder(),
dataBindingLayoutProcessor,
mergedNotCompiledResourcesOutputDirectory,
pseudoLocalesEnabled,
getCrunchPng());
// 7、执行merge resource 操作
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();
}
}
第一步:得到ResourcePreprocessor
子类对象,实际是MergeResourcesVectorDrawableRenderer
对象,该类又继承了VectorDrawableRenderer
,该类是通过VectorDrawable
文件生产PNG图片,或者拷贝 xml文件。
第二步:得到task的output目录,实际上就是/app/build/intermediates/res/merged/release目录;
第三步:得到inputs文件目录集合,调用getConfiguredResourceSets(preprocessor)
方法,该方法就是得到inputs路径;
第四步:生成ResourceMerger
对象。该类实现了DataMerger抽象类,主要的merge逻辑均在此类里面实现 ,类名com.android.ide.common.res2.ResourceMerger
第五步:mergeingLog 记录,就是intermediates/blame/res/release
目录,记录操作日志。
第六步:resourceCompiler,实际是Appt工具对象。
第七步:执行merge resource 操作
输入和输出
-----task begin-------->
project: project ':app_driver'
name: mergeReleaseResources
group: null
description: null
conv: [:]
inputs:
/Users/dongkai/Code/XiWeiLogistics/app_driver/build/generated/res/resValues/release
/Users/dongkai/Code/XiWeiLogistics/ymm_app_driver_main_module/build/intermediates/packaged_res/release
/Users/dongkai/Code/XiWeiLogistics/biz_common/build/intermediates/packaged_res/release
/Users/dongkai/.gradle/caches/transforms-1/files-1.1/activity_result_util-1.0.1.aar/4b6d13d6fe7210d069c29939ba8fea95/res
...
...
...
/Users/dongkai/.gradle/caches/transforms-1/files-1.1/sdk-2.13.3.0.aar/cbde3b805a388d5cd0d41d9ac1be0c66/res
/Users/dongkai/Code/XiWeiLogistics/app_driver/build/generated/res/rs/release
/Users/dongkai/Code/XiWeiLogistics/app_driver/src/release/res
/Users/dongkai/Code/XiWeiLogistics/app_driver/src/main/res
outputs:
/Users/dongkai/Code/XiWeiLogistics/app_driver/build/intermediates/blame/res/release
/Users/dongkai/Code/XiWeiLogistics/app_driver/build/generated/res/pngs/release
/Users/dongkai/Code/XiWeiLogistics/app_driver/build/intermediates/incremental/mergeReleaseResources
/Users/dongkai/Code/XiWeiLogistics/app_driver/build/intermediates/res/merged/release
<------task end -------
输入文件路径,大致有这几种:
generateReleaseResValues
任务(上一步)生成的resValues文件(generated/res/resValues/release/generated.xml);- 引用的aar包里面的资源(appcompat-v7-26.1.0.aar/a7cc521b4567369eba0ddb355f44a660/res,/sdk-2.13.3.0.aar/cbde3b805a388d5cd0d41d9ac1be0c66/res,第三方的res);
compileReleasegRenderscript
任务生的Renderscript
文件(generated/res/rs/release);- 项目中的res资源文件(/app/src/release/res、/app/src/main/res);
输入文件经过mergeReleaseResources
任务处理后,生产的输出文件有如下几种:
- merge操作的日志记录(intermediates/blame/res/release);
- png图片集合(generated/res/pngs/release);
- merge后的资源集合(incremental/mergeReleaseResources);
- 资源映射关系集合(intermediates/res/merged/release);