背景
去年适配了targetsdk 30,今年马上又要到GP后台的限定日期了,目前targetsdk 31已经适配完成,技术方案如下,希望各位童鞋能够有参考价值,少踩坑~
exported属性扫描
方案1:gradle脚本扫描
1,如果手动去找的话,那么多三方sdk不好找,初开始想到的是用gradle脚本扫描,最终实验很多次得到的有效的扫描的脚本如下:
gradle脚本1:
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().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 的才需要增加
(it.name() == 'activity' || it.name() == 'receiver' || it.name() == 'service') && it.attribute(androidSpace.exported) == null
}
///添加 exported,默认 false
nodes.each {
def hasIntentFilter = false
it.each {
if (it.name() == "intent-filter") {
hasIntentFilter = true
println("TestGradle: $manifestFile \n .....................$ { it.attributes().get(nameTag) } ......................")
}
}
if(hasIntentFilter){
println("TestGradle: it = $ { it.attributes().get(androidSpace.name) } ")
}
it.attributes().put(exportedTag, "true")
}
PrintWriter pw = new PrintWriter(manifestFile)
pw.write(groovy.xml.XmlUtil.serialize(xml))
pw.close()
}
}
}
2,但是由于在高版本的AGP上无法使用,原因在于新版本在 processDebugMainManifest ,或者说 processXXXXXXMainManifest 的处理逻辑发生了变化,通过找到 processDebugMainManifest 的实现类,可以看到问题出现就是在于 Merging library manifest,所以导致无法写入到最终merged的Androidmenifest.xml中,导致无效,所以只能打印一下哪些类存在没有声明exported的情况,如下:
3,可以看出存在一个小问题是:输出的Androidmenifest.xml路径是build中的不能直接点进去到源文件,试了“project.getTasks().getByName(taskName)”的遍历形式,但是也存在扫描不到的情况,所以最终可用的脚本形式如上的“gradle脚本1”的形式,不过也能达到应有的作用,只是没那么方便
方案2:终极形式-自定task,插件化
- 原理:通过自定义Task,把任务插入到 processxxxMainManifest Task之前,提前对manifest进行修改,即可完成对exported的检测添加,核心代码详见插件
- 插件使用:通过引入插件,自动修改所有manifest(含sdk)中的所有声明了“”并且没有显式声明“android:exported”属性的所有activity,service,receiver,使用方式如下
build.gradle
buildscript { //gradle 7.0以下:
repositories {
maven { url 'https://jitpack.io' }
}
}
pluginManagement { //gradle 7.0以上:
repositories {
maven { url 'https://jitpack.io' }
}
}
Gradle
dependencies {
classpath 'com.github.xiachufang:manifest-exported-plugin:1.0.6'
}
在主app Model中添加插件:
apply plugin: 'com.xiachufang.manifest.exported'
或
plugins {
id 'com.xiachufang.manifest.exported'
}
app-build.gradle
exported {
actionRules = ["android.intent.action.MAIN"]
enableMainManifest false
logOutPath "自定义的日志输出目录,如果不存在会自动创建"
}
参数说明:
logOutPath 日志输出目录,默认 app /build/ exported/outManifest.md
actionRules action的匹配项(数组), 如:
<activity android:name=".simple.MainActivity" >
<intent-filter>
// action 对应的 android:name 可与actionRules 数组任意一项匹配 ,并且当前没有配置exported
// -> yes: android:exported="true"
// -> no: android:exported="false"
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
enableMainManifest 是否对主 model-AndroidManifest 进行修改
github地址:https://github.com/xiachufang/manifest-exported-plugin
插件扫描结束之后会生成结果md文件:build/exported/outMainfestLog.md,里面会报出具体的类找到的需要适配的文件暴露出来,如果配置参数“enableMainManifest”设置为false,他会对打出来的包修改,如果为true,他会对源文件“AndroidManifest.xml”进行修改,扫描结果截图如下:
而我没有采用它的自动修改模式,我是根据它的扫描结果,找到对应的“AndroidManifest.xml”,然后找到具体的类加上“android:exported”属性,而sdk中的则采用replace的形式,如下所示:
<activity
android:name="xxxxx.XActivity"
android:exported="false"
tools:replace="android:exported"/>
PendIntent flag设置
- 要求:Taregtsdk 31 要求显式的指定PendingIntent 可变性,必须显式声明一个可变性标志即 FLAG_MUTABLE 或 FLAG_IMMUTABLE,在此之前,PendingIntent 默认是可变的。
- 问题:后来测试过程中发现,不只是PendingIntent.Activity,PendingIntent的很多方法都会出现这个问题,核心的出问题的代码是PendingIntent.checkFlags这个方法,而这个方法的调用会在PendingIntent的诸如:getActivity,getActivities,getBroadcast,getService,getForegroundService,都调用了checkFlags这个方法,所以只能通过hook的形式hook这些方法的调用,根据sdk版本替换Flag;不修改flag的报错截图和依赖库引入代码如下:
所以需要hook这些方法,hook组件是采用的开源封装(这个我会在接下来的文章中单独介绍)
TagtetSdk版本
使用版本如下:
android = [
compileSdkVersion: 31,
buildToolsVersion: "30.0.3",
minSdkVersion: 21,
targetSdkVersion: 31]
JDK 版本
Targetsdk 31 需要指定jdk版本至少为java 11
uses-library
targetsdk 31的APP 对于系统库的引用会出问题,所以需要通过uses-library的标签来指定路径
<uses-library
android:name="string"
android:required=["true", "false"]/>
总结
谷歌每年都会要求适配最新版本的Target API,早一些适配完,早一点暴露出线上的适配问题,及时修正;今天的分享就到这里了,巴拉巴拉~拜拜