行为变更:面向 Android 14 或更高版本的应用

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

务必同时查看影响在 Android 14 上运行的所有应用的行为变更列表,无论应用的 targetSdkVersion 是多少。

核心功能

需要前台服务类型

如果您的应用以 Android 14(API 级别 34)或更高版本为目标,则必须为应用中的每个前台服务指定至少一种前台服务类型。您应选择一种代表您应用用例的前台服务类型。系统期望具有特定类型的前台服务能满足特定用例。

如果您应用中的用例与这些类型中的任何一个都不关联,强烈建议您迁移逻辑以使用 WorkManager用户发起的数据传输作业

BluetoothAdapter 中 BLUETOOTH_CONNECT 权限的强制执行

对于目标 SDK 版本为 Android 14(API 级别 34)或更高的应用,Android 14 在调用 BluetoothAdapter getProfileConnectionState() 方法时强制执行 BLUETOOTH_CONNECT 权限。

此方法已经要求 BLUETOOTH_CONNECT 权限,但之前并未强制执行。请确保您的应用在 AndroidManifest.xml 文件中声明了 BLUETOOTH_CONNECT,如以下代码段所示,并在调用 getProfileConnectionState 之前检查用户是否已授予该权限

<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

OpenJDK 17 更新

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

其中一些变更可能会影响应用兼容性

  • 正则表达式的变更:无效的分组引用现在被禁止,以更严格地遵循 OpenJDK 的语义。您可能会看到 java.util.regex.Matcher 类抛出 IllegalArgumentException 的新情况,因此请确保测试您的应用中使用正则表达式的区域。在测试时启用或禁用此变更,可以使用兼容性框架工具切换 DISALLOW_INVALID_GROUP_REFERENCE 标志。
  • UUID 处理java.util.UUID.fromString() 方法现在在验证输入参数时会进行更严格的检查,因此在反序列化期间您可能会看到 IllegalArgumentException。在测试时启用或禁用此变更,可以使用兼容性框架工具切换 ENABLE_STRICT_VALIDATION 标志。
  • ProGuard 问题:在某些情况下,添加 java.lang.ClassValue 类会导致问题,如果您尝试使用 ProGuard 缩小、混淆和优化您的应用。问题源于一个 Kotlin 库,它根据 Class.forName("java.lang.ClassValue") 是否返回类来改变运行时行为。如果您的应用是针对没有 java.lang.ClassValue 类可用的旧运行时版本开发的,那么这些优化可能会从派生自 java.lang.ClassValue 的类中移除 computeValue 方法。

JobScheduler 强化回调和网络行为

自推出以来,JobScheduler 要求您的应用在几秒钟内从 onStartJobonStopJob 返回。在 Android 14 之前,如果作业运行时间过长,作业会停止并静默失败。如果您的应用以 Android 14(API 级别 34)或更高版本为目标,并且超出主线程上授予的时间,则应用会触发 ANR,并显示错误消息“No response to onStartJob”或“No response to onStopJob”。

此 ANR 可能是以下两种情况的结果:1. 有工作阻塞了主线程,阻止了回调 onStartJobonStopJob 在预期时间限制内执行和完成。2. 开发人员在 JobScheduler 回调 onStartJobonStopJob 中运行阻塞工作,阻止回调在预期时间限制内完成。

为了解决问题 #1,您需要进一步调试 ANR 发生时主线程被什么阻塞,您可以使用 ApplicationExitInfo#getTraceInputStream() 来获取 ANR 发生时的崩溃跟踪。如果您能够手动重现 ANR,您可以记录系统跟踪,并使用 Android StudioPerfetto 检查跟踪,以便更好地了解 ANR 发生时主线程正在运行什么。请注意,这在使用 JobScheduler API 或 androidx 库 WorkManager 时都可能发生。

为了解决问题 #2,请考虑迁移到 WorkManager,它支持在异步线程中封装 onStartJobonStopJob 中的任何处理。

JobScheduler 还引入了一个要求,即在使用 setRequiredNetworkTypesetRequiredNetwork 约束时声明 ACCESS_NETWORK_STATE 权限。如果您的应用在调度作业时未声明 ACCESS_NETWORK_STATE 权限,并且目标 SDK 版本为 Android 14 或更高,则会导致 SecurityException

磁贴启动 API

对于面向 14 及更高版本的应用,TileService#startActivityAndCollapse(Intent) 已废弃,并且现在调用时会抛出异常。如果您的应用从磁贴启动活动,请改用 TileService#startActivityAndCollapse(PendingIntent)

隐私

照片和视频的部分访问权限

Android 14 引入了“选择照片访问权限”,允许用户授予应用对其库中特定图像和视频的访问权限,而不是授予对给定类型的所有媒体的访问权限。

此变更仅在您的应用以 Android 14(API 级别 34)或更高版本为目标时启用。如果您尚未使用照片选择器,我们建议在您的应用中实现它,以便提供一致的图像和视频选择体验,同时在无需请求任何存储权限的情况下增强用户隐私。

如果您使用存储权限维护自己的图库选择器,并且需要完全控制您的实现,请调整您的实现以使用新的 READ_MEDIA_VISUAL_USER_SELECTED 权限。如果您的应用不使用新权限,系统会以兼容模式运行您的应用。

用户体验

安全的全屏 Intent 通知

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

全屏 Intent 通知旨在用于需要用户立即注意的极高优先级通知,例如来电或用户配置的闹钟设置。对于面向 Android 14(API 级别 34)或更高版本的应用,允许使用此权限的应用仅限于提供呼叫和闹钟功能的应用。Google Play 商店会撤销任何不符合此配置文件应用的默认 USE_FULL_SCREEN_INTENT 权限。这些政策变更的截止日期是 2024 年 5 月 31 日

对于用户更新到 Android 14 之前已安装在手机上的应用,此权限仍然启用。用户可以打开和关闭此权限。

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

安全性

隐式和待处理 Intent 的限制

对于面向 Android 14(API 级别 34)或更高版本的应用,Android 以以下方式限制应用向内部应用组件发送隐式 Intent

  • 隐式 Intent 仅传递给已导出的组件。应用必须使用显式 Intent 传递给未导出的组件,或将组件标记为已导出。
  • 如果应用创建的待处理 Intent 不指定组件或软件包,系统会抛出异常。

这些更改可防止恶意应用拦截旨在由应用内部组件使用的隐式 Intent。

例如,以下是您应用清单文件中可以声明的Intent 过滤器

<activity
    android:name=".AppActivity"
    android:exported="false">
    <intent-filter>
        <action android:name="com.example.action.APP_ACTION" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

如果您的应用尝试使用隐式 Intent 启动此活动,将抛出 ActivityNotFoundException 异常

Kotlin

// Throws an ActivityNotFoundException exception when targeting Android 14.
context.startActivity(Intent("com.example.action.APP_ACTION"))

Java

// Throws an ActivityNotFoundException exception when targeting Android 14.
context.startActivity(new Intent("com.example.action.APP_ACTION"));

要启动未导出的活动,您的应用应改用显式 Intent

Kotlin

// This makes the intent explicit.
val explicitIntent =
        Intent("com.example.action.APP_ACTION")
explicitIntent.apply {
    package = context.packageName
}
context.startActivity(explicitIntent)

Java

// This makes the intent explicit.
Intent explicitIntent =
        new Intent("com.example.action.APP_ACTION")
explicitIntent.setPackage(context.getPackageName());
context.startActivity(explicitIntent);

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

面向 Android 14(API 级别 34)或更高版本并使用上下文注册接收器的应用和服务,要求指定一个标志来指示接收器是否应导出到设备上的所有其他应用:分别是 RECEIVER_EXPORTEDRECEIVER_NOT_EXPORTED。此要求通过利用Android 13 中为这些接收器引入的功能,有助于保护应用免受安全漏洞的侵害。

仅接收系统广播的接收器的例外情况

如果您的应用仅通过 Context#registerReceiver 方法(例如 Context#registerReceiver())注册接收器以接收系统广播,则在注册接收器时不应指定标志。

更安全的动态代码加载

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

如果您必须动态加载代码,请使用以下方法在文件打开后立即(在写入任何内容之前)将动态加载的文件(例如 DEX、JAR 或 APK 文件)设置为只读

Kotlin

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, parentClassLoader)

Java

File jar = new File("DYNAMICALLY_LOADED_FILE.jar");
try (FileOutputStream os = new FileOutputStream(jar)) {
    // Set the file to read-only first to prevent race conditions
    jar.setReadOnly();
    // Then write the actual file content
} catch (IOException e) { ... }
PathClassLoader cl = new PathClassLoader(jar, parentClassLoader);

处理已存在的动态加载文件

为了防止对现有动态加载文件抛出异常,我们建议在您尝试在应用中再次动态加载文件之前,先删除并重新创建这些文件。在重新创建文件时,请遵循上述指南,在写入时将文件标记为只读。另外,您可以将现有文件重新标记为只读,但在这种情况下,我们强烈建议您首先验证文件的完整性(例如,通过检查文件的签名与受信任的值是否匹配),以帮助保护您的应用免受恶意行为的侵害。

启动后台活动的额外限制

对于目标 SDK 版本为 Android 14(API 级别 34)或更高的应用,系统会进一步限制应用何时允许从后台启动活动

这些更改扩展了现有的一系列限制,通过防止恶意应用滥用 API 从后台启动干扰性活动来保护用户。

Zip 路径遍历

对于面向 Android 14(API 级别 34)或更高版本的应用,Android 通过以下方式防止 Zip 路径遍历漏洞:如果 zip 文件条目名称包含“..”或以“/”开头,则 ZipFile(String)ZipInputStream.getNextEntry() 会抛出 ZipException

应用可以通过调用 dalvik.system.ZipPathValidator.clearCallback() 来选择退出此验证。

对于目标 SDK 版本为 Android 14(API 级别 34)或更高版本的应用,在以下任一场景中,MediaProjection#createVirtualDisplay 会抛出 SecurityException

您的应用必须在每次捕获会话之前请求用户同意。单个捕获会话是对 MediaProjection#createVirtualDisplay 的单次调用,并且每个 MediaProjection 实例只能使用一次。

处理配置变更

如果您的应用需要调用 MediaProjection#createVirtualDisplay 来处理配置变更(例如屏幕方向或屏幕尺寸变更),您可以按照以下步骤更新现有 MediaProjection 实例的 VirtualDisplay

  1. 使用新的宽度和高度调用 VirtualDisplay#resize
  2. VirtualDisplay#setSurface 提供一个具有新宽度和高度的 Surface

注册回调

您的应用应注册一个回调,以处理用户不同意继续捕获会话的情况。为此,请实现 Callback#onStop,并让您的应用释放任何相关资源(例如 VirtualDisplaySurface)。

如果您的应用未注册此回调,当您的应用调用 MediaProjection#createVirtualDisplay 时,它会抛出 IllegalStateException

更新的非 SDK 限制

Android 14 包含根据与 Android 开发人员的合作和最新的内部测试更新的受限制非 SDK 接口列表。我们尽可能确保在限制非 SDK 接口之前提供公共替代方案。

如果您的应用不以 Android 14 为目标,则其中一些更改可能不会立即影响您。然而,虽然您目前可以使用某些非 SDK 接口(取决于您应用的 Target API 级别),但使用任何非 SDK 方法或字段始终存在破坏您应用的较高风险。

如果您不确定您的应用是否使用非 SDK 接口,您可以测试您的应用以找出答案。如果您的应用依赖于非 SDK 接口,您应该开始计划迁移到 SDK 替代方案。尽管如此,我们理解某些应用在使用非 SDK 接口方面有合理的用例。如果您无法为应用中的某个功能找到使用非 SDK 接口的替代方案,您应该请求新的公共 API

要了解有关此 Android 版本中更改的更多信息,请参阅Android 14 中非 SDK 接口限制的更新。要了解有关非 SDK 接口的更多信息,请参阅非 SDK 接口的限制