当前位置:   article > 正文

Android 13 自动适配 exported 深入解析避坑_android exported

android exported

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

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

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

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

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

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

这块网上各个大佬之前都是采取脚本的方式解决的,原理其实也可简单,就是在打包过程中检索所有没有设置 exported 的组件,给他们动态配置上 exported,这里有个特殊需要注意的是,因为启动 Activity 默认就是需要被 Launcher 打开的,所以 "android.intent.action.MAIN" 需要 exported 设置为 true 。

脚本如下:

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

/**
 * 修改 Android 12 因为 exported 的构建问题
 */

  1. android.applicationVariants.all { variant ->
  2.     variant.outputs.all { output ->
  3.         output.processResources.doFirst { pm ->
  4.             String manifestPath = output.processResources.manifestFile
  5.             def manifestFile = new File(manifestPath)
  6.             def xml = new XmlParser(false, true).parse(manifestFile)
  7.             def exportedTag = "android:exported"
  8.             ///指定 space
  9.             def androidSpace = new groovy.xml.Namespace('http://schemas.android.com/apk/res/android', 'android')
  10.  
  11.             def nodes = xml.application[0].'*'.findAll {
  12.                 //挑选要修改的节点,没有指定的 exported 的才需要增加
  13.                 (it.name() == 'activity' || it.name() == 'receiver' || it.name() == 'service') && it.attribute(androidSpace.exported) == null
  14.  
  15.             }
  16.             ///添加 exported,默认 false
  17.             nodes.each {
  18.                 def isMain = false
  19.                 it.each {
  20.                     if (it.name() == "intent-filter") {
  21.                         it.each {
  22.                             if (it.name() == "action") {
  23.                                 if (it.attributes().get(androidSpace.name) == "android.intent.action.MAIN") {
  24.                                     isMain = true
  25.                                     println("......................MAIN FOUND......................")
  26.                                 }
  27.                             }
  28.                         }
  29.                     }
  30.                 }
  31.                 it.attributes().put(exportedTag, "${isMain}")
  32.             }
  33.  
  34.             PrintWriter pw = new PrintWriter(manifestFile)
  35.             pw.write(groovy.xml.XmlUtil.serialize(xml))
  36.             pw.close()
  37.         }
  38.     }
  39.  
  40. }

com.android.tools.build:gradle:4.0.0 以上版本
以下脚本经过测试支持的版本: 「gradle:4.1.0 & gradle-6.5.1-all.zip」

/**
 * 修改 Android 12 因为 exported 的构建问题
 */
 

  1. android.applicationVariants.all { variant ->
  2.     variant.outputs.each { output ->
  3.         def processManifest = output.getProcessManifestProvider().get()
  4.         processManifest.doLast { task ->
  5.             def outputDir = task.multiApkManifestOutputDirectory
  6.             File outputDirectory
  7.             if (outputDir instanceof File) {
  8.                 outputDirectory = outputDir
  9.             } else {
  10.                 outputDirectory = outputDir.get().asFile
  11.             }
  12.             File manifestOutFile = file("$outputDirectory/AndroidManifest.xml")
  13.             println("----------- ${manifestOutFile} ----------- ")
  14.  
  15.             if (manifestOutFile.exists() && manifestOutFile.canRead() && manifestOutFile.canWrite()) {
  16.                 def manifestFile = manifestOutFile
  17.                 ///这里第二个参数是 false ,所以 namespace 是展开的,所以下面不能用 androidSpace,而是用 nameTag
  18.                 def xml = new XmlParser(false, false).parse(manifestFile)
  19.                 def exportedTag = "android:exported"
  20.                 def nameTag = "android:name"
  21.                 ///指定 space
  22.                 //def androidSpace = new groovy.xml.Namespace('http://schemas.android.com/apk/res/android', 'android')
  23.  
  24.                 def nodes = xml.application[0].'*'.findAll {
  25.                     //挑选要修改的节点,没有指定的 exported 的才需要增加
  26.                     //如果 exportedTag 拿不到可以尝试 it.attribute(androidSpace.exported)
  27.                     (it.name() == 'activity' || it.name() == 'receiver' || it.name() == 'service') && it.attribute(exportedTag) == null
  28.  
  29.                 }
  30.                 ///添加 exported,默认 false
  31.                 nodes.each {
  32.                     def isMain = false
  33.                     it.each {
  34.                         if (it.name() == "intent-filter") {
  35.                             it.each {
  36.                                 if (it.name() == "action") {
  37.                                     //如果 nameTag 拿不到可以尝试 it.attribute(androidSpace.name)
  38.                                     if (it.attributes().get(nameTag) == "android.intent.action.MAIN") {
  39.                                         isMain = true
  40.                                         println("......................MAIN FOUND......................")
  41.                                     }
  42.                                 }
  43.                             }
  44.                         }
  45.                     }
  46.                     it.attributes().put(exportedTag, "${isMain}")
  47.                 }
  48.  
  49.                 PrintWriter pw = new PrintWriter(manifestFile)
  50.                 pw.write(groovy.xml.XmlUtil.serialize(xml))
  51.                 pw.close()
  52.  
  53.             }
  54.  
  55.         }
  56.     }
  57. }

这段脚本你可以直接放到 app/build.gradle 下执行,也可以单独放到一个 gradle 文件之后 apply 引入

但是问题是到了gradle 4.2+、gradle-6.7.1-all.zip 的时候,这些脚本就不能用了,原因「从 gradle:4.2.0 & gradle-6.7.1-all.zip 开始,TargetSDK 31 下脚本会有异常,因为在 processDebugMainManifest (带有Main) 的阶段,会直接扫描依赖库的 AndroidManifest.xml 然后抛出直接报错,从而进不去 processDebugManifest 任务阶段就编译停止,所以实际上脚本并没有成功运行 ,针对这个,额,又有个大佬做了这块的处理,下面贴上大佬的解决方法:

manifest-exported-plugin
 大佬的插件叫这个名字,使用方法:

依赖方式
添加jitpack仓库
build.gradle

Gradle7.0 以下

  1. buildscript {
  2.     repositories {
  3.             // ...
  4.             maven { url 'https://jitpack.io' }
  5.     }
  6. }


Gradle7.0+,并且已经对依赖方式进行过调整,则可能需要添加到如下位置:settings.gradle

  1. pluginManagement {
  2.     repositories {
  3.         //...
  4.            maven { url 'https://jitpack.io' }
  5.        }
  6.  }
  7. Gradle
  8. dependencies {
  9.       classpath 'com.github.xiachufang:manifest-exported-plugin:1.1.1'
  10. }


使用方式
添加插件
在主app Model中添加:

  1. apply plugin: 'com.xiachufang.manifest.exported'
  2. plugins {
  3.  id 'com.xiachufang.manifest.exported'
  4. }
  5.  build.gradle
  6. apply plugin: 'com.xiachufang.manifest.exported'
  7. ...
  8.   
  9. exported {
  10.     // 是否写入主Model
  11.     enableMainManifest false
  12.     // 规则
  13.     ruleFile new File("$projectDir/xxx.json")
  14.     // 输出文件,默认-app/build/exported/outManifestLog.md
  15.     outPutFile null
  16. }


配置参数说明
enableMainManifest

是否对主 model-AndroidManifest 进行修改

对于主model,属于业务可控的,建议开发者自行调整。

插件默认不会对主 model-AndroidManifest 进行修改,如果发现可用匹配上述规则的,即会进行修正。

开发者可根据日志中的提示,进行修改。

注意:这个操作会对Manifest的展示样式造成一定影响,建议一般不要打开。

outPutFile
日志输出目录,默认 app/build/exported/outManifest.md

ruleFile
具体的规则json文件, 格式如下:

  1. {
  2.   "actionRules": [
  3.     "android.intent.action.MAIN"
  4.   ],
  5.   "whiteNames": [],
  6.   "blackPackages": [],
  7.   "blackNames": [],
  8.   "blackIgnores": []
  9. }


actionRules

默认判断规则,用于当前没有配置 exported 的修改逻辑,如果当前存在exported,则跳过。

具体判断逻辑:

如果 intent-filter - action 对应的 android:name 与 actionRules 中任意一条匹配,则将 exported 修改为 true ,否则为false 。

whiteNames

白名单类名,如果遇到此类,并且使用了 intent-filter ,则会将 exported 修改为 true

blackPackages

黑名单 包名合集,对于此包名下的类,如果使用了 intent-filter ,则会将 exported 直接修改为 false

blackNames

黑名单 类名合集,如果遇到此合集中的类,并且使用了 intent-filter ,则会将 exported 直接修改为 true 。判断逻辑会与上面 blackPackages 一起判断,两者满足其一即可。

blackIgnores

黑名单 下要忽视的类,在黑名单判断时,如果遇到此类,则使用默认规则 actionRules 判断。

注意的是:

对于主model下的 manifest ,默认不进行适配(可开关 enableManifest ),会通过日志进行输出,建议大家自行对比调整。

为什么默认不对主 model 进行适配?

对于业务 model ,我们建议开发者自行适配,这属于我们可控范围,适配来说主要就是为了不可控的,即第三方 aar
修改之后,会影响原有的 manifest 代码风格,需要重新格式化一下,相比默认的,增加了不少空格,暂时不知道怎么解决。
原理简述
在ProgressXXXMainManifest任务之前进行插入,通过对manifest进行修改,从而实现exported的适配。

通常默认情况下,会在build文件夹下生成个export文件夹,其中有个outManifestLog.md,这个就等于是个插件执行日志之类的东西,嗯,就这样,反正挺6的就是
 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/空白诗007/article/detail/900604
推荐阅读
相关标签
  

闽ICP备14008679号