# 【Flutter】Flutter 修复 Android 12 无法构建的问题,自动适配 exported 深入解析避坑
【摘要】 众所周知,从 Android 12 开始,使用了 TargetSDK 31 之后,四大组件如果使用了 intent-filter, 但是没显性质配置 exported App 将会无法安装,甚至编译不通过…
比如启动的 Activity
就需要设置 exported
为 true
,至于其他组件是否设置为 true
则看它是否需要被其它应用调用。
# 然而这个事情的状态是这样的:
如果出现问题的 AndroidManifest
文件是你本地的,那手动修改即可;
但如果出现问题的是第三方远程依赖,并且对方并没有提供源码和更新,你就无法直接修改;
如果第三方依赖太多,查找哪些出了问题十分费时费力。
# 脚本
所以在之前的 《Android 12 快速适配要点》 一文中提供了一套脚本,专门用于适配 Android 12 下缺少 android:exported
无法编译或者安装的问题,但是在这期间收到了不少问题反馈:
一下脚本经过测试最高可到支持的版本: gradle:4.0.0 & gradle-6.1.1-all.zip
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 android.applicationVariants.all { variant -> variant.outputs.all { output -> output.processResources.doFirst { pm -> String manifestPath = output.processResources.manifestFile def manifestFile = new File (manifestPath) def xml = new XmlParser(false , true ).parse(manifestFile) def exportedTag = "android:exported" def androidSpace = new groovy.xml.Namespace('http://schemas.android.com/apk/res/android' , 'android' ) def nodes = xml.application[0 ].'*' .findAll { (it.name() == 'activity' || it.name() == 'receiver' || it.name() == 'service' ) && it.attribute(androidSpace.exported) == null } nodes.each { def isMain = false it.each { if (it.name() == "intent-filter" ) { it.each { if (it.name() == "action" ) { if (it.attributes().get(androidSpace.name) == "android.intent.action.MAIN" ) { isMain = true println ("......................MAIN FOUND......................" ) } } } } } it.attributes().put(exportedTag, "${isMain}" ) } PrintWriter pw = new PrintWriter(manifestFile) pw.write (groovy.xml.XmlUtil.serialize(xml)) pw.close() } } }
以下脚本经过测试支持的版本: gradle:4.1.0 & gradle-6.5.1-all.zip
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 android.applicationVariants.all { variant -> variant.outputs.each { output -> def processManifest = output.getProcessManifestProvider().get() processManifest.doLast { task -> def outputDir = task .multiApkManifestOutputDirectory File outputDirectory if (outputDir instanceof File ) { outputDirectory = outputDir } else { outputDirectory = outputDir.get().asFile } File manifestOutFile = file ("$outputDirectory/AndroidManifest.xml" ) println ("----------- ${manifestOutFile} ----------- " ) if (manifestOutFile.exists() && manifestOutFile.canRead() && manifestOutFile.canWrite()) { def manifestFile = manifestOutFile def xml = new XmlParser(false , false ).parse(manifestFile) def exportedTag = "android:exported" def nameTag = "android:name" def nodes = xml.application[0 ].'*' .findAll { (it.name() == 'activity' || it.name() == 'receiver' || it.name() == 'service' ) && it.attribute(exportedTag) == null } nodes.each { def isMain = false it.each { if (it.name() == "intent-filter" ) { it.each { if (it.name() == "action" ) { if (it.attributes().get(nameTag) == "android.intent.action.MAIN" ) { isMain = true println ("......................MAIN FOUND......................" ) } } } } } it.attributes().put(exportedTag, "${isMain}" ) } PrintWriter pw = new PrintWriter(manifestFile) pw.write (groovy.xml.XmlUtil.serialize(xml)) pw.close() } } } }
这段脚本你可以直接放到 app/build.gradle
下执行,也可以单独放到一个 gradle 文件之后 apply
引入,它的作用就是:
在打包过程中检索所有没有设置 exported
的组件,给他们动态配置上 exported
,这里有个特殊需要注意的是,因为启动 Activity
默认就是需要被 Launcher 打开的,所以 "android.intent.action.MAIN"
需要 exported
设置为 true
。(PS:更正规应该是用 LAUNCHER 类别,这里故意用 MAIN )
而后综合问题,具体反馈的问题有 :
label
直接写死中文,不是引用 @string
导致的在 3.x 的版本可以正常运行,但不能打包 ;
XmlParser
类找不到,这个首先确定 AGP 版本和 Gradle 版本是否匹配,具体可见 gradle-plugin,另外可以通过 groovy.util.XmlParser
或者 groovy.xml.XmlParser
全路径指定使用 ,如果是 gradle 文件里显示红色并不会影响运行;
运行报错提示 android:exported needs
,这个就是今天需要输入聊的 ;
1 2 3 4 Error : android :exported needs to be explicitly specified for <xxxx>. 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.
基于上述脚本测试和反馈,目前的结论是:
从 gradle:4.2.0 & gradle-6.7.1-all.zip
开始,TargetSDK 31 下脚本会有异常,因为在 processDebugMainManifest
(带有 Main) 的阶段,会直接扫描依赖库的 AndroidManifest.xml
然后抛出直接报错,从而进不去 processDebugManifest
任务阶段就编译停止,所以实际上脚本并没有成功运行 。
所以此时拿不到 mergerd_manifest
下的文件,因为 mergerd_manifest
下 AndroidManifest.xml
也还没创建成功,没办法进入 task ,也就是该脚本目前只能针对 gradle:4.1.0
以及其下版本安装 apk 到 Android12 的机器上, 有 intent-filter
但没有 exoprted 的适配问题,基于这个问题,不知道各位是否有什么好的建议?
# 新脚本
而目前基于这个问题,这里提供了如下脚本,在 gradle:4.2.0 & gradle-6.7.1-all.zip
以及 7.0
的版本上,该脚本的作用是在运行时自动帮你打印出现问题的 aar 包依赖路径和组建名称 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 android.applicationVariants.all { variant -> variant.outputs.each { output -> def vn if (variant.getFlavorName() != null && variant.getFlavorName() != "" ) { vn = variant.name; } else { if (variant.getBuildType().name == "release" ) { vn = "Release" } else { vn = "Debug" } } def taskName = "process${vn}MainManifest" ; try { println ("=============== taskName ${taskName} ===============" ) project .getTasks().getByName(taskName) } catch (Exception e) { return } project .getTasks().getByName(taskName).doFirst { it.getManifests().getFiles().each { if (it.exists() && it.canRead()) { def manifestFile = it def exportedTag = "android:exported" def nameTag = "android:name" def xml = new XmlParser(false , false ).parse(manifestFile) if (xml.application != null && xml.application.size () > 0 ) { def nodes = xml.application[0 ].'*' .findAll { (it.name() == 'activity' || it.name() == 'receiver' || it.name() == 'service' ) && it.attribute(exportedTag) == null } if (nodes.application != null && nodes.application.size () > 0 ) { nodes.each { def t = it it.each { if (it.name() == "intent-filter" ) { println ("$manifestFile \n .....................${t.attributes().get(nameTag)}......................" ) } } } } } } } } } }
如下图所示,因为目前官方如红色信息内容其实指向并不正确,容易误导问题方向,所以通过上述脚本打印,可以快速查找到问题所在的点,然后通过 tool:replace
临时解决 。
具体为什么之前的脚本在高版本 AGP 下无法使用,原因在于新版本在 processDebugMainManifest
,或者说 processXXXXXXMainManifest
的处理逻辑发生了变化,通过找到 processDebugMainManifest
的实现类,可以看到问题出现就是在于 Merging library manifest
。
processDebugMainManifest
的实现在 ProcessApplicationManifest 里,对应路径是 ProcessApplicationManifest -> MainfestHelper mergeManifestsForApplication -> MainfestMerger2
错误是在 Merging library manifest
的阶段出现异常,但是这个阶段的 task 里对于第三方依赖路径的输入,主要是从 private fun computeFullProviderList
方法开始,所以输入到 mergeManifestsForApplication
里的第三方路径是通过这个私有方法生成。
感觉唯一可以考虑操作的就是内部的 manifests
对象去变换路径,但是它是 private
,并且内部并不能很好复写其内容。
另外因为 aar 文件里的 AndroidManifset
是 readOnly ,所以如果真的要修改,感觉只能在输入之前读取到对应 AndroidManifset
, 并生成临时文件,在 manifests
对象中更改其路径来完成 ,不知道大家有没有什么比较好的思路 。
如果有好的解决办法,后续再更新。
# 最后
最后再说一个坑 ,如果你是低版本 Gradle 可以打包成功,但是运行到 Android12 机器的时候,可能会因为没有 exported 遇到安装失败的问题:
1、如果是模拟器 12,你可能会看到如下所示的错误提示 ,提示上显示还是很直观的, 直接告诉你是 android:exported
的问题:
1 2 3 4 5 6 * What went wrong : Execution failed for task ':app:installDebug' .> java.util .concurrent .ExecutionException : com.android .builder .testing .api .DeviceException : com.android .ddmlib .InstallException : INSTALL_PARSE_FAILED_MANIFEST_MALFORMED : Failed parse during installPackageLI : /data/ app/vmdl487461761.tmp /base.apk (at Binary XML file line #358 ): xxxxx.Activity : Targeting S+ (version 31 and above) requires that an explicit value for android :exported be defined when intent filters are present
2、如果你是真机 12,那可能就是这样的提示,提示然是 INSTALL_FAILED_USER_RESTRICTED
,不得不说小米系统这个安装失败很具误导性,比如 minSDK 太高导致无法安装,在小米上也会是 INSTALL_FAILED_USER_RESTRICTED
:
基本上内容就这些,具体如何进一步优化还待后续测试, 所以针对脚本实现,你还有什么问题或者想法,欢迎评论交流 ~
文章来源: carguo.blog.csdn.net ,作者:恋猫 de 小郭,版权归原作者所有,如需转载,请联系作者。
原文链接:carguo.blog.csdn.net/article/details/123438734
# 关于我
Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!
非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!