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

与以前的版本一样,Android 14 包含可能会影响您应用的行为变更。以下行为变更仅适用于面向 Android 14(API 级别 34)或更高版本的应用。如果您的应用面向 Android 14 或更高版本,则应在适用情况下修改您的应用以正确支持这些行为。

请务必还查看 影响在 Android 14 上运行的所有应用的行为变更列表,无论应用的 targetSdkVersion 如何。

核心功能

需要前台服务类型

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

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

在 BluetoothAdapter 中强制执行 BLUETOOTH_CONNECT 权限

对于面向 Android 14(API 级别 34)或更高版本的应用,Android 14 在调用 BluetoothAdaptergetProfileConnectionState() 方法时会强制执行 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 问题:在某些情况下,如果尝试使用 ProGuard 压缩、混淆和优化您的应用,添加 java.lang.ClassValue 类会导致问题。此问题源于一个 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 可能是以下 2 种情况的结果:1. 有工作阻塞了主线程,阻止 onStartJobonStopJob 回调在预期的时间限制内执行和完成。2. 开发人员在 JobScheduler 回调 onStartJobonStopJob 中运行阻塞工作,阻止回调在预期的时间限制内完成。

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

要解决问题 #2,请考虑迁移到 WorkManager,它提供支持,可以在异步线程中包装 onStartJobonStopJob 中的任何处理。

JobScheduler 还引入了要求,如果使用 setRequiredNetworkTypesetRequiredNetwork 约束,则必须声明 ACCESS_NETWORK_STATE 权限。如果您的应用在计划作业时未声明 ACCESS_NETWORK_STATE 权限,并且以 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。

例如,以下是在您的应用清单文件中可以声明的 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);

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

为了防止为现有动态加载文件抛出异常,我们建议在尝试再次动态加载它们之前删除并重新创建这些文件。在重新创建文件时,请遵循前面关于在写入时将文件标记为只读的指导。或者,您可以将现有文件重新标记为只读,但在这种情况下,我们强烈建议您首先验证文件的完整性(例如,通过将文件的签名与可信值进行比较),以帮助保护您的应用免受恶意操作的影响。

启动后台活动的更多限制

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

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

Zip 路径遍历

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

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

对于目标 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 接口(取决于您的应用的目标 API 级别),但使用任何非 SDK 方法或字段始终存在导致应用崩溃的高风险。

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

要详细了解此 Android 版本中的更改,请参阅 Android 14 中对非 SDK 接口限制的更新。要详细了解非 SDK 接口,请参阅 非 SDK 接口的限制