本文原作者: 恋猫de小郭,原文布于: GSYTech 

众所周知,从 Android 12 开始,使用了 TargetSDK 31 之后,四大组件如果使用了 intent-filter,但是没显性质配置 exported App 将会无法安装,甚至编译不通过。

比如启动的 Activity 就需要设置 ex‍ported 为 true,至于其他组件是否设置为 true 则看它是否需要被其它应用调用。

然而这个事情的状态是这样的:

  • 如果出现问题的 AndroidManifest 文件是您本地的,那手动修改即可;

  • 但如果出现问题的是第三方远程依赖,并且对方并没有提供源码和更新,您就无法直接修改;

  • 如果第三方依赖太多,查找哪些出了问题十分费时费力。

脚本

所以在之前的《Android 12 快速适配要点》一文中提供了一套脚本,专门用于适配 Android 12 下缺少 android:exported 无法编译或者安装的问题,但是在这期间收到了不少问题反馈:

com.android.tools.build:gradle:4.0.0 以及其下版本

以下脚本经过测试最高可到支持的版本: gradle:4.0.0 & gradle-6.1.1-all.zip

/*** 修改 Android 12 因为 exported 的构建问题*/
android.applicationVariants.all { variant ->variant.outputs.all { output ->output.processResources.doFirst { pm ->String manifestPath = output.processResources.manifestFiledef manifestFile = new File(manifestPath)def xml = new XmlParser(false, true).parse(manifestFile)def exportedTag = "android:exported"///指定 spacedef androidSpace = new groovy.xml.Namespace('http://schemas.android.com/apk/res/android', 'android')def nodes = xml.application[0].'*'.findAll {//挑选要修改的节点,没有指定的 exported 的才需要增加(it.name() == 'activity' || it.name() == 'receiver' || it.name() == 'service') && it.attribute(androidSpace.exported) == null}///添加 exported,默认 falsenodes.each {def isMain = falseit.each {if (it.name() == "intent-filter") {it.each {if (it.name() == "action") {if (it.attributes().get(androidSpace.name) == "android.intent.action.MAIN") {isMain = trueprintln("......................MAIN FOUND......................")}}}}}it.attributes().put(exportedTag, "${isMain}")}PrintWriter pw = new PrintWriter(manifestFile)pw.write(groovy.xml.XmlUtil.serialize(xml))pw.close()}}}

com.android.tools.build:gradle:4.0.0 以上版本

以下脚本经过测试支持的版本: gradle:4.1.0 & gradle-6.5.1-all.zip

/*** 修改 Android 12 因为 exported 的构建问题*/android.applicationVariants.all { variant ->variant.outputs.each { output ->def processManifest = output.getProcessManifestProvider().get()processManifest.doLast { task ->def outputDir = task.multiApkManifestOutputDirectoryFile outputDirectoryif (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///这里第二个参数是 false ,所以 namespace 是展开的,所以下面不能用 androidSpace,而是用 nameTagdef xml = new XmlParser(false, false).parse(manifestFile)def exportedTag = "android:exported"def nameTag = "android:name"///指定 space//def androidSpace = new groovy.xml.Namespace('http://schemas.android.com/apk/res/android', 'android')def nodes = xml.application[0].'*'.findAll {//挑选要修改的节点,没有指定的 exported 的才需要增加//如果 exportedTag 拿不到可以尝试 it.attribute(androidSpace.exported)(it.name() == 'activity' || it.name() == 'receiver' || it.name() == 'service') && it.attribute(exportedTag) == null}///添加 exported,默认 falsenodes.each {def isMain = falseit.each {if (it.name() == "intent-filter") {it.each {if (it.name() == "action") {//如果 nameTag 拿不到可以尝试 it.attribute(androidSpace.name)if (it.attributes().get(nameTag) == "android.intent.action.MAIN") {isMain = trueprintln("......................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 文件里显示红色并不会影响运行;

  • gradle-plugin

    https://developer.android.google.cn/studio/releases/gradle-plugin

  • 运行报错提示 android:exported needs,这个就是今天需要深入聊的

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 到 Android 12 的机器上,有 intent-filter 但没有 exoprted 的适配问题,基于这个问题,不知道各位是否有什么好的建议?

新脚本

而目前基于这个问题,这里提供了如下脚本,在 gradle:4.2.0 & gradle-6.7.1-all.zip 以及 7.0 的版本上,该脚本的作用是在运行时自动帮您打印出现问题的 aar 包依赖路径和组建名称:

android.applicationVariants.all { variant ->variant.outputs.each { output ->//println("=============== ${variant.getBuildType().name.toUpperCase()} ===============")//println("=============== ${variant.getFlavorName()} ===============")def vnif (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 {//def method = it.getClass().getMethods()it.getManifests().getFiles().each {if (it.exists() && it.canRead()) {def manifestFile = itdef exportedTag = "android:exported"def nameTag = "android:name"///这里第二个参数是 false ,所以 namespace 是展开的,所以下面不能用 androidSpace,而是用 nameTagdef xml = new XmlParser(false, false).parse(manifestFile)if (xml.application != null && xml.application.size() > 0) {def nodes = xml.application[0].'*'.findAll {//挑选要修改的节点,没有指定的 exported 的才需要增加//如果 exportedTag 拿不到可以尝试 it.attribute(androidSpace.exported)(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 = itit.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 可以打包成功,但是运行到 Android 12 机器的时候,可能会因为没有 exported 遇到安装失败的问题:

1. 如果是模拟器 12,您可能会看到如下所示的错误提示,提示上显示还是很直观的, 直接告诉您是 android:exported 的问题:

* 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:

基本上内容就这些,具体如何进一步优化还待后续测试,所以针对脚本实现,您还有什么问题或者想法,欢迎通过私信交流。


长按右侧二维码

查看更多开发者精彩分享

"开发者说·DTalk" 面向中国开发者们征集 Google 移动应用 (apps & games) 相关的产品/技术内容。欢迎大家前来分享您对移动应用的行业洞察或见解、移动开发过程中的心得或新发现、以及应用出海的实战经验总结和相关产品的使用反馈等。我们由衷地希望可以给这些出众的中国开发者们提供更好展现自己、充分发挥自己特长的平台。我们将通过大家的技术内容着重选出优秀案例进行谷歌开发技术专家 (GDE) 的推荐。

 点击屏末 | 阅读原文 | 即刻报名参与 "开发者说·DTalk"


Android 12 自动适配 exported 深入解析避坑 | 开发者说·DTalk相关推荐

  1. Android 12 自动适配 exported 深入解析避坑

    众所周知,从 Android 12 开始,使用了 TargetSDK 31 之后,四大组件如果使用了 intent-filter, 但是没显性质配置 exported App 将会无法安装,甚至编译不 ...

  2. Android 12应用适配指南

    Android 12应用适配指南 1.Android 12上的主要变更 1.1 兼容性 1.1.1 前台服务启动限制 1.1.2 前台服务通知延迟 1.1.3 待处理 intent 必须声明可变性 1 ...

  3. Android 12 快速适配要点

    Android 12 需要更新适配点并不多,本篇主要介绍最常见的两个需要适配的点:android:exported 和 SplashScreen . 一.android:exported 它主要是设置 ...

  4. Android 12 蓝牙适配

    Android 12.0蓝牙适配 前言 正文 一.Android版本中蓝牙简介 二.新建项目 ① 配置settings.gradle和build.gradle ② 配置AndroidManifest. ...

  5. 华为android o适配名单,Android 12首批适配名单公布:没有华为、荣耀

    谷歌已经正式发布了Android 12,新的系统底层带来了3个改动:更丰富的视觉元素,更全面的隐私保护,并且开始引入"多设备互联"的概念.说实话,Android 12的更新只能用& ...

  6. Android 性能优化「黑科技」Baseline Profiles | 开发者说·DTalk

    本文原作者: 朱涛,原文发布于: 朱涛的自习室 今天我们来扒一下 Baseline Profiles 的底层原理. 正文 今年 Google I/O 大会上,Android 官方强推了一把 Basel ...

  7. Android笔记(二十四):gradle写一个android12自动适配exported脚本,支持aab

    背景 由于Google play的政策,提审aab的时候需要适配android12,适配android12最大的工作就是在AndroidManifesst.xml文件中声明的四大组件,都要显式声明ex ...

  8. 面挂了腾讯、阿里巴巴、美团Android客户端,我有一份避坑指南分享给你!

    一.学习经历 比较菜,辣鸡本科生,去年十一月份开始全面一些的接触安卓,学了第一行代码,面试后厂村某公司某部门,正好比较缺人给我这零基础的人过了,十二月份去实习,断断续续中间回学校考试有请假,大概有效实 ...

  9. Android 12 变更及适配攻略

    这几个月有点忙,一年一篇的适配文章来的有点晚了.但其实也还好,因为我们项目也是下半年才适配.我这边也是提前调研踩坑,评估一下工作量.这个时间点也完全跟得上Google Play的审核要求(11月1号) ...

最新文章

  1. 点到点链路上的ospf
  2. 遵义医科大学计算机专业好吗,遵义医科大学专业评价
  3. 日志服务数据加工最佳实践: 从其他logstore拉取数据做富化
  4. DedeCMS更新文章同步发布到新浪微博
  5. 引气管理计算机失效,TAT信号无效导致ND上TAS-显示消失B737系列机务在线 - 认真、负责、细致 我们秉承的理念...
  6. [unity2D] 迷你拼图
  7. SketchUp + Photoshop:别墅平面图制作教程
  8. 耗电更少,苹果 M1 编译代码速度与 2019 年 Mac Pro 一样快
  9. MRI数据预处理--使用FSL-BET轻松去头骨,提取脑组织
  10. iOS--AFN实现原理
  11. 监听器到底是什么,有什么用
  12. 浅谈Linux下各种压缩 解压命令和压缩比率对比
  13. 数据库读写分离方法浅析
  14. centos7 安装 mysql5.5_CentOs7 安装 Mysql5.7
  15. 二叉树的遍历(二叉树与递归算法)
  16. echarts图表自定义图例
  17. 出自名门:微软杀毒软件MSE 2.1 正式版下载
  18. 食亨CEO王泰舟对话新华社:餐饮数字化经营时代已经到来
  19. openStack镜像状态以及创建流程学习
  20. 【大牛系列教学】java在线答题系统

热门文章

  1. 报关软件java_支付宝报关接口开发
  2. 定制自己的Linux 系统
  3. iPhone 电子书toolbar的实现
  4. 第四章 ROBOGUIDE界面介绍
  5. 手表计算机编程,手表界的一朵“奇葩”智能编程手表T-Watch
  6. 【Codecs系列】之NVIDIA Jetson TX1简介
  7. Redis的图形化客户端(RESP)的安装及使用
  8. springboot返回html
  9. 监听滚动条事件来实现导航栏吸顶效果
  10. Oracle删除用户以及表空间的操作步骤