Android Gradle 中的实例之动态修改AndroidManifest文件
Posted 好人静
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Gradle 中的实例之动态修改AndroidManifest文件相关的知识,希望对你有一定的参考价值。
前言
逐步整理的一系列的总结:
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 Plugin的Task
在Android Gradle 中的Transform(六)的一 APP打,包流程 已经介绍过APK的一个流程:
- 1.aidl文件经过aidl编译成Java Interface文件,R文件以及java源代码经过Java Compiler编译成.class文件;
- 2..class文件和第三方库的.class文件经过dex转换成Android虚拟机可以识别的.dex文件;
- 3..dex文件和一些Application resouces经过aapt编译之后的文件、以及其他的resouce文件经过apkbuilder打包成.apk文件;
- 4.apk文件经过签名工具最后生成可以签名的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/目录下。
- 1.preBuild和preDebugBuild
空task,仅做锚点使用,两者区别在于preDebugBuild针对变体的锚点,如设置了productFlavors则preDebugBuild就会变成preHuaweiDebugBuild,同样除去preBuild,其他的Task也都会增加该变体标识。对应实现类为AppPreBuildTask.java。
productFlavors相关内容可参见Android Gradle中的productFlavors。
- 2.compileDebugAidl
处理.aidl文件,将.aidl文件转换成java interface文件。若没有.aidl文件则会如上显示NO-SOURCE。对应实现类为AidlCompile.java。
- 3.compileDebugRenderscript
处理renderscript。若没有对应的文件,同样会显示NO-SOURCE。对应实现类为RenderscriptCompile.java。
RenderScript 是 Android 3.0 提出的一个高效的计算框架,能够自动的将计算任务分配给CPU、GPU、DSP等,为处理图片、数学模型计算等场景提供高效的计算能力。
语法类似 C/C++, 但它是在运行时编译,是跨平台的。性能比 Java 好,比 Native 略差。
在使用的时候分两步:(1)编写.rs文件;(2)使用 RenderScript。
- 4.generateDebugBuildConfig
生成BuildConfig.java文件,里面会包含DEBUG、APPLICATION_ID、FLAVOR、VERSION_CODE、VERSION_NAME以及自定义的属性,如图:
对应实现类为GenerateBuildConfig.java。
- 5.javaPreCompileDebug
生成annotaionProcessors.json文件。对应实现类为JavaPreCompileTask.java。
- 6.generateDebugResValues
生成revalues、generated.xml。对应实现类为GenerateResValues.java。
- 7.generateDebugResources
空task,仅做锚点。
- 8.checkDebugAarMetadata
- 9.createDebugCompatibleScreenManifests
Manifest文件中生成compatible-screens,指定屏幕适配。对应的实现类CompatibleScreenManifests.java。
- 10.extractDeepLinksDebug
- 11.processDebugMainManifest
合并所有的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
- 12.processDebugManifest
使用合并的AndroidManifest.xml文件,为所有的变体创建AndroidManifest.xml文件(一点思考:处理与变体相关的信息,可以在执行Task之后,对AndroidManifest.xml文件进行二次处理),最终保存在app/build/intermediates/merged_manifest,如图:
对应实现类ProcessMultiApkApplicationManifest.java。
- 13.mergeDebugNativeDebugMetadata NO-SOURCE
- 14.mergeDebugShaders
编译shaders,对应实现类ShaderCompile.java。
- 15.compileDebugShaders NO-SOURCE
- 16.generateDebugAssets UP-TO-DATE
- 17.mergeDebugAssets
合并assets文件。对应的实现类MergeSourceSetFolders.java。
- 18.compressDebugAssets
- 19.processDebugJavaRes
处理java res 。若没有对应的文件,同样会显示NO-SOURCE 。对应实现类为ProcessJavaresConfigAction.java。
- 20.checkDebugDuplicateClasses
- 21.desugarDebugFileDependencies
- 22.mergeDebugJavaResource
- 23.mergeDebugResources
合并资源文件(一点思考:可以在该Task之前对合并之前的资源文件进行预处理:例如在合并资源文件的时会进行去重操作,即相同的资源ID的资源文件会被认为是同一个,那么就改变该逻辑。),包括各个依赖包中的资源文件,最终调用aapt2命令去处理资源文件,生成“原资源文件名.xml.flat”格式的文件保存在app/build/intermediates/res目录下,如图:
对应实现类MergeResources.java。
- 24.mergeDebugJniLibFolders
合并jni lib文件,对应实现类MergeSourceSetFolder.java。
- 25.mergeLibDexDebug
- 26.validateSigningDebug
验证签名,对应实现类为ValidateSigningTask.java。
- 27.writeDebugAppMetadata
- 28.writeDebugSigningConfigVersions
- 29.processDebugManifestForPackage
- 30.mergeDebugNativeLibs
- 31.stripDebugDebugSymbols NO-SOURCE
- 32.mergeExtDexDebug
- 33.processDebugResources
aapt打包资源,生成最终的R.java(一点思考:可以在该执行该task之后对R文件进行二次处理)以及res.ap_,最终将文件保存app/build/intermediates/processed_res目录下:
对应实现类为ProcessAndroidResouces.java。
在使用aapt打包res资源文件,res资源文件又会分为二进制文件和非二进制文件,典型的非二进制文件如res/raw、图片,要求保持原样,不被编译。最终这些资源文件被打包成R.java、resources.arsc和res文件
- 34.compileDebugJavaWithJavac
编译java文件,对应的实现类为AndroidJavaCompile.java。
- 35.compileDebugSources
空task,仅做锚点。
- 36.dexBuilderDebug
- 37.mergeProjectDexDebug
- 38.packageDebug
打包apk,对应实现类PackageApplication
- 39.assembleDebug
空task,仅做锚点。
遗留问题:其他的Task可在后面陆续去研究。这次需要使用的仅用到上面红色标记的processDebugMainManifest和processDebugManifest。
二 实例1:适配Android12的android:exported属性
在前言提到的Android12如果没有对android:exported属性进行适配,那么执行到processHuaweiDebugMainManifest这个任务的时候就会抛出编译错误。所以借助这个应用场景,通过自定义Android Gradle plugin的方式来在processHuaweiDebugMainManifest这个任务执行之前,完成对第三方的SDK的Android12该属性的适配。
1.思路梳理
针对上面提出的问题,整理下代码逻辑:
- (1)自定义AddExportForEveryPackageManifestTask;
- 1)传入所有的依赖包以及主module的AndroidManifest.xml文件;
- 2)找到该AndroidManifest.xml文件的Activity、Service、BroadcastReceiver组件;
- 3)判断该组件是否满足“含有intent-filter && 没有添加android:exported”;
- 4)添加android:exported=true(因为考虑只为第三方的SDK中添加,所以设置为true比较保险。本APP的组件需要根据自身APP的特点由开发人员设置为true或false);
- 5)将处理好的内容重新写入AndroidManifest.xml文件;
- (2)找到锚点processDebugMainManifest Task,添加AddExportForEveryPackageManifestTask到任务队列
- 1)创建该插件的Project文件
- 2)找到processDebugMainManifest
- 3)在processDebugMainManifest执行之前添加AddExportForEveryPackageManifestTask
- (3)发布、使用插件
2.实例代码
有了上面的思路,开始逐步实现。
(1)新建、发布及使用插件
怎么创建一个Android Gradle Plugin可参见 Android Gradle插件开发初次交手(一),这里不在多余介绍。最终创建插件的源代码位于manifestplugin这个module下,将该插件应用到项目中。
(2)自定义AddExportForEveryPackageManifestTask
这个可以直接继承已有的API就可以实现一个自定义的Task,具体内容可参见Android Gradle的基本概念梳理(二)这里仅简单的在总结一下有三种已有的API:
- DefaultTask:org.gradle.api提供的Task的API。普通类,通常需要继承DefaultTask,就可以实现一个自定义的Task。通过为子类的方法上添加@TaskAction,就可以实现一个可执行的Task的执行方法;
- IncrementalTask:com.android.build.gradle提供的Task的API。抽象类,需要继承该类,就可以实现一个增量Task;需要通过复写getIncremental()返回true来支持增量编译.该API已经废弃,建议使用NewIncrementalTask
- NewIncrementalTask:com.android.build.gradle提供的Task的API。抽象类,需要继承该类,就可以实现一个增量Task;
- Tranform:com.android.build.gradle提供的Task的API。抽象类,需要继承该类,就可以实现一个运行在class文件转换成dex之前,对class文件进行处理的Task;
而实现 AddExportForEveryPackageManifestTask仅仅来继承DefaultTask即可,代码如下:
class AddExportForEveryPackageManifestTask extends DefaultTask
@TaskAction
void run()
//处理所有包下的AndroidManifest文件添加android:exported
SystemPrint.outPrintln("Running .....")
有了这个自定义的Task,先将Task加入到APP的打包编译的任务队列中,然后在逐步添加AddExportForEveryPackageManifestTask里面的逻辑代码。
(3)找到锚点processDebugMainManifest Task,添加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)
对上面的代码总结几个自己遇到的问题:
- 问题1:项目中的变体存在
因为项目中配置了变体(相关内容可参见Android Gradle中的productFlavors),所以所有的Task都会添加变体的信息,如processDebugMainManifest将会变为processHuaweiDebugMainManifest,其中HuaweiDebug为其中一个变体。那么在找processDebugMainManifest这个锚点的时候,就会变成了找到process$variantMainManifest,而变体信息是在主module在apply该插件的时候,就会输出的信息,而我添加自定义的Task需要在项目配置完成添加。
解决方案:在apply()的时候将所有的变体信息保存到variantNames集合中,然后在项目配置完成的时候,通过variantNames.each 的方式找到每个变体的process$variantMainManifest Task。
- 问题2:processDebugMainManifest对应的实现类
因为要从这个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的实现类。
- 问题3: 添加任务队列
现在可以通过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
(4)编写AddExportForEveryPackageManifestTask逻辑
已经有了这个插件的框架,现在就是填充AddExportForEveryPackageManifestTask逻辑了。
- 1)传入所有的依赖包以及主module的AndroidManifest.xml文件;
得到依赖包以及主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
- 2)找到该AndroidManifest.xml文件的Activity、Service、BroadcastReceiver组件;
使用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;
- 3)判断该组件是否满足“含有intent-filter && 没有添加android:exported”;
具体的代码可参见AddExportForEveryPackageManifestTask.groovy文件中的见hasAttributeExported()和hasIntentFilter()
- 4)添加android:exported=true(因为考虑只为第三方的SDK中添加,所以设置为true比较保险。本APP的组件需要根据自身APP的特点由开发人员设置为true或false);
具体的代码可参见AddExportForEveryPackageManifestTask.groovy文件中的handlerAddExportForNode(),这里仅总结自己遇到的几个坑:
点1:可通过node.attributes().put()为该node添加新的属性
因为有android:exported=true属性的该node在输出的时候,如下:
activity[attributes=http://schemas.android.com/apk/res/androidname=.TestActivity,
http://schemas.android.com/apk/res/androidexported=true;
value=[intent-filter[attributes=;
value=[action[attributes=http://schemas.android.com/apk/res/androidname=android.intent.action.SEARCH;
value=[]], category[attributes=http://schemas.android.com/apk/res/androidname=android.intent.category.DEFAULT;
value=[]]]]]]
在循环遍历node.attributes() 时,输出的key也为 http://schemas.android.com/apk/res/androidexported,所以一开始在添加android:exported=true属性的时候使用的是:
node.attributes().put(http://schemas.android.com/apk/res/androidexported, 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/androidname")都获取不到属性值,但是可以通过下面的代码获取到:
attrs.find
if("http://schemas.android.com/apk/res/androidname".equals(it.key.toString()))
String name = attrs.get(it.key)
//find return true相当于break
return true
猜测:这个可能跟Map的containsKey()逻辑有关系,因为传入的"http://schemas.android.com/apk/res/androidname"已经不是之前加入到该Map集合中的key,所以就不会匹配到value值。
- 5)将处理好的内容重新写入AndroidManifest.xml文件;
具体逻辑还是见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的属性,如图:
3.小结
在实现该自定义Android Gradle Plugin的过程,其实第一个关键点就是要找到合适的锚点来添加自定义的Task行为;第二个关键点就是自定义Task行为的实现。通过这个过程对自定义Android Gradle Plugin越发感兴趣。
三 实例2:读取配置文件的versionCode和versionName
前一个是对合并所有的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.思路梳理
针对上述的的实例,整理下代码逻辑:
- (1)自定义SetLatestVersionForMergedManifestTask
- 1)传入最后合并好的AndroidManifest.xml,要考虑到有变体情况
- 2)传入含有版本信息的version.xml,通过扩展属性传入
- 3)读取version.xml中的版本信息
- 4)更新到传入的AndroidManifest.xml文件中
- (2)找到生成最后AndroidManifest.xml的task(即processDebugManifest),作为锚点,在后面添加SetLatestVersionForMergedManifestTask
- 1)在前一个实例的代码基础上,在Project中找到processDebugManifest
- 2)在执行processDebugManifest之后,添加SetLatestVersionForMergedManifestTask
- (3)发布使用插件
2.实例代码
有了上面的思路,开始逐步实现代码。因为在二 实例1:适配Android12的android:exported属性已经有了该插件的框架,现在只需要自定义SetLatestVersionForMergedManifestTask和将该SetLatestVersionForMergedManifestTask加入到任务队列即可。
(1)自定义SetLatestVersionForMergedManifestTask
同样的方式继承DefaultTask,通过@TaskAction添加该任务的action,代码如下:
class SetLatestVersionForMergedManifestTask extends DefaultTask
@TaskAction
void doTaskAction()
(2)找到processDebugManifest ,将SetLatestVersionForMergedManifestTask添加到任务队列中
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到任务队列这种添加自定义任务的方式做了优化:
- 1)找到当前活跃的变体
在执行Build的时候,其中`project.gradle.getStartParameter().getTaskRequests()`返回的信息为:
[DefaultTaskExecutionRequestargs=[: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()返回的信息为: [DefaultTaskExecutionRequestargs=[],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时返回的内容:[DefaultTaskExecutionRequestargs=[],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时返回的内容:[DefaultTaskExecutionRequestargs=[],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的时候,该插件完全可以不作任何处理。
- 2) 找到processDebugManifest,添加自定义SetLatestVersionForMergedManifestTask
同样也是在项目配置完成之后,将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的编译打包的任务队列中。
(3)编写SetLatestVersionForMergedManifestTask逻辑
- 1)传入最后为每个变体创建的的AndroidManifest.xml文件
在前面添加SetLatestVersionForMergedManifestTask任务的时候已经将processDebugManifest中的变体的AndroidManifest.xml文件通过setManifestFile()方法传入到该自定义Task中
- 2)通过扩展属性来传入版本信息的version.xml
该实现方法就是要自定义扩展属性类,然后添加到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")
- 3)读取version.xml信息和更新最终的AndroidMainfest.xml
这部分内容就是通过XmlParse来读写xml文件,不在多余写代码。
具体的代码已经上传到至GitHub - wenjing-bonnie/AndroidPlugin: 用来学习Android Gradle Plugin。因为逐渐在这基础上进行迭代,可回退到Gradle_10.0.2该tag下可以查看相关内容,也可直接查看manifestplugin目录下的相关内容。
3.小结
相比较于实例一,优化了找锚点的方法,通过当前活跃的变体名,找到对应的锚点任务。
四 总结
经过两个实例,对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等注解来添加输入参数
加油!好玩
以上是关于Android Gradle 中的实例之动态修改AndroidManifest文件的主要内容,如果未能解决你的问题,请参考以下文章
Android Gradle 中的实例之动态修改AndroidManifest文件