Android14新功能和行为变更

419 阅读7分钟

新功能

OpenJDK 17 更新

Android 14 将继续更新 Android 的核心库,以与最新 OpenJDK LTS 版本中的功能保持一致,包括适合应用和平台开发者的库更新和 Java 17 语言支持。

下载之前请求批准安装

安装或更新应用可能需要用户批准。 例如,当使用 REQUEST_INSTALL_PACKAGES 权限的安装程序尝试安装新应用时。在之前的 Android 版本中,应用商店只有在 APK 写入安装会话且该会话已提交之后才能请求用户批准。

从 Android 14 开始,requestUserPreapproval() 方法可让安装程序在提交安装会话之前请求用户批准。此项改进可让应用商店将任何 APK 的下载操作推迟到用户批准安装之后。此外,用户批准安装后,应用商店可以在后台下载并安装应用,而不会干扰用户。

检测用户何时截取设备屏幕截图

为了打造更加标准化的屏幕截图检测体验,Android 14 引入了可保护隐私的屏幕截图检测 API。借助此 API,应用可以按 activity 注册回调。如果用户在该 activity 可见时截取屏幕截图,系统会调用这些回调并通知用户。

其它

更多新功能请参考官方文档:Android 14 功能和 API 概览

行为变更

Android 14 平台包含一些行为变更,这些变更可能会影响您的应用。以下行为变更将影响在 Android 14 上运行的所有应用,无论采用哪种 targetSdkVersion 都不例外。您应该测试您的应用,然后根据需要进行修改,以适当地支持这些变更。

行为变更:所有应用

默认拒绝设定精确的闹钟

精确的闹钟适用于用户指定的通知,或是在确切时间需要执行的操作。从 Android 14 开始,系统不再向以 Android 13 及更高版本为目标平台的大多数新安装应用预先授予 SCHEDULE_EXACT_ALARM 权限,该权限默认处于拒绝状态。

应用只能终止自己的后台进程

从 Android 14 开始,当您的应用调用 killBackgroundProcesses() 时,该 API 只能终止您自己应用的后台进程。

关于不可关闭通知用户体验方式的变更

如果您的应用向用户显示不可关闭的前台通知,请注意:Android 14 已更改此行为,允许用户关闭此类通知。

最低可安装的目标 API 级别

从 Android 14 开始,targetSdkVersion 低于 23 的应用无法安装。要求应用满足这些最低目标 API 级别要求有助于提高用户的安全性和隐私性。

恶意软件通常会以较旧的 API 级别为目标平台,以绕过在较新版本 Android 中引入的安全和隐私保护机制。例如,有些恶意软件应用使用 targetSdkVersion 22,以避免受到 Android 6.0 Marshmallow(API 级别 23)在 2015 年引入的运行时权限模型的约束。

行为变更:以 Android 14 或更高版本为目标平台的应用

与以前的版本一样,Android 14 包含一些行为变更,这些变更可能会影响您的应用。以下行为变更仅影响以 Android 14(API 级别 34)或更高版本为目标平台的应用。如果您的应用以 Android 14 或更高版本为目标平台,您应该修改自己的应用以适当地支持这些行为(如果适用)。

前台服务类型是必需的

如果您的应用程序针对Android 14(API级别34)或更高版本,则必须为应用程序中的每个前台服务指定至少一种前台服务类型。

Android 10 引入了 foregroundServiceType XML 清单属性,可以将其包含在多项特定服务的定义中。您可以为特定服务分配多个前台服务类型。

官方文档:前台服务类型

在 BluetoothAdapter 中强制执行 BLUETOOTH_CONNECT 权限

Android 14在调用BluetoothAdapter getProfileConnectionState()方法时强制BLUETOOTH\u CONNECT权限,该方法适用于针对Android 14(API级别34)或更高版本的应用程序。

OpenJDK 17 更新

安全的全屏 intent 通知

在 Android 11(API 级别 30)中,任何应用都可以在手机处于锁定状态时使用 Notification.Builder.setFullScreenIntent 发送全屏 intent。您可以通过在 AndroidManifest 中声明 USE_FULL_SCREEN_INTENT 权限,在应用安装时自动授予此权限。

对于以 Android 14(API 级别 34)或更高版本为目标平台的应用,获准使用此权限的应用仅限于提供通话和闹钟的应用。对于不适合此资料的任何应用,Google Play 商店会撤消其默认的 USE_FULL_SCREEN_INTENT 权限。

您可以使用新 API NotificationManager.canUseFullScreenIntent 检查应用是否具有该权限;如果没有,应用可以使用新 intent ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT 启动设置页面,在该页面中,用户可以授予权限。

对隐式 intent 和待处理 intent 的限制

隐式 intent

  • 如果目标组件在 AndroidManifest.xml 中被标记为 android:exported="true",则可以使用隐私intent。
  • 如果目标组件在 AndroidManifest.xml 中被标记为 android:exported="false",则无法使用隐私intent。

待处理 intent

需要指定具体的组件或软件包来创建待处理 intent。正确做法如下:

val intent = Intent(this, ExampleActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)

在运行时注册的广播接收器必须指定导出行为

针对以 Android 14(API 级别 34)或更高版本为目标的应用和服务,如果使用了通过 context 注册的接收器,则必须指定一个标志,指明接收器是否应该导出给设备上的所有其他应用。这个标志可以是 RECEIVER_EXPORTED 或 RECEIVER_NOT_EXPORTED。

val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
val receiver = MyBroadcastReceiver()

registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED) // 或 Context.RECEIVER_NOT_EXPORTED

对于仅接收系统广播的接收器,不需要指定导出行为。

更安全的动态代码加载

如果你的应用以 Android 14(API 级别 34)或更高版本为目标,并且使用了动态代码加载(Dynamic Code Loading,DCL),所有动态加载的文件必须标记为只读。否则,系统将抛出异常。我们建议应用尽可能避免动态加载代码,因为这样做会大大增加应用被代码注入或代码篡改的风险。

动态加载代码的正确方式

如果必须动态加载代码,请按照以下方法操作,在文件打开后且在写入任何内容之前,将动态加载的文件(如 DEX、JAR 或 APK 文件)设置为只读:

示例代码

val jar = File("DYNAMICALLY_LOADED_FILE.jar")
val os = FileOutputStream(jar)
os.use {
    // 首先将文件设置为只读以防止竞争条件
    jar.setReadOnly()
    // 然后写入实际的文件内容
}
val cl = PathClassLoader(jar, parentClassLoader)

关键点

  1. 动态加载的文件必须标记为只读:以防止代码注入和篡改。
  2. 避免动态加载代码:尽可能避免使用动态代码加载,因为它增加了应用被攻击的风险。
  3. 重新创建或重新标记文件:如果需要处理已存在的动态加载文件,删除并重新创建文件或重新标记文件为只读,同时验证其完整性。

针对从后台启动 Activity 的其他限制

解释

针对从后台启动 Activity 的其他限制

对于以 Android 14(API 级别 34)或更高版本为目标的应用,系统进一步限制了应用从后台启动活动(Activity)的情况:

关键点

  1. PendingIntent 启动活动的限制

    • 使用 PendingIntent#send() 等方法时,应用必须明确选择加入后台活动启动权限。
    • 通过 ActivityOptions 设置 setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED) 来实现。
  2. bindService 启动活动的限制

    • 可见应用在绑定后台应用的服务时,必须选择加入后台活动启动权限。
    • bindService() 方法中包含 BIND_ALLOW_ACTIVITY_STARTS 标志。

示例代码

通过 PendingIntent 启动活动

val intent = Intent(this, YourActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val options = ActivityOptions.makeBasic()
options.setPendingIntentBackgroundActivityStartMode(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
pendingIntent.send(null, 0, null, null, null, null, options.toBundle())

通过 bindService 启动活动

val serviceIntent = Intent(this, YourService::class.java)
bindService(serviceIntent, serviceConnection, Context.BIND_ALLOW_ACTIVITY_STARTS)

其它

更多行为变更请参考官方文档:行为变更:以 Android 14 或更高版本为目标平台的应用