1.2 蓝牙连接的权限变更
在 Android14 上,调用 BluetoothAdapter
的 getProfileConnectionState()
API 时必须申请 BLUETOOTH_CONNECT
权限,以前不是必须的,现在必须在 Manifest 文件中声明,并且在运行时向用户申请该权限。
很明显 Android 这几年逐渐在回收一些系统权限,对于开发者来说更加麻烦了,但有利于广大的使用者。
1.3 OpenJDK 17 更新
Android14 继续更新 Android 的核心库,使其与最新的 OpenJDK LTS 版本的特性、功能保持一致,包括对库的更新以及对应用和平台开发人员的 Java17 语言的支持。以下的一些变化可能会影响应用的兼容性:
- 正则表达式的变更:有些正则表达式已经更改,及时检查应用中使用了正则表达式的地方,查看是否出错。可以在开发者选项中关闭兼容模式,方便将有问题的地方查找出来,具体的兼容模式开关在 系统 > 高级 > 开发者选项 > 应用兼容性变更 这里(原生系统在这里,其他厂商就不好说了),并在 list 中选中自己的 App 即可关闭或打开。在这里就是需要在 list 中滑到最底下的
Enabled for targetSdkVersion >= 34
的地方,找到 DISALLOW_INVALID_GROUP_REFERENCE 选项切换; - UUID 处理:在验证输入参数时,
java.util.UUID.fromString()
方法会进行更严格的检查,因此可能会在反序列化时抛出IllegalArgumentException
异常。自测方法同上,需要在 应用兼容性变更 下把 ENABLE_STRICT_VALIDATION 选项切换一下; - ProGuard 出现的问题:在一些情况下使用
ProGuard
进行压缩,混淆,优化代码时,在添加了java.lang.ClassValue
之后会出现问题。此问题是因为一个 Kotlin 库改变了运行时的行为,即在执行Class.forName("java.lang.ClassValue")
是否会返回一个 class 而引发的,如果应用是针对没有java.lang.ClassValue
的旧版本开发的,那么这些优化会从java.lang.ClassValue
派生的类中删除computeValue
方法。
小结:JDK17 虽然会向下兼容,但有空还是升级一下比较好,毕竟有许多新的写法和优化。
2. 安全性
Android14 对安全性也有了更高的要求,这也是近几年来 Google 一直在关注的方向。
2.1 对隐式 Intent 和 PendingIntent 的限制
隐式 Intent(Implicit Intent)是 Android 应用程序组件之间进行通信的一种机制,它不明确指定要启动哪个组件,而是声明要执行的操作。系统会查找能够处理这个操作的组件,并启动它们。隐式 Intent 主要用于在应用程序内或与其他应用程序之间触发各种操作,如启动活动、启动服务、发送广播等。比较常见的例子就是先在 Manifest 文件中设置某个 Activity 中 intent-filter 的 action,然后可以通过设置启动 Intent 中的 action 来匹配这个 Activity 从而启动它.
- 隐式 Intent 只能传递给
android:exported="true"
的组件(四大组件:Activity、Service···)。所以在 App 中使用 Intent 传递数据要么使用显式 Intent 传递给android:exported="false"
的组件;要么使用隐式 Intent 传递给android:exported="true"
的组件。当然显式 Intent 传给 exported="true" 肯定也是可以的。 - 一个可变的
PendingIntent
必须设置 packageName,否则会抛出异常。
举个栗子:
// code 5 <!-- android:exported 设置为false,隐式 Intent 无法启动 --> <activity android:name=".AppActivity" android:exported="false"> <intent-filter> <action android:name="com.example.action.SEND" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity>
// code 6 // Throws an exception when targeting Android 14. 直接用隐式的 Intent 调用不管用 context.startActivity(Intent("com.example.action.SEND")) // This makes the intent explicit. 当设置了 intent 中的 package 参数时就可以了 val explicitIntent = Intent("com.example.action.SEND") explicitIntent.apply { `package` = context.packageName } context.startActivity(explicitIntent)
小结:这个变更得重视,特别是 android:exported 设置为 false 的组件,启动这些组件可能会崩溃,需要修改。这个更新还是为了安全,因为这些更改可以防止恶意应用拦截应用内部组件使用的隐式 Intent 。
2.2 动态广播接收器必须指定导出的行为
动态注册的广播接收器必须设置一个标记,用于表明接收器是否被导出到设备上的所有 App。标记位是 RECEIVER_EXPORTED
或 RECEIVER_NOT_EXPORTED
。早在 Android13 就引入了这个功能,可以让应用程序指定一个已注册的广播接收器是否应该被导出,并对设备上的其他应用可见。
只不过在 Android14 上变成了“必须设置”。而在以前的 Android 版本中,设备上的任何应用都可以向动态注册的广播接收器发送未受保护的广播,除非该接收器有签名许可。
举个栗子,在 A 应用中注册 AlarmReceiver 并发送广播:
// code 7 val filter = IntentFilter("alarmReceiver_custom_action") val listenToBroadcastsFromOtherApps = true val receiverFlags = if (listenToBroadcastsFromOtherApps) { ContextCompat.RECEIVER_EXPORTED // 该接收器对其他应用开放 } else { ContextCompat.RECEIVER_NOT_EXPORTED // 该接收器不对其他应用开放 } // 这里的 registerReceiver 方法必须设置 receiverFlags 参数 registerReceiver(requireContext(), AlarmReceiver(), filter, receiverFlags) // 发送广播 val intent = Intent("alarmReceiver_custom_action") // 方式1 //val intent = Intent(requireActivity(), AlarmReceiver::class.java) // 方式2 requireActivity().sendBroadcast(intent)
在其他的应用中只能通过 code7 中的方式1发送广播,如果 A 应用的 listenToBroadcastsFromOtherApps
设置为 true,那么在 A 应用就能收到其他应用通过方式1发送的广播信息了,否则无法收到。
在实践中还发现,如果 A 应用也通过方式1发送自己应用内部的广播,且设置 ContextCompat.RECEIVER_NOT_EXPORTED,那么这个广播是无法收到的,感兴趣的同学可以试试。
如果应用程序只是通过 Context#registerReceiver
方法 (比如 Context#registerReceiver()
)为系统广播注册接收器,那么它可以不在注册接收器时指定该标志。
小结:动态广播的注册方法改了,需要设置是否对其他应用可见,这跟 android:exported 的设置是一样的道理。其实本地广播和全局广播的功能和这个一样,只不过在 targetSdkVersion >= 34 上更加重视了。
2.3 更安全的动态代码加载
所有动态加载的文件都必须标记为只读。否则,系统将抛出异常。官方建议应用尽可能避免动态加载代码,因为这样做会大大增加应用被代码注入或代码篡改破坏的风险。
如必须动态加载代码,则需要将动态加载的文件(如 DEX、JAR 或 APK 文件)在文件打开并写入任何内容之前设置为只读:
// code 8 val jar = File("DYNAMICALLY_LOADED_FILE.jar") val os = FileOutputStream(jar) os.use { // Set the file to read-only first to prevent race conditions jar.setReadOnly() // Then write the actual file content } val cl = PathClassLoader(jar.absolutePath, parentClassLoader)
此外,为防止系统对现在已有的动态加载文件抛出异常,官方建议先删除并重新创建文件,然后再尝试在应用中重新动态加载这些文件。重新创建文件时,请按照上述指南在写入时将文件标记为只读。或者,可将现有文件重新标记为只读,但在这种情况下,官方建议先验证文件的完整性(例如,对照可信值检查文件的签名)以保护应用免遭恶意操作的影响。
2.4 Zip 路径遍历
针对 Android14 的应用,Android 系统通过以下方式防止 Zip 路径遍历的漏洞:如果 zip 文件条目名称包含 “..” 或以 “/” 开头,则 ZipFile(String)
和 ZipInputStream.getNextEntry()
会抛出一个 ZipException
异常。
如果不想抛出异常且文件名称又不能改,可以通过调用 dalvik.system.ZipPathValidator.clearCallback()
选择退出验证。当然这是不推荐的。
Zip 路径遍历漏洞:指恶意攻击者通过构造含有 "../" 或以 "/" 开头的文件路径,在解压缩 Zip 文件时可以访问 Zip 文件之外的文件系统上的任意文件或目录,从而对应用程序造成安全风险的漏洞。
2.5 后台启动 Activity 新增限制
在 Android14 上系统进一步限制了 App 从后台启动 Activity 的情况:
- 当 App 使用
PendingIntent#send()
或类似方法发送PendingIntent
时,必须选择是否要授予自己的后台 Activity 启动的权限来发送PendingIntent
。如果选择授权,则需要通过setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
方法返回并传递一个ActivityOptions
对象。 - 当一个前台可见应用使用
bindService()
方法绑定另一个后台应用的 Service 时,这个可见应用现在必须选择是否将自己的后台 Activity 启动权限授予被绑定的服务。如果选择授权,应用在调用bindService()
方法时需要设置BIND_ALLOW_ACTIVITY_STARTS
标志。
这些变化扩展了现有的限制集,通过防止恶意应用程序滥用 API 从后台启动破坏性 Activity 来保护用户。
小结:针对后台启动控制得更严格了,如果项目中有相关逻辑,建议跑一跑看能否后台启动,如有问题再对照上面的内容进行修改,硬啃实在是不知道说的啥意思。。。
3. 有关限制非 SDK 接口的更新
Android14 更新了受限的非 SDK 接口列表(基于与 Android 开发者之间的协作以及最新的内部测试使用的 API 列表)。在限制使用非 SDK 接口之前,官方会尽可能确保有可用的公开替代方案。
如果应用并非以 Android14 为目标平台,其中一些变更可能不会立即对应用产生影响。但只要 App 使用任何非 SDK 方法或字段,终归存在导致应用出问题的显著风险。
一般而言,公共 SDK 接口是在 Android 框架 软件包索引(https://developer.android.google.cn/reference/packages) 中记录的那些接口。非 SDK 接口的处理是 API 抽象出来的实现细节,因此这些接口可能会在不另行通知的情况下随时发生更改。
如果不确定自己的应用是否使用了非 SDK 接口,则可以在 Debug 模式下运行测试 App,如果该应用访问了某些非 SDK 接口,系统就会输出一条日志消息。可以检查应用的日志消息,查找以下详细信息:
1)声明的类、名称和类型(采用 Android 运行时所使用的格式);
2)访问方式:链接、反射或 JNI;
3)所访问的非 SDK 接口属于哪个名单;
还可以使用 adb logcat 来查看这些日志消息,这些消息显示在所运行应用的 PID 下。举例而言,日志中可能包含如下条目:
Accessing hidden field Landroid/os/Message;->flags:I (light greylist, JNI)
如果应用依赖于非 SDK 接口,应该开始计划迁移到 SDK 的替代方案。如果无法为应用中的某项功能找到使用非 SDK 接口的替代方案,应向官方请求新的公共 API。
如需查看 Android14 的所有非 SDK 接口的完整列表,可下载查看以下文件:hiddenapi-flags.csv(https://dl.google.…,这个表格文件内容很多,可用于查询。
小结:普通应用开发者一般情况下也不会用到非 SDK 接口,这个可忽略。
以上就是本篇的所有内容,可以看出,现有的 App 如果直接将 targetSdkVersion 升级到 34(Android14)的话还是有些地方需要注意并进行修改测试的。如果还想了解 Android14 新增了哪些功能,欢迎关注我,咱们下篇见!
更多内容,欢迎关注公众号:修之竹 或者查看 修之竹的 Android 专辑
赞人玫瑰,手留余香!欢迎点赞、转发~ 转发请注明出处~
参考文献
- Android 14 官方文档 https://developer.android.com/about/versions/14
- developer.android.google.cn/about/versi…
- Android 14 快速适配要点; 恋猫de小郭; https://juejin.cn/post/7231835495557890106?searchId=202307240025039D8229C74EA62159077B
- developer.android.google.cn/guide/compo…
- developer.android.com/about/versi…
- developer.android.google.cn/about/versi…
- developer.android.google.cn/about/versi…
- developer.android.google.cn/about/versi…