taskName:checkDebugManifest
=========================================================
output:/Users/apple/work/project/AndroidGradleTaskDemo/app/build/intermediates/check-manifest/debug
@TaskAction
void check() {
if (!isOptional && manifest != null && !manifest.isFile()) {
throw new IllegalArgumentException(
String.format(
"Main Manifest missing for variant %1$s. Expected path: %2$s",
getVariantName(), getManifest().getAbsolutePath()));
}
}
该任务是校验操作,就是校验清单文件的合法性,不合法则中断任务,抛出异常
配置阶段操作
@Override
public void execute(@NonNull CheckManifest checkManifestTask) {
//1. 设置任务实现类
scope.getTaskContainer().setCheckManifestTask(checkManifestTask);
//2. 设置相关入参
checkManifestTask.setVariantName(
scope.getVariantData().getVariantConfiguration().getFullName());
checkManifestTask.setOptional(isManifestOptional);
checkManifestTask.manifest =
scope.getVariantData().getVariantConfiguration().getMainManifest();
//3. 出参目录配置
checkManifestTask.fakeOutputDir =
new File(
scope.getGlobalScope().getIntermediatesDir(),
"check-manifest/" + scope.getVariantConfiguration().getDirName());
}
依赖任务:preBuildTask
BuildConfig.java想必大家非常熟悉,该任务就是生成它
taskName:generateDebugBuildConfig
=========================================================
output:/Users/apple/work/project/AndroidGradleTaskDemo/app/build/generated/source/buildConfig/debug
========================END==============================
@TaskAction
void generate() throws IOException {
//1. 清理操作
// must clear the folder in case the packagename changed, otherwise,
// there'll be two classes.
File destinationDir = getSourceOutputDir();
FileUtils.cleanOutputDir(destinationDir);
//2. 构建一个BuildConfig生成器,用于生成BuildConfig.java
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));
//3. 给生成器对象配置6个固定字段
// 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());//4. getItems,则是我们在build.gradle自定义的字段属性值
//5. 生成flavor字段
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) + '"');
}
}
generator.generate();
}
在build.gralde -> defaultConfig 添加
buildConfigField("String", "name", "\"xiaobaoyihao\"")
执行
./gradlew generateDebugBuildConfig
看下BuildConfig.java结构
/**
* Automatically generated file. DO NOT MODIFY
*/
package com.gradle.task.demo;
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "com.gradle.task.demo";
public static final String BUILD_TYPE = "debug";
public static final String FLAVOR = "";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "1.0";
// Fields from default config.
public static final String name = "xiaobaoyihao";
}
可以看到自定义属性字段成功写入到BuildConfig.java类中了;至于类生成具体实现细节是在generate方法中,主要使用了文件输出流配合JavaWriter类操作来协调完成的,有兴趣自行研究
//BuildConfigGenerator.java
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));
//1. 生成文件头注释说明文案
writer.emitJavadoc("Automatically generated file. DO NOT MODIFY")
.emitPackage(mBuildConfigPackageName)
.beginType("BuildConfig", "class", PUBLIC_FINAL);
//2. 就是上面提到的6个固定字段属性
for (ClassField field : mFields) {
emitClassField(writer, field);
}
//3. 自定义属性字段
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();
}
}
taskName:prepareLintJar
=========================================================
output:/Users/apple/work/project/AndroidGradleTaskDemo/app/build/intermediates/lint_jar/global/prepareLintJar/lint.jar
/**
* Task that takes the configuration result, and check that it's correct.
*
* Then copies it in the build folder to (re)publish it. This is not super efficient but because
* publishing is done at config time when we don't know yet what lint.jar file we're going to
* publish, we have to do this.
*/
public class PrepareLintJar extends DefaultTask {
...
@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
//1. 获取项目中依赖的自定义lint规则jar,此处gradle插件对其做了限制,只能有一个lint.jar超过一个会build失败
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");
}
//2. 如果项目中没有自定义lint规则,则清理文件,否则copyt lint.jar到指定目录
if (files.isEmpty()) {
if (outputLintJar.isFile()) {
FileUtils.delete(outputLintJar);
}
} else {
FileUtils.mkdirs(outputLintJar.getParentFile());
Files.copy(Iterables.getOnlyElement(files), outputLintJar);
}
}
//ConfigAction.java
@Override
public void execute(@NonNull PrepareLintJar task) {
//指定输出位置
task.outputLintJar =
scope.getArtifacts()
.appendArtifact(InternalArtifactType.LINT_JAR, task, FN_LINT_JAR);
//读取自定义规则文件集合
task.lintChecks = scope.getLocalCustomLintChecks();
}
}
从上可以看出这个任务就是copy功能,lint.jar不能超过1个,也就是说如果项目中存在混合语言时,如果你要对其进行各自自定义一套check规则(每套规则会生成一个lint.jar),那对不起,执行到这任务直接中断,这应该算Android插件一个缺陷吧,高版本已修复;
关于如何自定义check规则,可以参考如下官网相关链接
https://developer.android.com/studio/write/lint?hl=zh-cn
https://github.com/googlesamples/android-custom-lint-rules.git
http://tools.android.com/tips/lint-custom-rules
taskName:mainApkListPersistenceDebug
=========================================================
output:/Users/apple/work/project/AndroidGradleTaskDemo/app/build/intermediates/apk_list/debug/mainApkListPersistenceDebug/apk-list.gson
格式化后的apk-list.gson
[
{
"type": "MAIN",
"splits": [],
"versionCode": 1,
"versionName": "1.0",
"enabled": true,
"outputFile": "app-debug.apk",
"fullName": "debug",
"baseName": "debug"
}
]
这个任务其实就是生成一个apk信息的gson文件,看下核心入口代码
这个类非常简短,我直接全部贴出来吧,有意思的是它是kotlin写的
/**
* Task to persist the {@see OutputScope#apkdatas} which allows downstream tasks to depend
* on the {@see InternalArtifactType#APK_LIST} rather than on various complicated data structures.
* This also allow to record the choices made during configuration time about what APKs will be
* produced and which ones are enabled.
*/
open class MainApkListPersistence : AndroidVariantTask() {
@get:OutputFile
lateinit var outputFile: File
private set
@get:Input
lateinit var apkData : Collection<ApkData>
private set
@TaskAction
fun fullTaskAction() {
//1. 清理操作
FileUtils.deleteIfExists(outputFile)
//2. 返回一个apk信息的字符串
val apkDataList = ExistingBuildElements.persistApkList(apkData)
//3. 写入文件
FileUtils.createFile(outputFile, apkDataList)
}
class ConfigAction(
val scope: VariantScope) :
TaskConfigAction<MainApkListPersistence> {
override fun getName() = scope.getTaskName("mainApkListPersistence")
override fun getType() = MainApkListPersistence::class.java
override fun execute(task: MainApkListPersistence) {
task.variantName = scope.fullVariantName
task.apkData = scope.outputScope.apkDatas
task.outputFile = scope.artifacts.appendArtifact(
InternalArtifactType.APK_LIST,
task,
SdkConstants.FN_APK_LIST)
}
}
}
可以看到关键代码其实是在第二部操作中,我们看看内部如何实现的,
//ExistingBuildElements.kt
@JvmStatic
fun persistApkList(apkInfos: Collection<ApkInfo>): String {
val gsonBuilder = GsonBuilder()
gsonBuilder.registerTypeHierarchyAdapter(ApkInfo::class.java, ApkInfoAdapter())
val gson = gsonBuilder.create()
return gson.toJson(apkInfos)
}
//ApkInfoAdapter.kt
override fun write(out: JsonWriter, value: ApkInfo?) {
if (value == null) {
out.nullValue()
return
}
out.beginObject()
out.name("type").value(value.type.toString())
out.name("splits").beginArray()
for (filter in value.filters) {
out.beginObject()
out.name("filterType").value(filter.filterType)
out.name("value").value(filter.identifier)
out.endObject()
}
out.endArray()
out.name("versionCode").value(value.versionCode.toLong())
if (value.versionName != null) {
out.name("versionName").value(value.versionName)
}
out.name("enabled").value(value.isEnabled)
if (value.filterName != null) {
out.name("filterName").value(value.filterName)
}
if (value.outputFileName != null) {
out.name("outputFile").value(value.outputFileName)
}
out.name("fullName").value(value.fullName)
out.name("baseName").value(value.baseName)
out.endObject()
}
json字符串的构造其实是依赖于goole自定义的ApkInfoAdapter适配器类来实现的,具体不说啦。
今天就到这吧。。。