赞
踩
逐步整理的一系列的总结:
Android 自定义Gradle插件的Extension类(五)
Android Gradle 中的使用ASMified插件生成.class的技巧(九)
Android Gradle 中的实例之动态修改AndroidManifest文件(十)
前几天想把项目升级到Android12,在适配Android12的时候有一条适配为:如果在AndroidManifest.xml文件中注册Activity、Service、BroadcastReceiver的时候,如果使用了intent-filters修饰,那么就必须为该组件显示的声明android:exported属性,用来标记给组件是否支持其他应用调用,否则在编译阶段就会抛出以下异常:
- Execution failed for task ':app:processHuaweiDebugMainManifest'.
- > Manifest merger failed : Apps targeting Android 12 and higher are required to specify an explicit
- value for `android:exported` when the corresponding component has an intent filter defined.
- See https://developer.android.com/guide/topics/manifest/activity-element#exported for details.
对于APP本身的AndroidManifest.xml文件来说,可以根据实际的需求(即是否支持其他应用调用)来声明android:exported属性,但是如果第三方的SDK的AndroidManifest.xml文件中同样含有上述条件,并且还没有做适配的时候,如果将该项目的targetSdkVersion升级到31的时候,该项目会一直无法编译通过,因为所有的依赖包的AndroidManifest.xml都会被打包到一起。
问题就可以通过Android Gradle Plugin对AndroidManifest.xml文件中动态添加android:exported属性。
同样,像某些第三方的SDK的AndroidManifest.xml文件中含有一些节点是我们不需要或者某些权限需要剔除的,都可以用这种方式来解决。
这里主要是一些思路梳理过程。如果直接想结论性的内容,欢迎移步到个人公众号。自己会更加揣摩一句话该怎么写,怎么能把这个知识点简练的总结出来。
在Android Gradle 中的Transform(六)的一 APP打,包流程 已经介绍过APK的一个流程:
Android Studio通过Gradle就是通过一系列的Task来完成整个构建过程。 从Build输出窗口的打印的Task可以看出,在使用Android Studio运行一个APP从编译到打包成一个apk,会输出如下的一些Task:
> Task :app:preBuild > Task :app:preDebugBuild > Task :app:compileDebugAidl NO-SOURCE > Task :app:compileDebugRenderscript NO-SOURCE > Task :app:generateDebugBuildConfig > Task :app:javaPreCompileDebug > Task :app:generateDebugResValues > Task :app:generateDebugResources > Task :app:checkDebugAarMetadata > Task :app:createDebugCompatibleScreenManifests > Task :app:extractDeepLinksDebug > Task :app:processDebugMainManifest > Task :app:processDebugManifest > Task :app:mergeDebugNativeDebugMetadata NO-SOURCE > Task :app:mergeDebugShaders > Task :app:compileDebugShaders NO-SOURCE > Task :app:generateDebugAssets UP-TO-DATE > Task :app:mergeDebugAssets > Task :app:compressDebugAssets > Task :app:processDebugJavaRes NO-SOURCE > Task :app:checkDebugDuplicateClasses > Task :app:desugarDebugFileDependencies > Task :app:mergeDebugJavaResource > Task :app:mergeDebugResources > Task :app:mergeDebugJniLibFolders > Task :app:mergeLibDexDebug > Task :app:validateSigningDebug > Task :app:writeDebugAppMetadata > Task :app:writeDebugSigningConfigVersions > Task :app:processDebugManifestForPackage > Task :app:mergeDebugNativeLibs > Task :app:stripDebugDebugSymbols NO-SOURCE > Task :app:mergeExtDexDebug > Task :app:processDebugResources > Task :app:compileDebugJavaWithJavac > Task :app:compileDebugSources > Task :app:dexBuilderDebug > Task :app:mergeProjectDexDebug > Task :app:packageDebug > Task :app:assembleDebug
主要了解一些Task的作用,方便在整个过程中选择适合的锚点来添加自定义的Task,从而实现自定义行为。对应的实现类位于gradle-core/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/tasks/目录下。
空task,仅做锚点使用,两者区别在于preDebugBuild针对变体的锚点,如设置了productFlavors则preDebugBuild就会变成preHuaweiDebugBuild,同样除去preBuild,其他的Task也都会增加该变体标识。对应实现类为AppPreBuildTask.java。
productFlavors相关内容可参见Android Gradle中的productFlavors。
处理.aidl文件,将.aidl文件转换成java interface文件。若没有.aidl文件则会如上显示NO-SOURCE。对应实现类为AidlCompile.java。
处理renderscript。若没有对应的文件,同样会显示NO-SOURCE。对应实现类为RenderscriptCompile.java。
RenderScript 是 Android 3.0 提出的一个高效的计算框架,能够自动的将计算任务分配给CPU、GPU、DSP等,为处理图片、数学模型计算等场景提供高效的计算能力。
语法类似 C/C++, 但它是在运行时编译,是跨平台的。性能比 Java 好,比 Native 略差。
在使用的时候分两步:(1)编写.rs文件;(2)使用 RenderScript。
生成BuildConfig.java文件,里面会包含DEBUG、APPLICATION_ID、FLAVOR、VERSION_CODE、VERSION_NAME以及自定义的属性,如图:
对应实现类为GenerateBuildConfig.java。
生成annotaionProcessors.json文件。对应实现类为JavaPreCompileTask.java。
生成revalues、generated.xml。对应实现类为GenerateResValues.java。
空task,仅做锚点。
Manifest文件中生成compatible-screens,指定屏幕适配。对应的实现类CompatibleScreenManifests.java。
合并所有的Manifest文件(包含各个依赖包的AndroidManifest.xml)(一点思考:可在该Task执行之前,对AndroidManifest.xml进行预处理:例如实现在合并AndroidManifest.xml之前,为符合条件的组件添加Android12的android:exported属性),然后替换掉AndroidManifest.xml文件中的某些在主module中在build.gradle中定义的placeholders或者属性(如package、version_code)等,生成最终的AndroidManifest.xml文件(一点思考:可以处理一些与变体无关的信息:例如可以在该Task执行之后,对最终对AndroidManifest.xml文件进行二次处理:如修改每次上线的versionCode和versionName)保存在app/build/intermediates/merged_manifest,如图:
对应的实现类ProcessApplicationManifest.java
使用合并的AndroidManifest.xml文件,为所有的变体创建AndroidManifest.xml文件(一点思考:处理与变体相关的信息,可以在执行Task之后,对AndroidManifest.xml文件进行二次处理),最终保存在app/build/intermediates/merged_manifest,如图:
对应实现类ProcessMultiApkApplicationManifest.java。
编译shaders,对应实现类ShaderCompile.java。
合并assets文件。对应的实现类MergeSourceSetFolders.java。
处理java res 。若没有对应的文件,同样会显示NO-SOURCE 。对应实现类为ProcessJavaresConfigAction.java。
合并资源文件(一点思考:可以在该Task之前对合并之前的资源文件进行预处理:例如在合并资源文件的时会进行去重操作,即相同的资源ID的资源文件会被认为是同一个,那么就改变该逻辑。),包括各个依赖包中的资源文件,最终调用aapt2命令去处理资源文件,生成“原资源文件名.xml.flat”格式的文件保存在app/build/intermediates/res目录下,如图:
对应实现类MergeResources.java。
合并jni lib文件,对应实现类MergeSourceSetFolder.java。
验证签名,对应实现类为ValidateSigningTask.java。
aapt打包资源,生成最终的R.java(一点思考:可以在该执行该task之后对R文件进行二次处理)以及res.ap_,最终将文件保存app/build/intermediates/processed_res目录下:
对应实现类为ProcessAndroidResouces.java。
在使用aapt打包res资源文件,res资源文件又会分为二进制文件和非二进制文件,典型的非二进制文件如res/raw、图片,要求保持原样,不被编译。最终这些资源文件被打包成R.java、resources.arsc和res文件
编译java文件,对应的实现类为AndroidJavaCompile.java。
空task,仅做锚点。
打包apk,对应实现类PackageApplication
空task,仅做锚点。
遗留问题:其他的Task可在后面陆续去研究。这次需要使用的仅用到上面红色标记的processDebugMainManifest和processDebugManifest。
在前言提到的Android12如果没有对android:exported属性进行适配,那么执行到processHuaweiDebugMainManifest这个任务的时候就会抛出编译错误。所以借助这个应用场景,通过自定义Android Gradle plugin的方式来在processHuaweiDebugMainManifest这个任务执行之前,完成对第三方的SDK的Android12该属性的适配。
针对上面提出的问题,整理下代码逻辑:
有了上面的思路,开始逐步实现。
怎么创建一个Android Gradle Plugin可参见 Android Gradle插件开发初次交手(一),这里不在多余介绍。最终创建插件的源代码位于manifestplugin这个module下,将该插件应用到项目中。
这个可以直接继承已有的API就可以实现一个自定义的Task,具体内容可参见Android Gradle的基本概念梳理(二)这里仅简单的在总结一下有三种已有的API:
而实现 AddExportForEveryPackageManifestTask仅仅来继承DefaultTask即可,代码如下:
- class AddExportForEveryPackageManifestTask extends DefaultTask {
-
- @TaskAction
- void run() {
- //处理所有包下的AndroidManifest文件添加android:exported
- SystemPrint.outPrintln("Running .....")
- }
- }
有了这个自定义的Task,先将Task加入到APP的打包编译的任务队列中,然后在逐步添加AddExportForEveryPackageManifestTask里面的逻辑代码。
在找到processDebugMainManifest Task,自己走了一点弯路。因为没有搞清楚processDebugMainManifest和processDebugManifest区别,导致一开始找的是processDebugManifest这个,最后在运行最后的结果的时候,发现是还是会抛出前言中的编译异常,后来经过几次调整在最终确定依赖该Task,代码如下:
- class ManifestProject implements Plugin<Project> {
- List variantNames = new ArrayList()
-
- @Override
- void apply(Project project) {
- SystemPrint.outPrintln("Welcome ManifestProject")
- getAllVariantManifestTask(project)
- addExportTaskForEveryPackageManifest(project)
- }
- /**
- * 获取所有的变体相关的process%sManifest任务名称
- * // processDebugManifest:生成最终 AndroidManifest 文件
- * @param project
- */
- void getAllVariantManifestTask(Project project) {
- project.extensions.findByType(AppExtension.class)
- .variantFilter {
- variantNames.add(it.name)
- }
- }
- /**
- * 为所有依赖的包的AndroidManifest添加android:exported
- * @param project
- */
- void addExportTaskForEveryPackageManifest(Project project) {
- AddExportForEveryPackageManifestTask beforeAddTask = project.getTasks().create(AddExportForEveryPackageManifestTask.TAG,
- AddExportForEveryPackageManifestTask)
- //在项目配置完成后,添加自定义Task
- project.afterEvaluate {
- //直接通过task的名字找到ProcessApplicationManifest这个task
- variantNames.each {
- //找到processHuaweiDebugMainManifest,在这个之前添加export
- ProcessApplicationManifest processManifestTask = project.getTasks().getByName(String.format("process%sMainManifest", it.capitalize()))
- beforeAddTask.setManifestsFileCollection(processManifestTask.getManifests())
- beforeAddTask.setMainManifestFile(processManifestTask.getMainManifest().get())
- processManifestTask.dependsOn(beforeAddTask)
- }
- }
- }
- }

对上面的代码总结几个自己遇到的问题:
因为项目中配置了变体(相关内容可参见Android Gradle中的productFlavors),所以所有的Task都会添加变体的信息,如processDebugMainManifest将会变为processHuaweiDebugMainManifest,其中HuaweiDebug为其中一个变体。那么在找processDebugMainManifest这个锚点的时候,就会变成了找到process${variant}MainManifest,而变体信息是在主module在apply该插件的时候,就会输出的信息,而我添加自定义的Task需要在项目配置完成添加。
解决方案:在apply()的时候将所有的变体信息保存到variantNames集合中,然后在项目配置完成的时候,通过variantNames.each {}的方式找到每个变体的process${variant}MainManifest Task。
因为要从这个Task中得到所有依赖包以及主Module的AndroidMainfest.xml文件,所以就需要知道processDebugMainManifest这个Task对应的实现类才能知道哪些方法可以获取到这些信息。但是Android Gradle都是在摸索学习中,所以一下子无法得到processDebugMainManifest的实现类,然后也没有搜到相关的解决方案。
解决方案:但是发现了一个找到这个processDebugMainManifest的便捷方法:随便让project.getTasks().getByName()等于任意类型的一个类,编译发布插件,然后通过Android Studio编译整个项目,发现就会抛出以下异常:
- Cannot cast object 'task ':app:processHuaweiDebugMainManifest''
- with class 'com.android.build.gradle.tasks.ProcessApplicationManifest_Decorated'
- to class 'java.lang.Process'
从提示中可以看到在该任务中将ProcessApplicationManifest类型转换成Process类型,然后就得到了processDebugMainManifest的实现类。
现在可以通过dependsOn将 AddExportForEveryPackageManifestTask添加到processDebugMainManifest之前,那如果要添加到一个已有的任务队列之后呢?
解决方案:对比几个常用的设置Task执行顺序的方法,其中it为processDebugManifest这个Task,task为自定义的Task
1)it.dependsOn(task):将自定义task添加到任务队列中。执行顺序为:先执行task,然后在去执行it这个task;
2)it.finalizedBy(task):将自定义的task添加的任务队列中。执行顺序为:先执行it,在执行完it之后自动去执行task。
而mustRunAfter和shouldRunAfter两个同样是可以定义两个task的执行的先后顺序,但并不会将该task添加到整个任务队列中。
3)task1.mustRunAfter task2 task1.shouldRunAfter task2:执行顺序为:先执行task2,在执行task1。两者都不会将task添加到任务队列中,区别在于mustRunAfter为必须遵守该顺序,而shouldRunAfter为非必须。通常用于几个task同时依赖一个task的时候,设置这几个task的执行顺序。例如it.dependsOn(task1,task2),那么就可以使用mustRunAfter或shouldRunAfter 来设置task1和task2的执行顺序。
经过上面的三步之后,然后将该插件编译发布,通过Android Studio就可以看到在执行processDebugMainManifest这个Task之前,已经执行先执行了AddExportForEveryPackageManifestTask,如下:
- > Task :app:generateHuaweiDebugResources UP-TO-DATE
- > Task :app:mergeHuaweiDebugResources UP-TO-DATE
- > Task :app:createHuaweiDebugCompatibleScreenManifests UP-TO-DATE
-
- > Task :app:AddExportForEveryPackageManifestTask
- #@@#@@# ManifestProject #@@#@@# Running .....
- > Task :app:extractDeepLinksHuaweiDebug UP-TO-DATE
- > Task :app:processHuaweiDebugMainManifest
- > Task :app:processHuaweiDebugManifest
- > Task :app:mergeHuaweiDebugNativeDebugMetadata NO-SOURCE
- > Task :app:mergeHuaweiDebugShaders
已经有了这个插件的框架,现在就是填充AddExportForEveryPackageManifestTask逻辑了。
得到依赖包以及主module的AndroidManifest.xml文件,所以在Project中初始化AddExportForEveryPackageManifestTask的时候,从processDebugMainManifest取出来赋值到相应的方法,代码如下:
- //获取所有依赖包的manifest文件
- beforeAddTask.setManifestsFileCollection(processManifestTask.getManifests())
- //获取主module的manifest文件
- beforeAddTask.setMainManifestFile(processManifestTask.getMainManifest().get())
小技巧: 由于对Android Gradle Plugin的源码并不是很清楚,但是看到方法名以及@InputFiles猜测这两个可能就是想要的方法,试了试果然可以。
在 AddExportForEveryPackageManifestTask中的代码就是两个set方法,代码如下:
-
- /**
- * 设置所有的 需要合并的Manifest文件
- * @param collection
- */
- void setManifestsFileCollection(FileCollection collection) {
- manifestCollection = collection
- }
-
- /**
- *
- * @param file
- */
- void setMainManifestFile(File file){
- mainManifestFile = file
- }

使用groovy提供的XmlParser来解析AndroidManifest.xml文件,具体的代码可参见AddExportForEveryPackageManifestTask.groovy文件中的handlerVariantManifestFile()方法里面的内容,这里仅针对语法上面总结几点:
点1:通过XmlParser解析的xml获得的文件内容是一个Node
代码如下:
- XmlParser xmlParser = new XmlParser()
- def node = xmlParser.parse(manifestFile)
一个Node与AndroidManifest.xml文件的对比关系如下:
点2:可以通过node.attributes()来获取当前节点的所有属性,当然也可以通过node.attributes().get("package")来获取特定的属性值;
其中<>除去标签名之前的xxx=xxx为该节点的属性 。
点3:可以通过node.children()来获取到当前节点的所有子节点,当然也可以通过node.子节点名字的方式来获取特定的子节点;
其中每个<></>来表示一个节点。
点4:在each{}中不可以调用private声明的变量或方法;
点5:在使用each{}循环的时候,return true相当于continue;在使用find{}循环的时候,return true相当于break;
具体的代码可参见AddExportForEveryPackageManifestTask.groovy文件中的见hasAttributeExported()和hasIntentFilter()
具体的代码可参见AddExportForEveryPackageManifestTask.groovy文件中的handlerAddExportForNode(),这里仅总结自己遇到的几个坑:
点1:可通过node.attributes().put()为该node添加新的属性
因为有android:exported=true属性的该node在输出的时候,如下:
- activity[attributes={{http://schemas.android.com/apk/res/android}name=.TestActivity,
- {http://schemas.android.com/apk/res/android}exported=true};
- value=[intent-filter[attributes={};
- value=[action[attributes={{http://schemas.android.com/apk/res/android}name=android.intent.action.SEARCH};
- value=[]], category[attributes={{http://schemas.android.com/apk/res/android}name=android.intent.category.DEFAULT};
- value=[]]]]]]
在循环遍历node.attributes() 时,输出的key也为 {http://schemas.android.com/apk/res/android}exported,所以一开始在添加android:exported=true属性的时候使用的是:
node.attributes().put({http://schemas.android.com/apk/res/android}exported, true)
在编译的时候抛出以下异常:
'元素类型 "activity" 必须后跟属性规范 ">" 或 "/>"。'
后来尝试直接使用:
node.attributes().put("android:exported", true)
没想到竟然成功了,同时生成的 AndroidManifest.xml文件也是正确的。遗留问题:暂时不知道原因是什么。
点2: 可通过node.attributes().get()来修改属性值
像不带android:的其他属性例如package等信息,可以直接通过node.attributes().get("package")获取到对应的属性值,但是对于带有android:的,例如android:name,不管通过node.attributes().get("android:name")还是node.attributes().get("{http://schemas.android.com/apk/res/android}name")都获取不到属性值,但是可以通过下面的代码获取到:
- attrs.find{
- if("http://schemas.android.com/apk/res/android}name".equals(it.key.toString())) {
- String name = attrs.get(it.key)
- //find return true相当于break
- return true
- }
- }
猜测:这个可能跟Map的containsKey()逻辑有关系,因为传入的"{http://schemas.android.com/apk/res/android}name"已经不是之前加入到该Map集合中的key,所以就不会匹配到value值。
具体逻辑还是见AddExportForEveryPackageManifestTask.groovy文件中的handlerVariantManifestFile(),这里仅简单罗列,代码如下:
- //第四步:保存到原AndroidManifest文件中
- String result = XmlUtil.serialize(node)
- manifestFile.write(result, "utf-8")
仍然将文件保存到之前传入的AndroidManifest.xml文件中,像前面提到的'元素类型 "activity" 必须后跟属性规范 ">" 或 "/>"。'异常也是在 XmlUtil.serialize()中抛出来的。 具体的代码已经上传到至GitHub - wenjing-bonnie/AndroidPlugin: 用来学习Android Gradle Plugin。因为逐渐在这基础上进行迭代,可回退到Gradle_10.0.1该tag下可以查看相关内容,也可直接查看manifestplugin目录下的相关内容(在实例三中有对该部分代码做优化,所以如果不回退的话,会看到一些定义和这里描述的有出入,但是大的逻辑是没有动的)。
将插件发布,将主module的compileSdkVersion和targetSdkVersion改为31,添加一个没有适配Android12的第三方插件,如Mobpush(具体可参见官方文档:MobTech集成文档-MobTech),未添加该插件,运行项目回抛出编译异常:
- > Task :app:processHuaweiDebugMainManifest FAILED
- /Users/j1/Documents/android/code/studio/AndroidPlugin/app/src/main/AndroidManifest.xml Error:
- Apps targeting Android 12 and higher are required to specify an explicit value for `android:exported` when the corresponding component has an intent filter defined. See https://developer.android.com/guide/topics/manifest/activity-element#exported for details.
- /Users/j1/Documents/android/code/studio/AndroidPlugin/app/src/main/AndroidManifest.xml Error:
- Apps targeting Android 12 and higher are required to specify an explicit value for `android:exported` when the corresponding component has an intent filter defined. See https://developer.android.com/guide/topics/manifest/activity-element#exported for details.
- /Users/j1/Documents/android/code/studio/AndroidPlugin/app/src/main/AndroidManifest.xml Error:
- Apps targeting Android 12 and higher are required to specify an explicit value for `android:exported` when the corresponding component has an intent filter defined. See https://developer.android.com/guide/topics/manifest/activity-element#exported for details.
- /Users/j1/Documents/android/code/studio/AndroidPlugin/app/src/main/AndroidManifest.xml Error:
- Apps targeting Android 12 and higher are required to specify an explicit value for `android:exported` when the corresponding component has an intent filter defined. See https://developer.android.com/guide/topics/manifest/activity-element#exported for details.
-
- See http://g.co/androidstudio/manifest-merger for more information about the manifest merger.
添加该插件之后,通过Android Studio可以成功运行安装APP,并且使用Build->Analyzer查看生成的apk的AndroidManifest.xml文件发现已经添加了android:exported=true的属性,如图:
在实现该自定义Android Gradle Plugin的过程,其实第一个关键点就是要找到合适的锚点来添加自定义的Task行为;第二个关键点就是自定义Task行为的实现。通过这个过程对自定义Android Gradle Plugin越发感兴趣。
前一个是对合并所有的AndroidManifest.xml文件之前,对项目中所有用到的依赖包的AndroidManifest.xml文件进行修改,然后在看一个对最后合并之后的AndroidManifest.xml文件进行修改内容的实例。
实例背景:在项目目录下会有一个version.xml文件,用来记录每次上线的内容以及最新一次的版本等信息,如下:
- <?xml version="1.0" encoding="utf-8" ?>
- <versions>
- <version latest="true">
- <versionDescription>新增购物车</versionDescription>
- <versionCode>12</versionCode>
- <versionName>2.0.0</versionName>
- <date>2021/09/16</date>
- </version>
- <version>
- <versionDescription>APP第一版本上线</versionDescription>
- <versionCode>12</versionCode>
- <versionName>1.0.0</versionName>
- <date>2021/09/15</date>
- </version>
- </versions>
这样就可以不需要每次手动修改AndroidManifest.xml ,可以清楚的记录每次升级的内容,比在AndroidManifest.xml文件或者build.gradle文件中进行记录更清晰明了。
针对上述的的实例,整理下代码逻辑:
有了上面的思路,开始逐步实现代码。因为在二 实例1:适配Android12的android:exported属性已经有了该插件的框架,现在只需要自定义SetLatestVersionForMergedManifestTask和将该SetLatestVersionForMergedManifestTask加入到任务队列即可。
同样的方式继承DefaultTask,通过@TaskAction添加该任务的action,代码如下:
- class SetLatestVersionForMergedManifestTask extends DefaultTask {
- @TaskAction
- void doTaskAction() {
- }
- }
processDebugManifest 该任务是根据之前的合并所有依赖包的AndroidManifest.xml的任务processDebugMainManifest得到的合并后的AndroidManifest.xml来得到不同变体的AndroidManifest.xml文件。所以在该任务之后,对AndroidManifest.xml的versionCode和versionName进行修改,才是最终被打包到apk中。
因为在项目构建完成之后,会将所有变体相关的Task全都塞到project.getTasks()集合中,而在Android Studio在通过菜单栏的Build或者Run编译打包apk,每次只执行其中一个变体的任务集合,也就是说我们在通过Build->Make Project时候,仅仅执行的是在变体集合中活跃的那个变体,如图:
详细的内容见Android Gradle中的productFlavors
因为设置了productFlavors{},所以processDebugManifest就会变成了processHuaweiDebugManifest,那么在前面的二 实例1:适配Android12的android:exported属性中的时候为了找到这个任务,将项目中的所有变体集合中循环每一个变体,都来执行下自定义的Task,但是在实际过程中,其实完全没有必要这种。只需要找到这个活跃的变体,就可以直接找到该变体下的任务集合,然后在对应的集合中添加自定义任务即可。所以针对在(3)找到锚点processDebugMainManifest Task,添加AddExportForEveryPackageManifestTask到任务队列这种添加自定义任务的方式做了优化:
在执行Build的时候,其中`project.gradle.getStartParameter().getTaskRequests()`返回的信息为:
[DefaultTaskExecutionRequest{args=[:wjplugin:assemble, :wjplugin:testClasses, :manifestplugin:assemble, :manifestplugin:testClasses, :firstplugin:assemble, :firstplugin:testClasses, :app:assembleHuaweiDebug],projectPath='null'}]
从该字符串中我们可以看到其中含有当前变体的信息 “HuaweiDebug”,我们只要截取出该字符串就可以得到当前变体的名字,代码如下:
- void getVariantNameInBuild(Project project) {
- String parameter = project.gradle.getStartParameter().getTaskRequests().toString()
- //assemble(\w+)(Release|Debug)仅提取Huawei
- String regex = parameter.contains("assemble") ? "assemble(\\w+)" : "generate(\\w+)"
- Pattern pattern = Pattern.compile(regex)
- Matcher matcher = pattern.matcher(parameter)
- if (matcher.find()) {
- //group(0)就是指的整个串,group(1) 指的是第一个括号里的东西,group(2)指的第二个括号里的东西
- variantName = matcher.group(1)
- }
- }
但是执行sync的时候, project.gradle.getStartParameter().getTaskRequests()返回的信息为: [DefaultTaskExecutionRequest{args=[],projectPath='null'}]
显然已经无法获取到当前变体名称。那么其实思考一下这个sync,这个是一个同步的过程,我们既可以在sync的时候,对该插件不做任务处理,也可以借助project.extensions.findByType(AppExtension.class).variantFilter{}返回所有的变体信息中抽取任意一个来完成sync过程而已(PS:因为所有的变体相关的Task都会加入到project.getTasks()集合中,所以这里仅仅是为了让sync能够执行成功,在build的时候,又会重新从project.gradle.getStartParameter().getTaskRequests()中返回变体信息)。
当然不仅仅限于sync,应该还有其他在执行project.gradle.getStartParameter().getTaskRequests()返回无变体信息的内容。
那么就有了两种实现方案:
方案一:从所有变体集合中,抽取任意一个变体信息
在getVariantNameInBuild()中通过matcher匹配到对应的字符串之后,增加一个检验该字符串有效性的处理,如果是一个无效的字符串,则从所有的变体信息中,抽取一个变体的名字设置为当前变体的名称,代码如下:
- void getVariantNameInBuild(Project project) {
- String parameter = project.gradle.getStartParameter().getTaskRequests().toString()
- //assemble(\w+)(Release|Debug)仅提取Huawei
- String regex = parameter.contains("assemble") ? "assemble(\\w+)" : "generate(\\w+)"
- Pattern pattern = Pattern.compile(regex)
- Matcher matcher = pattern.matcher(parameter)
- if (matcher.find()) {
- //group(0)就是指的整个串,group(1) 指的是第一个括号里的东西,group(2)指的第二个括号里的东西
- variantName = matcher.group(1)
- }
- //但是sync时返回的内容:[DefaultTaskExecutionRequest{args=[],projectPath='null'}].
- //所以此时走注释中的(2),实现"则直接但是最理想的解决方案是该在sync的时候,可以不执行该插件"这种方案,则直接隐藏下面的代码
- if (!isValidVariantName()) {
- //从AppExtension中获取所有变体,作为获取当前变体的备用方案
- getValidVariantNameFromAllVariant(project)
- }
- }
-
- /**
- * 获取所有的变体中的一个可用的变体名,仅仅用来保证sync任务可执行而已
- * project.extensions.findByType()有执行时机,所以会出现在getVariantNameInBuild()中直接调用getVariantNameFromAllVariant()将无法更新variantName
- *
- * @param project
- */
- void getValidVariantNameFromAllVariant(Project project) {
- if (isValidVariantName()) {
- return
- }
- //但是sync时返回的内容:[DefaultTaskExecutionRequest{args=[],projectPath='null'}],其实该过程可以不执行该插件也可以
- //直接从所有的变体中取一个可用的变体名,返回
- //
- project.extensions.findByType(AppExtension.class).variantFilter {
- variantName = it.name.capitalize()
- SystemPrint.outPrintln(String.format("Fake variant name from all variant is \" %s \"", variantName))
- if (isValidVariantName()) {
- return true
- }
- }
- }
-
- boolean isValidVariantName() {
- variantName != null && variantName.length() > 0
- }

该方案仅仅为了sync能够执行成功而已,没有具体实际意义。
方案二:sync的时候,该插件不做任务处理
即在apply()的时候,做一个检验是不是一个有效的variantName,如果不是一个有效的字符串,那么就直接不在执行添加自定义Task的代码,代码如下:
-
- @Override
- void apply(Project project) {
- //创建ManifestExtension
- createManifestExtension(project)
- //在sync中无法获取到variantName
- getVariantNameInBuild(project)
- SystemPrint.outPrintln(String.format("Welcome %s ManifestProject", variantName))
- //如果不是一个有效的variant,则直接返回
- if (!isValidVariantName()) {
- return
- }
- addTaskForVariantAfterEvaluate(project)
- }
另外在获取 variantName的方法中去掉从所有的变体集合中找一个可用的变体名,代码如下:
- void getVariantNameInBuild(Project project) {
- String parameter = project.gradle.getStartParameter().getTaskRequests().toString()
- //assemble(\w+)(Release|Debug)仅提取Huawei
- String regex = parameter.contains("assemble") ? "assemble(\\w+)" : "generate(\\w+)"
- Pattern pattern = Pattern.compile(regex)
- Matcher matcher = pattern.matcher(parameter)
- if (matcher.find()) {
- //group(0)就是指的整个串,group(1) 指的是第一个括号里的东西,group(2)指的第二个括号里的东西
- variantName = matcher.group(1)
- }
- }
该方案应该更符合实际意义,本来在sync的时候,该插件完全可以不作任何处理。
同样也是在项目配置完成之后,将SetLatestVersionForMergedManifestTask添加到processDebugManifest后执行,代码如下:
-
- void addVersionTaskForMergedManifest(Project project, SetLatestVersionForMergedManifestTask versionTask) {
- //在项目配置完成后,添加自定义Task
- //方案一:直接通过task的名字找到ProcessMultiApkApplicationManifest这个task
- //直接找到ProcessDebugManifest,然后在执行后之后执行该Task
- ProcessMultiApkApplicationManifest processManifestTask = project.getTasks().getByName(String.format("process%sManifest", variantName))
- versionTask.setManifestFile(processManifestTask.getMainMergedManifest().asFile.get())
- processManifestTask.finalizedBy(versionTask)
- }
这样就完成了将SetLatestVersionForMergedManifestTask添加到整个APP的编译打包的任务队列中。
在前面添加SetLatestVersionForMergedManifestTask任务的时候已经将processDebugManifest中的变体的AndroidManifest.xml文件通过setManifestFile()方法传入到该自定义Task中
该实现方法就是要自定义扩展属性类,然后添加到Project中。
自定义扩展属性类,代码如下:
- class ManifestExtension {
- protected static final String TAG = "ManifestPlugin"
- private File versionFile
-
- protected void setVersionFile(File file) {
- this.versionFile = file
- }
-
- protected File getVersionFile() {
- return versionFile
- }
-
- }
在Project中添加该扩展属性,代码如下:
- @Override
- void apply(Project project) {
- //创建ManifestExtension
- createManifestExtension(project)
- .....
- }
-
- /**
- * 配置扩展属性
- * @param project
- */
- void createManifestExtension(Project project) {
- project.getExtensions().create(ManifestExtension.TAG, ManifestExtension)
- }
在主module中的build.gradle中使用该扩展属性,代码如下:
- /**'com.wj.plugin.manifest'*/
- ManifestPlugin {
- versionFile = file("version.xml")
- }
这部分内容就是通过XmlParse来读写xml文件,不在多余写代码。
具体的代码已经上传到至GitHub - wenjing-bonnie/AndroidPlugin: 用来学习Android Gradle Plugin。因为逐渐在这基础上进行迭代,可回退到Gradle_10.0.2该tag下可以查看相关内容,也可直接查看manifestplugin目录下的相关内容。
相比较于实例一,优化了找锚点的方法,通过当前活跃的变体名,找到对应的锚点任务。
经过两个实例,对Android Gradle 自定义插件的有了更深的理解
- 1.在自定义Gradle插件的几个要点:
- (1)找到合适的锚点任务;
- (2)将自定义Task通过dependsOn或finalizedBy添加到锚点任务的之前或之后执行,并且还是必须要通过这两个方法添加到任务队列中;
- (3)通过扩展属性添加输入参数;
- 2.几种自定义Task的方式
- (1)继承DefaultTask,可以将该自定义的Task添加到已有的任务队列
- (2)继承Transform,会自动添加到任务队列中,并且添加到.class文件被打包成.dex文件之前,用于进行字节码
- 3.processDebugMainManifest会将所有的依赖包的AndroidManifest.xml文件合并成一个AndroidManifest.xml文件,可以通过该processDebugMainManifest作为锚点解决第三方SDK中未适配Android 12 抛出的“...value for `android:exported`..”编译错误;当然也可以解决去除第三方SDK某些敏感权限的问题;
- 4.processDebugManifest会为所有的变体生成最终的AndroidManifest.xml文件文件,可以通过processDebugManifest作为锚点来解决修改最终AndroidManifest.xml文件的问题;
- 5.当前Android Gradle中的其他的Task可以根据实际的功能来解决一些对应的文件处理
- 6.project.getTasks()返回的是所有变体的Task的集合,而在实际Android Studio编译打包过程中,只有一个活跃的变体,所以可以获取当前活跃变体的信息来找到对应变体的Task;
- 7.在找Android Gradle的task的实现类的时候,可以通过随便将project.getTasks().getByName()返回值返回给一个类型的变量,那么最后在编译的时候,抛出的异常中就会提示对应的类型;(这个是这次最大的收获!!!我都佩服我自己能发现这么一个便捷的方式。)
- 8.XmlParse可以用来读写xml文件,可以通过node.一级标签名.二级标签名的方式获取到对应的节点
遗留问题:1.怎么通过NewIncrementalTask 来实现一个增量Task;2.怎么使用@InputFile等注解来添加输入参数
加油!好玩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。