行为变更:所有应用

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

此外,请务必查看仅影响以 Android 12 为目标平台的应用的行为变更列表。

用户体验

滚动效果

滚动事件的行为在 Android 12 中发生了变化。如需了解详情,请参阅滚动效果

前台服务通知用户体验延迟

为了在 Android 12 上提供针对短时间运行的前台服务的流畅体验,系统可以为某些前台服务延迟 10 秒显示前台服务通知。此更改使某些短期任务在显示通知之前完成。

如果某项前台服务至少具有以下特征之一,则系统会在服务启动后立即显示相关通知:

  • 该服务与包含操作按钮的通知相关联。
  • 该服务的 foregroundServiceTypemediaPlaybackmediaProjectionphoneCall
  • 该服务根据通知的类别属性中的定义,提供与通话、导航或媒体播放相关的用例。
  • 该服务通过在设置通知时将 FOREGROUND_SERVICE_IMMEDIATE 传入 setForegroundServiceBehavior(),已停用行为变更。

沉浸模式下的手势导航改进

Android 12 简化了沉浸模式,使手势导航更简单且与其余活动体验(例如观看视频和阅读图书)更加一致。如需了解详情,请参阅功能页面上的相应条目。

网络 intent 解析

为了在用户选择网页链接时提供更流畅的体验,如果某个给定的网络 intent 包含未批准的网域,Android 12 会在用户的默认浏览器中打开该 intent。您的应用可以使用以下某种方法来获准处理网域:

  • 使用 Android App Links 验证网域。
  • 请求用户将您的应用与网域相关联。

如果您的应用调用网络 intent,不妨考虑添加一个提示或对话框,要求用户确认操作。

详细了解网络 intent 解析的变更

限制性应用待机模式存储分区

应用待机模式存储分区有助于系统根据使用应用的时间新近度和频率来确定应用的资源请求的优先级。

每个存储分区代表一个优先级。低优先级意味着系统会对运行您的应用施加更多限制。

从 Android 12 开始,有一个名为“受限”的新存储分区。在所有存储分区中,受限存储分区的优先级最低(限制最高)。存储分区按优先级从高到低的顺序排列如下:

  1. 活跃:应用目前正在使用中,或者最近刚刚使用过
  2. 工作集:会定期使用应用
  3. 常用:会经常使用应用,但不是每天都使用
  4. 极少使用:不经常使用应用
  5. 受限

除了使用模式之外,系统还会考虑应用的行为,以决定是否要将您的应用放在受限存储分区中。如果您的应用更负责地使用系统资源,就不太可能被放在受限存储分区中。

如果用户直接与您的应用互动,系统会将其放在一个限制较少的存储分区中。

电源管理限制

如果系统将您的应用放在受限存储分区中,会受到以下限制:

  • 您每天可以在 10 分钟的批处理会话中运行作业一次。在此会话期间,系统会将您应用的作业与其他应用的作业分组在一起。
  • 与系统将您的应用放在限制较少的存储分区中相比,您的应用可以运行较少的加急作业
  • 您应用的不精确的闹钟每天传送一次。您在调用 set()setInexactRepeating()setAndAllowWhileIdle()setWindow() 方法时创建不精确的闹钟。
  • 您的应用每天可以及时接收五条高优先级 Firebase Cloud Messaging (FCM) 消息。所有后续 FCM 消息都按普通优先级传送,因此如果设备在节能模式下,这些消息可能会延迟。

允许运行前台服务

如果系统将您的应用放在受限存储分区中,您的应用仍可运行前台服务。不过,如果您的应用以 Android 12 为目标平台,它仍会受到前台服务启动限制的影响。

检查您的应用是否在受限存储分区中

如需检查系统是否已将您的应用放在受限存储分区中,请调用 getAppStandbyBucket()。如果此方法的返回值为 STANDBY_BUCKET_RESTRICTED,则您的应用在受限存储分区中。

测试受限存储分区行为

如需测试您的应用在系统将其放在受限存储分区中时的行为,您可以手动将您的应用移至该存储分区。为此,请在终端窗口中运行以下命令:

adb shell am set-standby-bucket PACKAGE_NAME restricted

Display#getRealSize 和 getRealMetrics:废弃和沙盒

Android 设备有许多不同的外形规格,如大屏设备、平板电脑和可折叠设备。为了针对每种设备适当地呈现内容,您的应用需要确定屏幕或显示屏尺寸。随着时间的推移,Android 提供了不同的 API 来检索此信息。在 Android 11 中,我们引入了 WindowMetrics API 并废弃了以下方法:

在 Android 12 中,我们继续建议使用 WindowMetrics,并且正在逐步废弃以下方法:

为了缓解应用使用 Display API 检索应用边界的行为,Android 12 添加了一种新的沙盒机制来更正这些 API 返回的信息。这可能会对将此信息与 MediaProjection 一起使用的应用产生影响。

应用应使用 WindowMetrics API 查询其窗口的边界,并使用 Configuration.densityDpi 查询当前的密度。

为了与较低的 Android 版本实现更广泛的兼容性,您可以使用 Jetpack WindowManager 库,它包含一个 WindowMetrics 类,该类支持 Android 4.0(API 级别 14)及更高版本。

关于如何使用 WindowMetrics 的示例

首先,确保应用的 activity 完全可调整大小

activity 应依赖于来自 activity 上下文的 WindowMetrics 来执行任何与界面相关的工作,特别是 WindowManager.getCurrentWindowMetrics()

如果您的应用创建了 MediaProjection,则必须正确地调整边界的大小,因为投影会捕捉显示内容。如果应用完全可调整大小,则 activity 上下文会返回正确的边界,如下所示:

WindowMetrics projectionMetrics = activityContext
        .getSystemService(WindowManager.class).getMaximumWindowMetrics();

如果应用并非完全可调整大小,则必须从 WindowContext 实例查询边界,并使用 WindowManager.getMaximumWindowMetrics() 检索应用可用的最大显示区域的 WindowMetrics

Context windowContext = mContext.createWindowContext(mContext.getDisplay(),
        TYPE_APPLICATION, null /* options */);
WindowMetrics projectionMetrics = windowContext.getWindowManager()
        .getMaximumWindowMetrics();

图形和图片

改进了刷新率切换

在 Android 12 中,无论显示屏是否支持无缝过渡到新的刷新率,都会发生使用 setFrameRate() 实现的刷新率变化;无缝过渡是指没有任何视觉中断,比如一两秒钟的黑屏。以前,如果显示屏不支持无缝过渡,它在调用 setFrameRate() 后通常会继续使用同一刷新率。您可以调用 getAlternativeRefreshRates() 来提前确定向新刷新率的过渡是否有可能是无缝过渡。通常,会在刷新率切换完成后调用回调 onDisplayChanged(),但对于某些外接显示屏,会在非无缝过渡期间调用该回调。

以下示例说明了您可以如何实现此行为:

Kotlin

// Determine whether the transition will be seamless.
// Non-seamless transitions may cause a 1-2 second black screen.
val refreshRates = this.display?.mode?.alternativeRefreshRates
        val willbeSeamless = Arrays.asList<FloatArray>(refreshRates).contains(newRefreshRate)

// Set the frame rate even if the transition will not be seamless.
surface.setFrameRate(newRefreshRate, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS)

Java

// Determine whether the transition will be seamless.
// Non-seamless transitions may cause a 1-2 second black screen.
Display display = context.getDisplay(); // API 30+
Display.Mode mode = display.getMode();
float[] refreshRates = mode.getAlternativeRefreshRates();
boolean willbeSeamless = Arrays.asList(refreshRates).contains(newRefreshRate);

// Set the frame rate even if the transition will not be seamless.
surface.setFrameRate(newRefreshRate, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS);

安全和隐私设置

麦克风和摄像头切换开关

快捷设置图块标有“摄像头使用权限”和“麦克风使用权限”
图 1. “快捷设置”中的麦克风和摄像头切换开关。

在 Android 12 中,受支持的设备允许用户通过按一个切换开关选项,为设备上的所有应用启用和停用摄像头和麦克风使用权限。用户可以从快捷设置访问可切换的选项(如图 1 所示),也可以从系统设置中的“隐私设置”屏幕访问。

摄像头和麦克风切换开关会影响设备上的所有应用:

  • 当用户关闭摄像头使用权限后,应用会收到空白的摄像头画面。
  • 当用户关闭麦克风使用权限后,应用会收到无声音频。此外,无论您是否声明 HIGH_SAMPLING_RATE_SENSORS 权限,移动传感器都有采样率限制

当用户关闭摄像头或麦克风的使用权限,然后启动需要使用摄像头或麦克风信息的应用时,系统会提醒用户,设备范围的切换开关已关闭。

检查给定的设备是否支持麦克风和摄像头切换开关

如需检查设备是否支持麦克风和摄像头切换开关,请添加以下代码段中所示的逻辑:

Kotlin

val sensorPrivacyManager = applicationContext
        .getSystemService(SensorPrivacyManager::class.java)
        as SensorPrivacyManager
val supportsMicrophoneToggle = sensorPrivacyManager
        .supportsSensorToggle(Sensors.MICROPHONE)
val supportsCameraToggle = sensorPrivacyManager
        .supportsSensorToggle(Sensors.CAMERA)

Java

SensorPrivacyManager sensorPrivacyManager = getApplicationContext()
        .getSystemService(SensorPrivacyManager.class);
boolean supportsMicrophoneToggle = sensorPrivacyManager
        .supportsSensorToggle(Sensors.MICROPHONE);
boolean supportsCameraToggle = sensorPrivacyManager
        .supportsSensorToggle(Sensors.CAMERA);

检查响应麦克风和摄像头切换开关的应用行为

麦克风和摄像头切换开关不应影响您的应用处理 CAMERARECORD_AUDIO 权限的方式,前提是您遵循关于 Android 权限的最佳做法

特别是,确保您的应用做到以下几点:

  • 等到用户向您的应用授予 CAMERA 权限后再使用设备的摄像头。
  • 等到用户向您的应用授予 RECORD_AUDIO 权限后再使用设备的麦克风。

麦克风和摄像头指示标志

右上角的圆角矩形,其中包含摄像头图标和麦克风图标
图 2. 麦克风和摄像头指示标志,显示了最近的数据访问。

在搭载 Android 12 的设备上,当应用使用麦克风或摄像头时,图标会出现在状态栏中。如果应用处于沉浸模式,则图标会出现在屏幕的右上角。用户可以打开“快捷设置”,并选择图标以查看哪些应用当前正在使用麦克风或摄像头。图 2 显示了包含图标的示例屏幕截图。

为了提供更好的用户体验,在用户明确向您的应用授予权限之前,请勿使用麦克风或摄像头。

应用无法关闭系统对话框

为了加强用户与应用和系统互动时的控制,从 Android 12 开始,弃用了 ACTION_CLOSE_SYSTEM_DIALOGS intent 操作。除了一些特殊情况之外,当应用尝试调用包含此操作的 intent 时,系统会基于应用的目标 SDK 版本执行以下操作之一:

  • 如果应用以 Android 12 为目标平台,则会发生 SecurityException
  • 如果应用以 Android 11(API 级别 30)或更低版本为目标平台,则系统不会执行 intent,并且 Logcat 中会显示以下消息:

    E ActivityTaskManager Permission Denial: \
    android.intent.action.CLOSE_SYSTEM_DIALOGS broadcast from \
    com.package.name requires android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS, \
    dropping broadcast.
    

异常

在以下情况下,应用仍然可以在 Android 12 上关闭系统对话框:

  • 您的应用运行的是插桩测试
  • 您的应用以 Android 11 或更低版本为目标平台,并在抽屉式通知栏顶部显示一个窗口。

  • 您的应用以 Android 11 或更低版本为目标平台。此外,用户已与通知互动,可能使用了通知的操作按钮,您的应用正在处理服务广播接收器来响应该用户操作。

  • 您的应用以 Android 11 或更低版本为目标平台并且具有有效的无障碍服务。如果您的应用以 Android 12 为目标平台并且想要关闭通知栏,请改用 GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE 无障碍操作。

不受信任的触摸事件被屏蔽

为了维持系统安全并保持良好的用户体验,Android 12 会阻止应用使用触摸事件,使用触摸事件时叠加层会以不安全的方式遮掩应用。 换言之,系统会屏蔽穿透某些窗口的触摸操作,但有一些例外情况

受影响的应用

此变更会影响选择让触摸操作穿透其窗口的应用,例如使用 FLAG_NOT_TOUCHABLE 标志。包括但不限于以下示例:

异常

在以下情况下,允许执行“穿透”触摸操作:

  • 应用中的互动。您的应用会显示叠加层,并且只有当用户与您的应用进行互动时才会显示叠加层。
  • 可信窗口。包括但不限于以下窗口:

  • 不可见窗口。窗口的根视图是 GONEINVISIBLE

  • 全透明窗口。窗口的 alpha 属性为 0.0。

  • 足够半透明的系统警报窗口。当组合后的不透明度小于或等于系统针对触摸的最大遮掩不透明度时,系统会将一组系统警报窗口视为足够半透明。在 Android 12 Beta 版中,这一最大不透明度为 0.8。此值在未来的 Beta 版中可能会发生变化。

在不受信任的触摸操作被屏蔽时能够检测到

如果系统屏蔽触摸操作,Logcat 会记录以下消息:

Untrusted touch due to occlusion by PACKAGE_NAME

测试变更

在搭载 Android 12 开发者预览版 3 的设备上,默认情况下会屏蔽不受信任的触摸操作。如需允许不受信任的触摸操作,请在终端窗口中运行以下 ADB 命令

# A specific app
adb shell am compat disable BLOCK_UNTRUSTED_TOUCHES com.example.app

# All apps
# If you'd still like to see a Logcat message warning when a touch would be
# blocked, use 1 instead of 0.
adb shell settings put global block_untrusted_touches 0

如需将行为还原为默认设置(不受信任的触摸操作被屏蔽),请运行以下命令:

# A specific app
adb shell am compat reset BLOCK_UNTRUSTED_TOUCHES com.example.app

# All apps
adb shell settings put global block_untrusted_touches 2

权限软件包可见性

在搭载 Android 12 的设备上,根据应用对其他应用的软件包可见性,以 Android 11(API 级别 30)或更高版本为目标平台且调用以下某种方法的应用会收到一组过滤后的结果:

移除了 Bouncy Castle 实现

Android 12 移除了之前废弃的加密算法(包括所有 AES 算法)的许多 BouncyCastle 实现。系统改用这些算法的 Conscrypt 实现。

如果符合以下任何条件,则此变更会影响您的应用:

  • 您的应用使用 512 位的密钥大小。Conscrypt 不支持此密钥大小。如有必要,请更新您应用的加密逻辑以使用其他密钥大小。
  • 您的应用将无效的密钥大小与 KeyGenerator 一起使用。与 BouncyCastle 相比,Conscrypt 的 KeyGenerator 实现会对密钥参数执行额外的验证。例如,Conscrypt 不允许您的应用生成 64 位 AES 密钥,因为 AES 仅支持 128 位、192 位和 256 位密钥。

    BouncyCastle 允许生成大小无效的密钥,但如果稍后这些密钥与 Cipher 一起使用,验证会失败。如果使用 Conscrypt,验证失败的时间会更早。

  • 您使用并非 12 字节的大小初始化伽罗瓦/计数器模式 (GCM) 加密。Conscrypt 的 GcmParameterSpec 实现要求初始化为 12 字节,这是 NIST 推荐的做法。

剪贴板访问通知

在 Android 12 中,当某个应用首次调用 getPrimaryClip() 以访问来自其他应用的 ClipData 时,系统会显示一条消息框消息,通知用户此次剪贴板访问。

消息框消息内的文本包含以下格式:APP pasted from your clipboard.

检索剪贴说明时未显示消息

您的应用可能会调用 getPrimaryClipDescription() 以接收有关剪贴板上当前数据的信息。当您的应用调用此方法时,系统不会显示消息框消息。

Android 12 增强了此方法以检测下面这些额外的详细信息:

连接性

Passpoint 更新

Android 12 中添加了以下 API:

  • isPasspointTermsAndConditionsSupported():“条款及条件”是一项 Passpoint 功能,允许网络部署将不安全的强制门户(使用开放网络)替换为安全的 Passpoint 网络。当要求用户接受条款及条件时,系统会向用户显示一条通知。如果应用建议的 Passpoint 网络受条款及条件制约,应用必须先调用此 API,以确保设备支持该功能。如果设备不支持该功能,就不能连接到此网络,并且必须建议一个替代网络或旧网络。
  • isDecoratedIdentitySupported():对带有前缀修饰的网络进行身份验证时,修饰的身份前缀允许网络运营商更新网络访问标识符 (NAI),以通过 AAA 网络内的多个代理执行显式路由(如需详细了解这一点,请参阅 RFC 7542)。

    Android 12 实现了此功能,以符合 PPS-MO 扩展的 WBA 规范。如果应用建议的 Passpoint 网络需要修饰的身份,应用必须先调用此 API,以确保设备支持该功能。如果设备不支持该功能,身份就不会进行修饰,并且对网络的身份验证可能会失败。

如需创建 Passpoint 建议,应用必须使用 PasspointConfigurationCredentialHomeSp 类。这些类描述了 Wi-Fi Alliance Passpoint 规范中定义的 Passpoint 配置文件。

更新后的非 SDK 接口限制

Android 12 包含更新后的受限制非 SDK 接口列表(基于与 Android 开发者之间的协作以及最新的内部测试)。在限制使用非 SDK 接口之前,我们会尽可能确保有可用的公开替代方案。

如果您的应用并非以 Android 12 为目标平台,其中一些变更可能不会立即对您产生影响。然而,虽然您目前仍可以使用一些非 SDK 接口(具体取决于应用的目标 API 级别),但只要您使用任何非 SDK 方法或字段,终归存在导致应用出问题的显著风险。

如果您不确定自己的应用是否使用了非 SDK 接口,则可以测试您的应用来进行确认。如果您的应用依赖于非 SDK 接口,您应该开始计划迁移到 SDK 替代方案。然而,我们知道某些应用具有使用非 SDK 接口的有效用例。如果您无法为应用中的某项功能找到使用非 SDK 接口的替代方案,应请求新的公共 API

如需详细了解此 Android 版本中的变更,请参阅 Android 12 中有关限制非 SDK 接口的更新。如需全面了解有关非 SDK 接口的详细信息,请参阅对非 SDK 接口的限制