Android构建流程——篇三
文章目录
- Task5 checkDebugManifest
- 1. input/ouput
- 2. 核心类(CheckManifest)
- Task6 generateDebugBuildConfig
- 1. input/output
- 2. 核心类(GenerateBuildConfig)
- Task7 prepareLintJar
- 1. input/ouput
- 2. 核心类(PrepareLintJar)
- Task8 mainApkListPersistenceDebug
- 1. input/output
- 2. 核心类(MainApkListPersistence)
Task5 checkDebugManifest
1. input/ouput
taskName:checkDebugManifest=========================================================output:/Users/apple/work/project/AndroidGradleTaskDemo/app/build/intermediates/check-manifest/debug
2. 核心类(CheckManifest)
@TaskActionvoid 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())); }}
该任务是校验操作,就是校验清单文件的合法性,不合法则中断任务,抛出异常
配置阶段操作
@Overridepublic 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
Task6 generateDebugBuildConfig
BuildConfig.java想必大家非常熟悉,该任务就是生成它
1. input/output
taskName:generateDebugBuildConfig=========================================================output:/Users/apple/work/project/AndroidGradleTaskDemo/app/build/generated/source/buildConfig/debug========================END==============================
2. 核心类(GenerateBuildConfig)
@TaskActionvoid 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.javapublic 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(); }}
Task7 prepareLintJar
1. input/ouput
taskName:prepareLintJar=========================================================output:/Users/apple/work/project/AndroidGradleTaskDemo/app/build/intermediates/lint_jar/global/prepareLintJar/lint.jar
2. 核心类(PrepareLintJar)
/** * 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 {...@TaskActionpublic 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@Overridepublic 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
Task8 mainApkListPersistenceDebug
1. input/output
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文件,看下核心入口代码
2. 核心类(MainApkListPersistence)
这个类非常简短,我直接全部贴出来吧,有意思的是它是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@JvmStaticfun persistApkList(apkInfos: Collection<ApkInfo>): String { val gsonBuilder = GsonBuilder() gsonBuilder.registerTypeHierarchyAdapter(ApkInfo::class.java, ApkInfoAdapter()) val gson = gsonBuilder.create() return gson.toJson(apkInfos)}//ApkInfoAdapter.ktoverride 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适配器类来实现的,具体不说啦。
今天就到这吧。。。
更多相关文章
- Android调用系统自带的文件管理器,打开指定路径
- Gallery中重复文件夹的问题
- android sqlite3 数据库升级,加字段
- Android Studio Gradle 缓存文件夹设置
- Android内核驱动开发中的Kconfig文件结构分析(图文)
- Android震动和播放资源文件中的声音文件
- Android文件管理器开发对各类文件的打开以及处理