Android 12 应用适配指南4 迁移指南 

Android 12 应用适配指南4 迁移指南

迁移指南

每次发布新的 Android 版本时,都会推出一些全新的功能并引入一些行为变更,目的就在于提高 Android 的实用性、安全性和性能。在许多情况下,您的应用都可以直接使用并完全按预期运行;而在其他的一些情况下,您可能需要对应用进行更新以适应这些平台变更。为了确保您的用户在Android 12上获得良好体验,请按照如下方法,将您的应用迁移到Android 12。

常见的迁移包含两个阶段,这两个阶段可以同时进行:

  1. 确保应用兼容性(在 Android 12 最终发布前)
  2. 针对新平台的功能和 API 调整应用(最终发布后尽快进行)

5.1 确保应用与Android 12兼容

您通常可以调整应用并发布更新,而无需更改应用的 ​targetSdkVersion​。您必须测试现有应用在 Android 12 上的运行情况,以确保更新到最新版 Android 的用户获得良好的体验。有些平台变更可能会影响应用的行为方式,在开始测试之前,请仔细阅读“应用变更:所有应用”章节的内容,即使您不更改应用的​targetSdkVersion​,这些变更也可能会影响您的应用。

请务必查看并测试非 SDK 接口限制,您应使用应用公共 SDK 或 NDK 等效项替换应用使用的任何受限接口。留意突出显示这些访问权限的 logcat 警告,并使用 ​StrictMode​ 方法 ​detectNonSdkApiUsage()​ 以编程方式捕获它们。

最后,请务必完整测试应用中的库和 SDK,确保它们在 Android 12 上按预期运行,并遵循隐私权、性能、用户体验、数据处理和权限方面的最佳做法。如果您遇到问题,请尝试更新到最新版本的 SDK,或联系 SDK 开发者寻求帮助。

当您完成测试并进行更新后,建议您立即发布兼容的应用,尽早帮助用户顺利过渡到 Android 12。

5.2 更新应用的targetSdkVerison并使用新API构建

当您计划全面支持 Android 12 时,请查看“以Android 12为目标平台的应用行为变更”,这些变更可能会影响应用的功能,可能需要进行大量开发工作,建议您尽早了解并解决这类问题。以下是全面支持Android 12的步骤:

编译应用并将其安装到搭载 Android 12 的设备上后,请开始测试,以确保应用能够在 Android 12 上正常运行,将测试重点放在以 Android 12 为目标平台的应用的行为变更上。

请务必查看并测试可能适用的受限非 SDK 接口的使用。留意突出显示这些访问权限的 logcat 警告,并使用 StrictMode 方法 detectNonSdkApiUsage() 以编程方式捕获它们。

最后,请务必完整测试应用中的库和 SDK,确保它们在 Android 12 上按预期运行,并遵循隐私权、性能、用户体验、数据处理和权限方面的最佳做法。如果您遇到问题,请尝试更新到最新版本的 SDK,或联系 SDK 开发者寻求帮助。

Android 12 为开发者引入了一项新功能:兼容性框架,可让您更轻松地测试应用的针对性行为变更。对于可调试的应用,切换开关可让您:

  • 在不实际更改应用的 targetSdkVersion 的情况下测试有针对性的更改。您可以使用切换开关强制启用特定的针对性行为变更,以评估对现有应用的影响。
  • 仅针对特定变更进行测试。您可以使用切换开关停用除要测试的变更之外的所有针对性变更,而不必一次处理所有针对性变更。
  • 通过 adb 管理切换开关。您可以使用 adb 命令在自动测试环境中启用和停用可切换的变更。
  • 使用标准变更 ID 更快地进行调试。每个可切换的变更都具有唯一 ID 和名称,可用于在日志输出中快速调试根本原因。

兼容性框架的详细内容,请参考:兼容性框架变更 (Android 12)

6.重点适配问题

为了解当前应用对Android 12 版本的适配情况,以便更好推进MIUI Android 12适配工作,我们已在内部进行了自动化兼容性测试。我们选取了小米应用商店Top的各类应用,对每个应用进行下载、安装、启动、monkey测试、遍历测试、卸载,并在整个过程中检测是否有FC/ANR问题发生。根据测试结果,我们强烈建议您关注以下问题。若您的应用存在以下情况,请尽快适配。

6.1 使用了第三方加固工具

由于加固工具和系统耦合紧密,因此若您的应用使用了第三方加固工具,请尽快更新您所使用的加固工具以适配Android 12。我们也会尽快推动加固厂商适配Android 12。
在我们的测试中,存在兼容性问题的第三方加固工具有:

  1. 爱加密加固
  • 样例问题trace1
05-24 14:38:02.242 10186 21122 21122 E AndroidRuntime: FATAL EXCEPTION: main
05-24 14:38:02.242 10186 21122 21122 E AndroidRuntime: Process: xxx, PID: xxx
05-24 14:38:02.242 10186 21122 21122 E AndroidRuntime: java.lang.VerifyError: Rejecting class xxx that attempts to sub-type erroneous class xxx (declaration of xxx appears in /data/user/0/xxx/Anonymous-DexFile@xxx.jar)
......
  • 样例问题trace2
04-28 15:39:04.470 10238 24709 24709 E AndroidRuntime: FATAL EXCEPTION: main
04-28 15:39:04.470 10238 24709 24709 E AndroidRuntime: Process: xxx, PID: xxx
......
04-28 15:39:04.470 10238 24709 24709 E AndroidRuntime:         at android.app.FragmentController.attachHost(FragmentController.java:89)
......

2.梆梆加固

  • 样例问题trace1
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'Redmi/alioth/alioth:11/RKQ1.200826.002/20.12.28:user/release-keys'
Revision: '0'
ABI: 'arm'
Timestamp: 2021-01-04 09:19:50+0800
pid: 24985, tid: 25000, name: xxx  >>> xxx <<<
uid: 10296
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xc050ffdc
    r0  00006199  r1  00000000  r2  0000000a  r3  00000000
......
backtrace:
......
#06 pc 00037201  /data/app/~~Sog5FGGDSipk_Lf4DCNCLw==/xxx-Gc_aKgh9uIp2F1djVkczxg==/lib/arm/libDexHelper.so (BuildId: 73f2fb76430d4168663cb4ade4cb836abf0fe502)
#07 pc 00037e83  /data/app/~~Sog5FGGDSipk_Lf4DCNCLw==/xxx-Gc_aKgh9uIp2F1djVkczxg==/lib/arm/libDexHelper.so (BuildId: 73f2fb76430d4168663cb4ade4cb836abf0fe502)
......
  • 样例问题trace2
01-14 18:01:11.350 10686 31177 31177 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
01-14 18:01:11.350 10686 31177 31177 F DEBUG   : Build fingerprint: 'Xiaomi/venus/venus:11/RKQ1.200928.002/V12.0.14.0.RKBCNXM:user/release-keys'
01-14 18:01:11.350 10686 31177 31177 F DEBUG   : Revision: '0'
01-14 18:01:11.350 10686 31177 31177 F DEBUG   : ABI: 'arm'
01-14 18:01:11.350 10686 31177 31177 F DEBUG   : Timestamp: 2021-01-14 18:01:11+0800
01-14 18:01:11.350 10686 31177 31177 F DEBUG   : pid: 31144, tid: 31144, name: xxx  >>> xxx <<<
01-14 18:01:11.350 10686 31177 31177 F DEBUG   : uid: 10686
01-14 18:01:11.350 10686 31177 31177 F DEBUG   : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xe7e15000
01-14 18:01:11.350 10686 31177 31177 F DEBUG   :     r0  00000000  r1  e7e15000  r2  00000000  r3  e51ff004
......
01-14 18:01:11.571 10686 31177 31177 F DEBUG   : backtrace:
01-14 18:01:11.571 10686 31177 31177 F DEBUG   :       #00 pc 00020406  /data/app/~~B1wDWHt01dZ4xgeR9qXtEw==/xxx-xIZ3NSdHSP5cB-tYLjQrYA==/lib/arm/libDexHelper_sdk.so
01-14 18:01:11.571 10686 31177 31177 F DEBUG   :       #01 pc 00021b47  /data/app/~~B1wDWHt01dZ4xgeR9qXtEw==/xxx-xIZ3NSdHSP5cB-tYLjQrYA==/lib/arm/libDexHelper_sdk.so (pFE1FFE21C810240D8476E0376B1F05CD+18)
01-14 18:01:11.571 10686 31177 31177 F DEBUG   :       #02 pc 0001ff5d  /data/app/~~B1wDWHt01dZ4xgeR9qXtEw==/xxx-xIZ3NSdHSP5cB-tYLjQrYA==/lib/arm/libDexHelper_sdk.so (pFF03AF407F2AEDDF5A59E1985C5A14AB+360)
......

3.乐固加固

  • 样例问题trace1
03-03 17:26:20.978 10241 14180 14180 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
03-03 17:26:20.978 10241 14180 14180 F DEBUG   : Build fingerprint: 'Xiaomi/grus/grus:10/QKQ1.190828.002/9.12.25:user/release-keys'
03-03 17:26:20.978 10241 14180 14180 F DEBUG   : Revision: '0'
03-03 17:26:20.978 10241 14180 14180 F DEBUG   : ABI: 'arm'
03-03 17:26:20.978 10241 14180 14180 F DEBUG   : Timestamp: 2020-03-03 17:26:20+0800
03-03 17:26:20.978 10241 14180 14180 F DEBUG   : pid: 14110, tid: 14110, name: xxx  >>> xxx <<<
03-03 17:26:20.978 10241 14180 14180 F DEBUG   : uid: 10241
03-03 17:26:20.978 10241 14180 14180 F DEBUG   : signal 6 (SIGABRT), code 0 (SI_USER), fault addr --------
03-03 17:26:20.978 10241 14180 14180 F DEBUG   : Abort message: 'JNI DETECTED ERROR IN APPLICATION: obj == null
03-03 17:26:20.978 10241 14180 14180 F DEBUG   :     in call to GetObjectField
03-03 17:26:20.978 10241 14180 14180 F DEBUG   :     from void com.tencent.StubShell.TxAppEntry.load(android.content.Context)'
03-03 17:26:20.978 10241 14180 14180 F DEBUG   :     r0  00000000  r1  0000371e  r2  00000006  r3  ffbe8b30
......
03-03 17:26:21.204 10241 14180 14180 F DEBUG   : backtrace:
03-03 17:26:21.204 10241 14180 14180 F DEBUG   :       #00 pc 00056dfe  /apex/com.android.runtime/lib/bionic/libc.so (abort+166) (BuildId: b1803e2c54cf63f48664b8839ccf313b)
......
  • 样例trace2
05-29 06:32:41.237 10390  6287  6287 E AndroidRuntime: FATAL EXCEPTION: main
05-29 06:32:41.237 10390  6287  6287 E AndroidRuntime: Process: xxx, PID: 6287
05-29 06:32:41.237 10390  6287  6287 E AndroidRuntime: java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/core/content/FileProvider$PathStrategy;
05-29 06:32:41.237 10390  6287  6287 E AndroidRuntime:         at androidx.core.content.FileProvider.getPathStrategy(FileProvider.java:576)
......
05-29 06:32:41.237 10390  6287  6287 E AndroidRuntime: Caused by: java.lang.ClassNotFoundException: androidx.core.content.FileProvider$PathStrategy
05-29 06:32:41.237 10390  6287  6287 E AndroidRuntime:         ... 14 more

4.360加固

  • 样例trace1
03-14 23:24:09.078 10272 15691 15691 D AndroidRuntime: Shutting down VM
03-14 23:24:09.078 10272 15691 15691 E AndroidRuntime: FATAL EXCEPTION: main
03-14 23:24:09.078 10272 15691 15691 E AndroidRuntime: Process: xxx, PID: 15691
03-14 23:24:09.078 10272 15691 15691 E AndroidRuntime: java.lang.RuntimeException: Unable to create application com.stub.StubApp: java.lang.RuntimeException: java.lang.NoClassDefFoundError: Failed resolution of: Lcom/stub/StubApp;
03-14 23:24:09.078 10272 15691 15691 E AndroidRuntime: at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6997)
......
03-14 23:24:09.078 10272 15691 15691 E AndroidRuntime: Caused by: java.lang.ClassNotFoundException: com.stub.StubApp
03-14 23:24:09.078 10272 15691 15691 E AndroidRuntime: ... 13 more

5.百度加固

  • 样例trace1
05-28 19:38:13.575 10209  5992  5992 F DEBUG   : Build fingerprint: 'Xiaomi/venus/venus:12/SPB1.210331.013/eng.builde.20210525.073644:user/test-keys'
05-28 19:38:13.575 10209  5992  5992 F DEBUG   : Revision: '0'
05-28 19:38:13.575 10209  5992  5992 F DEBUG   : ABI: 'arm'
05-28 19:38:13.575 10209  5992  5992 F DEBUG   : Timestamp: 2021-05-28 19:38:13.445686711+0800
05-28 19:38:13.575 10209  5992  5992 F DEBUG   : Process uptime: 0s
05-28 19:38:13.575 10209  5992  5992 F DEBUG   : pid: 5969, tid: 5969, name: xxx  >>> xxx <<<
05-28 19:38:13.575 10209  5992  5992 F DEBUG   : uid: 10209
05-28 19:38:13.575 10209  5992  5992 F DEBUG   : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x62696c0e
05-28 19:38:13.575 10209  5992  5992 F DEBUG   :     r0  62696c0e  r1  ffd08284  r2  e78d01ad  r3  dd422f24
05-28 19:38:13.575 10209  5992  5992 F DEBUG   :     r4  d7502b90  r5  f1cb9d09  r6  ffd08284  r7  f5b9e2f0
05-28 19:38:13.575 10209  5992  5992 F DEBUG   :     r8  f5cfdadf  r9  f5db6314  r10 00000003  r11 f5d02427
05-28 19:38:13.575 10209  5992  5992 F DEBUG   :     ip  e655ef3c  sp  ffd08268  lr  dd419a07  pc  f1cb9d08
05-28 19:38:13.575 10209  5992  5992 F DEBUG   : backtrace:
05-28 19:38:13.575 10209  5992  5992 F DEBUG   :       #00 pc 00034d08  /apex/com.android.runtime/lib/bionic/libc.so (strcmp_a15+0) (BuildId: c95adaa6733585cf4f8f4efd331ecb2d)
05-28 19:38:13.575 10209  5992  5992 F DEBUG   :       #01 pc 00012a05  /data/app/~~eFnoVjNlNOx8V-dvL-_piw==/com.yukexing.mobileticket-uHImw17j1Y-uuPI3t8I8Iw==/lib/arm/libbaiduprotect.so
05-28 19:38:13.575 10209  5992  5992 F DEBUG   :       #02 pc 000125a3  /data/app/~~eFnoVjNlNOx8V-dvL-_piw==/com.yukexing.mobileticket-uHImw17j1Y-uuPI3t8I8Iw==/lib/arm/libbaiduprotect.so
......

若您的应用使用了以上的加固工具,请务必关注相应厂商的适配进度,以便第一时间更新加固工具以适配Android 12。若您使用的是其它加固工具,也请关注工具的Android 12适配情况。

6.2 其他问题

应用在启动时,创建activity报错闪退,报错信息是”Activity {packageName/compomentName} did not call through to super.onCreate()” ,经确认,这个问题是梆梆加固的问题,请遇到的开发者联系梆梆加固厂商,用最新的版本升级即可。

05-13 09:53:25.471 10732 15838 15838 E AndroidRuntime: FATAL EXCEPTION: main
05-13 09:53:25.471 10732 15838 15838 E AndroidRuntime: Process: xxxx, PID: xxx
05-13 09:53:25.471 10732 15838 15838 E AndroidRuntime: android.util.SuperNotCalledException: Activity {xxx/xxxx} did not call through to super.onCreate()
05-13 09:53:25.471 10732 15838 15838 E AndroidRuntime:         at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3549)
05-13 09:53:25.471 10732 15838 15838 E AndroidRuntime:         at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3729)
..............................................//此处省略

Android 12 应用适配指南3 影响应用的行为变更

Android 12 应用适配指南3 影响应用的行为变更

影响应用的行为变更

4.1 行为变更:所有应用

Android 12 平台包含一些行为变更,这些变更可能会影响您的应用。以下行为变更将影响在 Android 12 上运行的所有应用,无论采用哪种 targetSdkVersion都不例外。您应该充分测试您的应用,相应做出修改以支持这些变更。

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

Android 12 简化了沉浸模式,使手势导航更易于操作且与其他活动体验(例如观看视频和阅读图书)更加一致。应用仍然可以在全屏游戏体验中防止意外手势,以免用户在玩游戏时意外退出游戏;所有其他全屏或沉浸式体验现在都允许用户通过滑动手势进行导航。为了实现这一点,从 Android 12 开始,已弃用非粘性沉浸式体验(​BEHAVIOR_SHOW_BARS_BY_TOUCH​、​BEHAVIOR_SHOW_BARS_BY_SWIPE​)的现有行为。它们已被默认行为 (​BEHAVIOR_DEFAULT​) 取代,即在隐藏系统栏后,允许使用滑动手势。此标志会根据模式显示不同的视觉和功能行为:

  • 在“三按钮”模式下,视觉和功能行为与 Android 12 之前的版本中的沉浸模式相同。
  • 在手势导航模式下,行为如下:
  • 在视觉上,它与 Android 11 及更低版本中的沉浸模式相同。从功能上讲,即使系统栏被隐藏,也允许使用手势;只需滑动屏幕一次便可调用系统返回操作,而 Android 11 需要滑动两次。下拉通知栏或转到主屏幕无需另外滑动屏幕。

Android 12 的粘性沉浸模式 (​BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE​) 没有任何变化。请注意此功能的以下向后兼容性:

  • 如果应用以 Android 11 及更低版本为目标平台,则在 Android 12 上运行时:
  • ​BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE​ 展示出的功能和呈现出的视觉效果相同。默认值会映射到 ​BEHAVIOR_SHOW_BARS_BY_SWIPE​。
  • 如果应用以 Android 12 为目标平台,则在 Android 11(API 级别 30)及更低版本上运行时:
  • 展示出的功能和呈现出的视觉效果应该是相同的,但 ​BEHAVIOR_SHOW_BARS_BY_TOUCH​ 映射到 ​BEHAVIOR_SHOW_BARS_BY_SWIPE​ 的情况除外。请务必更新您的 SDK 级别,以获得新的默认值 (​BEHAVIOR_SHOW_BARS_BY_SWIPE​)。否则,​BEHAVIOR_SHOW_BARS_BY_TOUCH​ 的默认值会保持不变。

4.1.2 前台服务通知延迟

在Android 12上为了优化短时间运行的前台服务的流畅体验,系统会延迟10s显示该前台服务的通知。此更改使某些短期任务在显示通知前完成。如果某项前台服务至少具有以下特征之一,则系统会在服务启动后立即显示相关通知:

  1. 该服务与包含“操作按钮”的通知相关联。
  2. 该服务的foregroundServiceType为 ​connectedDevice​、​mediaPlayback​、​mediaProjection​ 或 ​phoneCall​。
  3. 该服务根据通知的category中的定义,提供与通话、导航或媒体播放相关的用例。
  4. 该服务通过在设置通知时调用setShowForegroundImmediately()

4.1.3 对 Netlink MAC 地址的限制

Android 12 进一步限制了所有非系统应用对设备的 MAC 地址(不可重置的标识符)的访问,应用无论采用哪种targetSdkVersion都不例外。相关 API 会返回空值或占位值,具体取决于应用的targetSdkVersion:

  • 如果应用以 Android 12 为目标平台,则该 API 会返回 null。
  • 如果应用以 Android 11 或更低版本为目标平台,则该 API 将返回硬编码占位值:​02:00:00:00:00:00​

如果您的应用访问了MAC地址,logcat 输出会显示:CompatibilityChangeReporter: Compat change id reported: 170188668

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

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

4.1.4.1 受影响的应用

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

  • 需要 ​SYSTEM_ALERT_WINDOW​ 权限并使用 ​FLAG_NOT_TOUCHABLE​ 标志的叠加层,例如使用 ​TYPE_APPLICATION_OVERLAY​ 的窗口。
  • 使用 ​FLAG_NOT_TOUCHABLE​ 标志的 activity 窗口。
  • Toast窗口

例如,使用以上示例创建的窗口,在Android 12上会呈现出不可“穿透”的触摸操作:

如示例图所示,触摸操作将无法穿透窗口,即无法点击窗口内的应用图标。

4.1.4.2 适配方法

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

注意:类型为TYPE_APPLICATION_OVERLAY的窗口不受信任。

  • 不可见窗口。窗口的根视图是 GONE 或 INVISIBLE。
  • 全透明窗口。窗口的 alpha 属性为 0.0。
  • 足够半透明的系统警报窗口。当组合后的不透明度小于或等于系统针对触摸的最大遮掩不透明度时,系统会将一组系统警报窗口视为足够半透明。在 Android 12 Beta 版中,这一最大不透明度为 0.8。此值在未来的 Beta 版中可能会发生变化,可通过如下方式获取:
InputManager inputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
Log.d("最大允许的透明度:",""+inputManager.getMaximumObscuringOpacityForTouch());
4.1.4.3 检测不受信任的触摸操作是否被屏蔽

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

Untrusted touch due to occlusion by package_name
4.1.4.4 测试变更

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

# 单个应用
adb shell am compat disable BLOCK_UNTRUSTED_TOUCHES com.example.app

# 对所有应用生效
# 设置成0,表示允许,设置成1,日志会打印对应的信息
adb shell settings put global block_untrusted_touches 0

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

# 单个应用
adb shell am compat reset BLOCK_UNTRUSTED_TOUCHES com.example.app

# 对所有应用生效
adb shell settings put global block_untrusted_touches 2

4.1.5 应用无法关闭系统对话框

为了加强用户与应用和系统互动时的控制,从 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 或更低版本为目标平台,并在抽屉式通知栏顶部显示一个窗口。

4.1.6 Display#getRealSize 和 getRealMetrics:废弃和沙盒

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

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

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

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

为了与较低的 Android 版本实现更广泛的兼容性,您可以使用 JetpackWindowManager库,它包含一个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();

注意:使用 MediaProjection 的任何库也应遵循此建议,并查询应用窗口的相应 WindowMetrics。

4.1.7 改进了刷新率切换

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

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

// 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);

4.1.8 麦克风和摄像头切换开关

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

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

注意:当用户拨打应急服务电话(如 911)时,系统会开启麦克风使用权限。此行为可保护用户安全。

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

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

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

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

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

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

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

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

4.1.9 麦克风和摄像头指示标志

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

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

4.1.10 权限软件包可见性

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

4.1.11 移除了 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 推荐的做法。

4.1.12 剪贴板访问通知

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

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

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

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

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

4.1.13 应用两个 Task 之间的过渡动画

Android 12 版本在两个 Task 之间的过渡动画逻辑发生了变更:

1.使用 Activity.overridePendingAppTransition 接口提供给系统的过渡动画无法作用于 Task 之间跳转。

2.使用 ActivityOptions.makeCustomAnimation 接口创建的过渡动画无法作用于 Task 之间跳转。

如果您的应用运行在 Android 12 设备上,且出现两个 Task 跳转时无过渡动画的情况,建议参考以下方案恢复 Task 动画的正常播放:

第一步,在项目资源目录下为 Task 创建过渡动画资源文件,如:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <scale
        android:fromXScale="1"
        android:toXScale="0.5"
        android:fromYScale="1"
        android:toYScale="0.5"
        android:pivotX="50%"
        android:pivotY="50%"
        android:fillAfter="false"
        android:duration="200" />

</set>

第二步,在项目主题资源文件中配置 Task 过渡动画,如:

<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Theme.MyApplication" parent="Theme.AppCompat">
        //在主题中引用自定义的过渡动画
        <item name="android:windowAnimationStyle">@style/ActivitAnimStyle</item>
        //禁止启动动画,防止启动动画影响自定义动画的播放
        <item name="android:windowDisablePreview">true</item>
    </style>


    <style name="ActivitAnimStyle" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
        <item name="android:taskOpenEnterAnimation">@anim/anim_xxx</item>
        <item name="android:taskOpenExitAnimation">@anim/anim_xxx</item>
        <item name="android:taskCloseEnterAnimation">@anim/anim_xxx</item>
        <item name="android:taskCloseExitAnimation">@anim/anim_xxx</item>
    </style>
</resources>

第三步,在 AndroidManifest.xml 文件中应用自定义主题,如:

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true">
	<activity
        android:name=".MainActivity"
        android:exported="true"
        android:theme="@style/Theme.MyApplication">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <activity android:name=".Secondctivity"
        android:launchMode="singleTask"
        android:taskAffinity=".NewTask"
        android:theme="@style/Theme.MyApplication"
        android:exported="true">
    </activity>
</application>

4.2 行为变更:以Android 12为目标平台的应用

4.2.1 WebView中SameSite cookie行为

Android 的 WebView 组件基于为 Google 开源项目 Chromium。在过去一年中,Chromium 变更了对第三方 Cookie 的处理方式,目的是为了更好地保护用户的安全和隐私,并赋予用户更高的透明度和控制权。从 Android 12 开始,这些变更将应用于 WebView 中。

Cookie 的 ​SameSite​ 属性决定了它是可以与任何请求一起发送,还是只能与同站点请求一起发送。Android 12 中的 WebView 基础版本(版本 89.0.4385.0)包含以下隐私保护方面的变更,旨在改善对第三方 Cookie 的默认处理方式,并帮助防止意外跨站点共享:

  • 没有 ​SameSite​ 属性的 Cookie 被视为 ​SameSite=Lax​。
  • 带有 ​SameSite=None​ 的 Cookie 还必须指定 ​Secure​ 属性,这意味着它们需要安全的上下文,并应通过 HTTPS 发送。
  • 站点的 HTTP 版本和 HTTPS 版本之间的链接现在被视为跨站点请求,因此除非将 Cookie 正确标记为 ​SameSite=None; Secure​,否则 Cookie 不会被发送。

对于开发者而言,一般指导意见是识别关键用户流中的跨站点 Cookie 依赖项,并确保在需要时使用适当的值显式设置 ​SameSite​ 属性。您必须显式指定允许在不同网站上运行的 Cookie,或适用于从 HTTP 切换到 HTTPS 进行同站点导航的 Cookie。

如需了解有关这些变更的更详细内容,请参阅:WebView 中的现代 SameSite Cookie 行为

4.2.2 adb备份

为了保护私有应用数据,Android 12 变更了 ​adb backup​ 命令的默认行为。对于以 Android 12 为目标平台的应用,用户运行 ​adb backup​ 命令时,从设备导出的任何其他系统数据都不包含应用数据。如果您的测试或开发工作流程依赖于使用 ​adb backup​ 的应用数据,现在您可以选择通过在应用的清单文件中将 ​android:debuggable​ 设置为 ​true​ 来导出应用数据。注意:为了帮助保护应用数据,请务必在发布应用前将 ​android:debuggable​ 设置为 ​false​。

4.2.3 组件安全性

4.2.3.1 更安全的exported组件

如果您的应用以 Android 12 为目标平台,且包含使用<intent-filter>的 activity、服务或广播接收器,您必须为这些应用组件显式声明 ​android:exported​ 属性。注意:targetSdkVersion为Android 12的应用,如果activity、服务或广播接收器使用 intent 过滤器,并且未显式声明 ​android:exported​ 的值,则您的应用将无法在搭载 Android 12 的设备上进行安装。强制安装时会有提示:

Targeting S+ (version 10000 and above) requires that an explicit value for 
android:exported be defined when intent filters are present

以下为正确的服务配置代码段:

<service android:name="com.example.app.xxxService"
         android:exported="false">
    <intent-filter>
        <action android:name="com.example.app.xxxxAction" />
    </intent-filter>
</service>
4.2.3.2 PendingIntent必须声明可变性

如果您的应用以 Android 12 为目标平台,您必须为您的应用创建的每个 ​PendingIntent​ 对象指定可变性。这项额外的要求可提高应用的安全性。如需声明特定 ​PendingIntent​ 对象是否可变,请分别使用 ​PendingIntent.FLAG_MUTABLE​ 或 ​PendingIntent.FLAG_IMMUTABLE​ 标志。如果您的应用试图在不设置任何可变标志的情况下创建 ​PendingIntent​ 对象,系统会抛出 ​IllegalArgumentException​,并在 Logcat 中显示以下消息:

PACKAGE_NAME: Targeting S+ (version 10000 and above) requires that one of 
FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.

Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if 
some functionality depends on the PendingIntent being mutable, e.g. if 
it needs to be used with inline replies or bubbles.

在大多数情况下,您的应用应创建不可变的 ​PendingIntent​ 对象,如以下代码段所示。如果 ​PendingIntent​ 对象不可变,则应用无法修改 intent 来调整调用 intent 的结果。

PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(),
        requestCode, intent,/*flags */ PendingIntent.FLAG_IMMUTABLE);

有些情况下,应用需要创建可变的​PendingIntent​ 对象 :

  • 通知中的直接回复操作需要变更与回复关联的 ​PendingIntent​ 对象中的剪辑数据。通常,您可以通过将 ​FILL_IN_CLIP_DATA​ 作为标志传递给 ​fillIn()​ 的方法请求此变更。
  • 如果您的应用使用 ​PendingIntent​ 将对话放在气泡中,则 intent 应该可变,以便系统可以应用正确的标志,例如 ​FLAG_ACTIVITY_MULTIPLE_TASK​ 和 ​FLAG_ACTIVITY_NEW_DOCUMENT​。

如果您的应用创建了可变的 ​PendingIntent​ 对象,强烈建议您使用显式 intent 并填写​ComponentName​。如此一来,每当另一个应用调用 ​PendingIntent​ 并将控制权传回您的应用时,应用中的相同组件都会启动。

4.2.3.3 以不安全的方式启动嵌套intent

嵌套 intent 是在其他 intent 中作为 extra 传递的 intent。为了提高平台安全性,Android 12 提供了一种调试功能,如果您的应用以不安全的方式启动嵌套 intent,此功能便会发出警告。如果您的应用同时执行以下两项操作,就会发生 StrictMode 违规行为。

  1. 您的应用从已传递的 intent 的 extra 中解封嵌套 intent。
  2. 您的应用立即使用该嵌套 intent 启动应用组件,例如将 intent 传递给 ​startActivity()​、​startService()​ 或 ​bindService()​。

如需检查您的应用中是否会以不安全的方式启动嵌套 intent,请在配置 ​VmPolicy​ 时调用 ​detectUnsafeIntentLaunch()​,如以下代码段所示。如果您的应用检测到 StrictMode 违规行为,您可能需要停止应用的执行以保护潜在的敏感信息。

protected void onCreate() {
    StrictMode.setVmPolicy(new VmPolicy.Builder()
        // Other StrictMode checks that you've previously added.
        // ...
        .detectUnsafeIntentLaunch()
        .penaltyLog()
        // Consider also adding penaltyDeath()
        .build());
}

注意:如果您的应用以 Android 12 为目标平台,并在其 ​VmPolicy​ 定义中使用 ​detectAll()​ 方法,系统将自动调用 ​detectUnsafeIntentLaunch()​ 方法。

您的应用可能会启动嵌套 intent,以便在应用的各个组件之间导航,或代表其他应用执行操作。如需在这两种情况下最大限度地降低出现 StrictMode 违规行为的可能性,请执行以下操作:

  • 嵌套 intent 的内部启动:组件配置为 android:exported=”false” 。
  • 嵌套 intent 的跨应用启动:使用 ​PendingIntent​ 代替嵌套 intent。这样当 ​PendingIntent​ 从包含它的 ​Intent​ 中解封时,应用组件可以使用调用进程的身份启动 ​PendingIntent​。该配置允许提供程序应用向调用应用的任何组件(包括未导出的组件)发送回调。

4.2.4 前台服务启动限制

以Android 12为目标平台的应用在后台运行时无法再启动前台服务,但一些特殊情况除外。如果应用在后台运行时尝试启动前台服务且不符合特殊情况的,系统会抛出IllegalStateException。允许从后台启动前台服务的情况,请参考developer文档。

系统推荐替代前台服务的方案是WorkManager,Android 12 Beta 版发布时,WorkManager 将成为启动高优先级后台任务的推荐解决方案。Work Manager 2.7.0-alpha01 支持加急作业,后者是 Android 12 中的一种新作业。在 Android 11 及更低版本中,此版本的 WorkManager 使用前台服务提供向后兼容性。关于加急作业和WorkManager的更多介绍,请参考:加急作业

4.2.5 非SDK接口限制

从Android 9(API级别28)开始,Android平台对应用使用非SDK接口实施了限制。这些限制旨在提升用户体验,降低应用发生崩溃的风险,同时也为开发者降低紧急发布的风险。当应用通过引用非 SDK 接口或尝试使用反射或 JNI 来获取其句柄,这些限制就会生效,常见的访问非SDK接口,系统会抛出NoSuchFieldError,NoSuchMethodError,NoSuchFieldException,NoSuchMethodException,返回的结果可能是NULL。在Android 12中包含了更新后的受限制非SDK接口列表,在限制之前,Android会尽可能确保有可用的替代方案。如果您的应用依赖于非 SDK 接口,您应该开始计划迁移到 SDK 替代方案。如果您无法为应用中的某项功能找到使用非 SDK 接口的替代方案,应请求新的公共 API

Android 12 中的列表变更分为以下几类:

  • Android 11(API 级别 30)中不受支持的(已被列入灰名单)的非 SDK 接口现已在 Android 12 中被禁止使用
  • Android 11(API 级别 30)中受限的非 SDK 接口,它们在 Android 12 被添加到了 Android SDK 中。

如需查看 Android 12 的所有非 SDK 接口的完整列表,请下载以下文件:​hiddenapi-flags.csv​关于更多限制非SDK接口访问的内容,请参考:针对非 SDK 接口的限制

4.2.6 自定义通知变更

Android 12 改变了完全自定义通知的外观。 以前,自定义通知能够使用整个通知区域并提供自己的布局和样式,这可能在不同设备上引发布局兼容性问题。
对于以 Android 12 为目标平台的应用,包含自定义内容视图的通知将不再使用完整通知区域;相反,系统会应用标准模板。此模板可确保自定义通知在所有状态下都与其他通知相同,例如,在收起状态下的通知图标和展开功能,以及在展开状态下的通知图标、应用名称和收起功能。此行为与 ​Notification.DecoratedCustomViewStyle​ 的行为几乎完全相同。
通过这种方式,Android 12 通过为用户提供可看到且熟悉的通知展开功能,使所有通知保持外观一致且易于浏览。下图显示了标准模板中的自定义通知:

以下示例展示了在收起状态和展开状态下呈现的自定义通知:

Android 12 中的变更会影响某些定义 ​Notification.Style​ 的自定义子类的应用,或使用 ​Notification.Builder​ 的方法 ​setCustomContentView(RemoteViews)​、​setCustomBigContentView(RemoteViews)​ 和 ​setCustomHeadsUpContentView(RemoteViews)​ 的应用。如果您的应用使用的是完全自定义的通知,我们建议您尽快使用新模板进行测试,并进行必要的调整:

  1. 启用自定义通知变更:
  2. 将应用的 ​targetSdkVersion​ 变更为 ​S​ 以启用新行为。重新编译。在搭载 Android 12 的设备或模拟器上安装您的应用。
  3. 测试所有使用自定义视图的通知,确保这些通知在通知栏中看起来符合预期。
  4. 请注意自定义视图规格。一般来说,提供给自定义通知的高度比之前小。在收起状态下,自定义内容的最大高度已从 106dp 减少到 48dp。此外,水平空间也减小了。
  5. 为了确保“浮动通知”状态看起来符合您的预期,请勿忘记将通知渠道的重要性提升至“高”(在屏幕中弹出)。

4.2.7 连接性

以 Android 12 及更高版本为目标平台的设备在具有硬件支持的设备上运行时,在与对等设备建立连接时,使用点对点连接不会断开与现有 Wi-Fi 的连接。如需检查是否支持此功能,请使用 ​WifiManager.isMultiStaConcurrencySupported()​。

4.2.8 大致位置

使用以 Android 12 为目标平台的应用时,用户可以请求应用只能访问大致位置信息。

注意:如果您的应用请求 ACCESS_COARSE_LOCATION 但未请求 ACCESS_FINE_LOCATION,则此变更不会影响您的应用。

如果您的应用以 Android 12 为目标平台并且请求ACCESS_FINE_LOCATION运行时权限,则您还必须请求ACCESS_COARSE_LOCATION权限。您必须在单个运行时请求中包含这两项权限。

当您的应用同时请求 ACCESS_FINE_LOCATION 和 ACCESS_COARSE_LOCATION 时,系统权限对话框将为用户提供以下新选项,如图 1 所示:

  • 确切:提供 ACCESS_FINE_LOCATION 权限提供的位置精确度。
  • 大致:提供 ACCESS_COARSE_LOCATION 权限提供的位置精确度。

详细了解 Android 12 中的大致位置

4.2.9 应用休眠

Android 12 在 Android 11(API 级别 30)中引入的自动重置权限行为的基础上进行了扩展。如果您的应用以 Android 12 为目标平台并且用户有几个月未与您的应用互动,则系统会自动重置授予的所有权限并将您的应用置于休眠状态。

注意:如果您的应用是实现DeviceAdminService或者在设备所有者或个人资料所有者操作模式下运行的设备政策控制器,则系统绝不会将您的应用置于休眠状态。

处于休眠状态的应用具有以下特征:

  • 系统会针对存储空间而非性能进行优化。应用缓存中的任何文件都会被移除。
  • 应用无法从后台运行作业或提醒。
  • 应用无法接收推送通知,包括通过 Firebase Cloud Messaging 发送的高优先级消息。

当用户下次与应用互动时,应用会退出休眠状态,并且可以再次创建作业、提醒和通知。不过,您需要重新调度在应用进入休眠状态之前调度的所有作业、提醒和通知。此工作流与用户从系统设置中手动强行停止应用时的工作流类似。为了更轻松地支持此工作流,请使用WorkManager。您还可以在ACTION_BOOT_COMPLETED 广播接收器中添加重新调度逻辑,系统会在您的应用离开休眠状态时以及设备启动后调用它。

请求用户停用休眠

如果您预计应用中的某个用例会受休眠的影响,您可以向用户发送请求,让其准许应用免于休眠和自动重置权限。如果用户希望应用主要在后台运行,即使用户不与应用互动,应用也能正常工作,例如,当应用执行以下一项或多项操作时,这种豁免很有用:

  • 通过定期报告家庭成员的位置来保障家庭安全。
  • 在设备与应用的服务器之间同步数据。
  • 与智能设备(如电视)通信。
  • 与配套设备(如手表)配对。

如需请求豁免,请调用包含Intent.ACTION_APPLICATION_DETAILS_SETTINGSintent 操作的 intent。在显示的屏幕中,用户可以关闭名为撤消权限并释放空间的选项。

注意:在调用 intent 之前,不妨考虑向用户显示一个指导界面,以便用户了解为什么您的应用将他们引导至系统设置。

测试休眠行为

如需将应用置于休眠状态以进行测试,请执行以下操作:

1.在设备上启用该行为:

adb shell device_config put app_hibernation app_hibernation_enabled true

2.更改应用的状态,使其进入休眠状态。包含 –global 标志的命令会强制应用进入“完全休眠”状态,以模拟系统已为多用户设备上的所有用户将应用置于休眠状态的情况。

adb shell cmd app_hibernation set-state  true && 
  adb shell cmd app_hibernation set-state --global  true

4.2.10 通知 trampoline 限制

当用户与通知互动时,某些应用会启动一个应用组件来响应通知点按操作,该应用组件最终会启动用户最终看到并与之互动的 activity。此应用组件被称为通知 trampoline。

为了改进应用性能和用户体验,以 Android 12 为目标平台的应用无法从用作通知 trampoline 的服务广播接收器中启动 activity。

换言之,当用户点按通知或通知中的操作按钮时,您的应用无法在服务或广播接收器内调用startActivity()

当您的应用尝试从充当通知 trampoline 的服务或广播接收器启动 activity 时,系统会阻止该 activity 启动,并在Logcat 中显示以下消息:

Indirect notification activity start (trampoline) from PACKAGE_NAME, 
this should be avoided for performance reasons.

识别哪些应用组件充当通知 trampoline 

测试您的应用时,点按通知后,您可以识别哪个服务或广播接收器在您的应用中充当通知 trampoline。为此,请查看以下终端命令的输出:

adb shell dumpsys activity service 
  com.android.systemui/.dump.SystemUIAuxiliaryDumpService

输出的某一部分包含文本“NotifInteractionLog”。此部分包含识别因点按通知而启动的组件所需的信息。

更新应用

如果您的应用从充当通知 trampoline 的服务或广播接收器启动 activity,请完成以下迁移步骤:

  1. 创建一个与用户点按通知后看到的 activity 关联的 PendingIntent 对象。
  2. 构建通知的过程中,使用您在上一步中创建的 PendingIntent 对象。

如需识别 activity 的来源,例如为了执行日志记录,请在发布通知时使用 extra。对于集中式日志记录,请使用ActivityLifecycleCallbacksJetpack 生命周期观察器

切换行为

在开发者预览版计划期间测试您的应用时,您可以使用 NOTIFICATION_TRAMPOLINE_BLOCK 应用兼容性标志启用和停用此限制。

Android 12 应用适配指南2 新功能和API

Android 12 应用适配指南2 新功能和API

新功能和API

3.1 用于接收内容的统一API

当前,用户更喜欢图片、视频等富有表现力的内容,但在应用中插入和移动并非易事。为了使应用能够轻松地接收富媒体内容,Android 12 引入了全新的统一 API,您可以从任何可用来源(剪贴板粘贴、键盘或拖放操作)接收富媒体内容。

3.1.1 接收内容的API

以前每个界面机制(例如长按菜单或拖放)都有对应的 API。这意味着您必须单独与每个 API 集成,并为每种插入内容的机制添加类似的代码:

现在新的统一 API 会通过创建一个要实现的单一 API 来整合这些不同的代码路径,这样您就可以专注于应用特定的逻辑,而让系统处理其余的工作:

新 API 是一个监听器接口,包含一种方法,即 ​OnReceiveContentListener​。此回调会成为您的代码处理接收所有内容(从纯文本和样式文本到标记、图片、视频、音频文件等)的唯一位置。为了兼容较低版本的 Android 平台,我们建议您使用 AndroidX,可在 Core 1.5.0-beta1 和 Appcompat 1.3.0-beta-01 中获得该API。

3.1.2 实现代码

如需使用该 API,请先指定您的应用可以处理哪些类型的内容,以开始实现该监听器:

public class MyReceiver implements OnReceiveContentListener {
     public static final String[] MIME_TYPES = new String[] 
     {"image/*", "video/*"};
     // ...

指定您的应用支持的所有内容 MIME 类型后,实现该监听器的其余部分:

public class MyReceiver implements OnReceiveContentListener {
     public static final String[] MIME_TYPES = new String[] {"image/*", "video/*"};

     @Override
     public ContentInfoCompat onReceiveContent(View view, ContentInfoCompat contentInfo) {
         Pair<ContentInfoCompat, ContentInfoCompat> split = contentInfo.partition(
                 item -> item.getUri() != null);
         ContentInfo uriContent = split.first;
         ContentInfo remaining = split.second;
         if (uriContent != null) {
             ClipData clip = uriContent.getClip();
             for (int i = 0; i < clip.getItemCount(); i++) {
                 Uri uri = clip.getItemAt(i).getUri();
                 // App-specific logic to handle the URI ...
             }
         }
         // Return anything that your app didn't handle. This preserves the default platform
         // behavior for text and anything else that you aren't implementing custom handling for.
         return remaining;
     }
 }

3.1.3 实现效果

3.2 兼容的媒体转码

Android 12 引入了一项新功能,让录制视频的应用可以对设备上录制的视频进行更现代、存储效率更高的编码,同时又不影响与其他应用的兼容性。如果视频被不支持 HEVC 格式的应用打开,Android 会自动将以 HEVC (H.265) 等格式录制的视频转码为 AVC (H.264) 格式。对于在设备上创建的以下格式的内容,系统可以自动进行转码:

媒体格式XML属性MediaFormat MMIE类型
HEVC (H.265)HEVCMediaFormat.MIMETYPE_VIDEO_HEVC
HDR10HDR10MediaFeature.HdrType.HDR10
HDR10+HDR10PlusMediaFeature.HdrType.HDR10_PLUS

Android系统默认应用可以支持播放所有媒体格式,因此“兼容的媒体转码”默认情况下是关闭的。应用有两种方法来请求将媒体文件转码成更兼容的格式:1.在资源文件中声明;2.在代码中声明。

3.2.1 在资源文件中声明功能

首先在xml目录下创建media_capabilities.xml资源文件,内容如下:

<?xml version="1.0" encoding="utf-8"?>
<media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
    <format android:name="HEVC" supported="true"/>
    <format android:name="HDR10" supported="false"/>
    <format android:name="HDR10Plus" supported="false"/>
</media-capabilities>

这个例子中,录制的HEVC视频无法转码,而HDR视频会被转码为AVC SDR(标准动态范围)视频。同时,需要在应用AndroidManifest.xml中application 标签中使用property来标记对媒体功能文件的引用:

<property
    android:name="android.media.PROPERTY_MEDIA_CAPABILITIES"
    android:resource="@xml/media_capabilities" />

3.2.2 在代码中声明功能

您可以使用构建器构造 ApplicationMediaCapabilities对象的实例,在代码中声明媒体功能:

ApplicationMediaCapabilities mediaCapabilities = new ApplicationMediaCapabilities.Builder() 
        .addSupportedVideoMimeType(MediaFormat.MIMETYPE_VIDEO_HEVC)
        .addUnsupportedHdrType(MediaFeature.HdrType.HDR10)
        .addUnsupportedHdrType(MediaFeature.HdrType.HDR10_PLUS)
        .build();

通过 ContentResolver#openTypedAssetFileDescriptor()等方法访问媒体内容时,请使用此对象:

Bundle providerOptions = new Bundle();
providerOptions.putParcelable(MediaStore.EXTRA_MEDIA_CAPABILITIES, mediaCapabilities);
try (AssetFileDescriptor fileDescriptor =  contentResolver.openTypedAssetFileDescriptor(mediaUri, mediaMimeType, providerOptions)) {
    //视频内容会转换成ApplicationMediaCapabilities指定的格式
}

注意:代码的方式会优先于资源文件,这样可以对特定代码路径做更精细控制。

3.3 在企业中使用Android

3.3.1 Android 12中的企业功能新变化

  • 密码复杂度功能以预定义复杂存储分区(高、中、低和无)的形式设定设备级密码要求。如有必要,可对工作资料安全验证应用严格的密码要求。
  • 工作资料安全验证新手入门流程已简化。现在,设置会考虑设备密码是否符合管理员要求,让用户可以轻松选择是增强设备密码的强度还是使用工作资料安全验证。
  • 注册特定 ID 提供了一个唯一 ID,用于标识特定组织的工作资料注册,并且可在恢复出厂设置后保持稳定。在 Android 12 中,对于具有工作资料的个人设备,系统会移除对该设备的其他硬件标识符(IMEI、MEID、序列号)的访问权限。
  • 公司自有设备(无论是否具有工作资料)可以选择是否保留上述列表项中列出的功能,但不需要在 Android 12 中采用。

更详细的资料,请参阅:Android 12企业功能新变化

3.4.新的应用启动动画

Android 12 添加了SplashScreenAPI,它可为所有应用启用新的应用启动动画。这包括启动时的进入应用运动、显示应用图标的启动画面,以及向应用本身的过渡。

这种新体验可让应用每次启动时都呈现标准设计元素,但它也可自定义,以便您的应用能够保持其独特的品牌。

3.4.1 启动画面的工作原理

当用户启动应用而应用的进程未在运行(冷启动)或 Activity 尚未创建(温启动)时,会发生以下事件。(在热启动期间从不显示启动画面。)

  • 系统使用主题以及您已定义的任何动画显示启动画面。
  • 当应用准备就绪时,会关闭启动画面并显示应用。

3.4.2 动画的元素和机制

动画的元素由 Android 清单中的 XML 资源文件定义。每个元素都有浅色模式和深色模式版本。它们由窗口背景、动画形式的应用图标和图标背景组成。

关于这些元素,请注意以下几点:

  • 应用图标应该是矢量可绘制对象,它可以是静态或动画形式。虽然动画的时长可以不受限制,但我们建议让其不超过 1000 毫秒。默认情况下,使用启动器图标。
  • 图标背景是可选的,在图标与窗口背景之间需要更高的对比度时很有用。如果您使用一个自适应图标,当该图标与窗口背景之间的对比度足够高时,就会显示其背景。
  • 与自适应图标一样,前景的 ⅓ 被遮盖。
  • 窗口背景由不透明的单色组成。如果窗口背景已设置且为纯色,则未设置相应的属性时默认使用该背景。

启动画面动画机制由进入动画和退出动画组成。

  • 进入动画由系统视图到启动画面组成。这由系统控制且不可自定义。
  • 退出动画由隐藏启动画面的动画运行组成。如果您要对其进行自定义,您将可以访问 SplashScreenView 及其图标,并且可以在它们之上运行任何动画(需要设置转换、不透明度和颜色)。在这种情况下,当动画完成时,需要手动移除启动画面。

3.4.3 自定义应用中的启动画面

默认情况下,SplashScreen 使用主题的 windowBackground(如果它是单色)和启动器图标。启动画面的自定义通过向应用主题添加属性来完成。

您可以通过以下任一方式自定义应用的启动画面:

  • 设置主题属性以更改其外观
  • 让其在屏幕上显示更长时间
  • 自定义用于关闭启动画面的动画

设置启动画面的主题以更改其外观

您可以在 Activity 主题中指定以下属性来自定义应用的启动画面。如果您已有使用 android:windowBackground 等属性的旧版启动画面实现,不妨考虑为 Android 12 提供替代资源文件。

1.使用windowSplashScreenBackground 以特定的单色填充背景:

<item name="android:windowSplashScreenBackground">@color/...</item>

2.使用windowSplashScreenAnimatedIcon替换起始窗口中心的图标。如果对象通过AnimationDrawableAnimatedVectorDrawable可呈现动画效果且可绘制,则也会在显示起始窗口的同时播放动画。

<item name="android:windowSplashScreenAnimatedIcon">@drawable/...</item>

3.使用windowSplashScreenAnimationDuration设置启动画面在关闭之前显示的时长。最长时间为 1000 毫秒。

4.使用 windowSplashScreenIconBackground 设置启动画面图标后面的背景。当窗口背景与图标之间的对比度不够高时,这很有用。

<item name="android:windowSplashScreenIconBackground">@color/...</item>

5.(可选)您可以使用windowSplashScreenBrandingImage设置要显示在启动画面底部的图片。设计准则建议不要使用品牌图片。

<item name="android:windowSplashScreenBrandingImage">@drawable/...</item>

注意:建议的设计准则是不要使用品牌图片。

让启动画面在屏幕上显示更长时间

当应用绘制第一帧后,启动画面会立即关闭。如果您需要从本地磁盘异步加载少量数据(如应用内主题设置),您可以使用ViewTreeObserver.OnPreDrawListener让应用暂停绘制第一帧。

// Create a new event for the activity.
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Set the layout for the content view.
    setContentView(R.layout.main_activity);

    // Set up an OnPreDrawListener to the root view.
    final View content = findViewById(android.R.id.content);
    content.getViewTreeObserver().addOnPreDrawListener(
            new ViewTreeObserver.OnPreDrawListener() {
                @Override
                public boolean onPreDraw() {
                    // Check if the initial data is ready.
                    if (mViewModel.isReady()) {
                        // The content is ready; start drawing.
                        content.getViewTreeObserver().removeOnPreDrawListener(this);
                        return true;
                    } else {
                        // The content is not ready; suspend.
                        return false;
                    }
                }
            });
}

自定义用于关闭启动画面的动画

您可以通过Activity.getSplashScreen进一步自定义启动画面的动画。

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // ...

    // Add a callback that's called when the splash screen is animating to
    // the app content.
    getSplashScreen().setOnExitAnimationListener(splashScreenView -> {
        final ObjectAnimator slideUp = ObjectAnimator.ofFloat(
                splashScreenView,
                View.TRANSLATION_Y,
                0f,
                -splashScreenView.getHeight()
        );
        slideUp.setInterpolator(new AnticipateInterpolator());
        slideUp.setDuration(200L);

        // Call SplashScreenView.remove at the end of your custom animation.
        slideUp.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                splashScreenView.remove();
            }
        });

        // Run your animation.
        slideUp.start();
    });
}

在此回调开始时,启动画面上动画形式的矢量可绘制对象已经开始。根据应用启动的时长,可绘制对象可能在其动画的中间。使用SplashScreenView.getIconAnimationStartMillis 可了解动画何时开始。您可以按如下方式计算图标动画的剩余时长:

// Get the duration of the animated vector drawable.
long animationDuration = splashScreenView.getIconAnimationDurationMillis();
// Get the start time of the animation.
long animationStart = splashScreenView.getIconAnimationStartMillis();
// Calculate the remaining duration of the animation.
long remainingDuration = Math.max(
        animationDuration - (SystemClock.uptimeMillis() - animationStart),
        0L
);

3.5 画中画 (PiP) 改进

Android 12 针对画中画 (PiP) 模式引入了行为改进和新功能

3.5.1 点按一次和点按两次的行为改进

Android 12 改进了点按一次和点按两次的画中画行为,具体说明如下:

  • 现在,点按一次画中画窗口会为用户显示控件。以前,点按一次会展开画中画窗口并显示控件。
  • 现在,点按两次画中画窗口会在当前画中画大小与最大画中画大小之间切换。以前,点按两次会离开画中画模式而改为全屏模式。

3.5.2 新功能

Android 12 针对画中画模式引入了以下新功能:

3.5.2.1 用于在手势导航中更流畅地过渡到画中画模式的新 API 标志

Android 12 可让您使用新的setAutoEnterEnabled标志,在手势导航模式下向上滑动转到主屏幕时更流畅地过渡到画中画模式。以前,Android 会等到“向上滑动转到主屏幕”动画结束,然后再淡入画中画窗口。

如需实现此功能,请执行以下操作:

1.使用setAutoEnterEnabled 构造PictureInPictureParams.Builder,如下所示:

setPictureInPictureParams(new PictureInPictureParams.Builder()
    .setAspectRatio(aspectRatio)
    .setSourceRectHint(sourceRectHint)
    .setAutoEnterEnabled(true)    
    .build());

注意:启用 setAutoEnterEnabled 后,无需在onUserLeaveHint中显式调用enterPictureInPictureMode

2.尽早使用最新的 PictureInPictureParams 调用 setPictureInPictureParams。应用不应等待 onUserLeaveHint 回调(就像在 Android 11 中所做的那样)。

例如,应用可能要在第一次播放以及后续任何一次播放时(如果宽高比发生了变化)调用 setPictureInPictureParams。

3.根据需要调用 setAutoEnterEnabled(false)。例如,如果当前播放处于暂停状态,则视频应用进入画中画模式可能不是最佳选择。

3.5.2.2 用于为非视频内容停用无缝大小调整的新 API 标志

Android 12 添加了SeamlessResizeEnabled标志,在画中画窗口中调整非视频内容的大小时,该标志可提供更流畅的交替淡变动画。以前,在画中画窗口中调整非视频内容的大小时会产生烦人的视觉伪影。

为了向后兼容,默认情况下,将 setSeamlessResizeEnabled 标志设置为 true。对于视频内容,请将其设置保留为 true;对于非视频内容,请将其更改为 false。

如需停用非视频内容的无缝大小调整,请编写以下代码:

 setPictureInPictureParams(new PictureInPictureParams.Builder()
      .setSeamlessResizeEnabled(false)
      .build());
3.5.2.3 退出画中画模式时支持更流畅的动画

在 Android 12 中,SourceRectHint标志现在重用于在退出画中画模式时实现更流畅的动画。退出时,系统会使用当前可用的 sourceRectHint 创建动画,无论它是用于进入画中画模式的原始 Rect,还是应用提供的更新后的 Rect。

如需实现此功能,请按以下方式更新您的应用:

1.继续使用sourceRectHintaspectRatio构造 PictureInPictureParams,以实现流畅的进入动画。

2.如有必要,请在系统开始退出过渡之前更新sourceRectHint。当系统即将退出画中画模式时,activity 的视图层次结构会布局成其目标配置(例如全屏)。应用可以将布局更改监听器附加到其根视图或目标视图(如视频播放器视图),以检测事件并在动画开始前更新 sourceRectHint。

// Listener is called immediately after the user exits PIP but before animating.  
 playerView.addOnLayoutChangeListener { _, left, top, right, bottom,                      
               oldLeft, oldTop, oldRight, oldBottom ->      
   if (left != oldLeft || right != oldRight || top != oldTop 
           || bottom != oldBottom) {         
       // The playerView's bounds changed, update the source hint rect to         
       // reflect its new bounds.         
       val sourceRectHint = Rect()         
       playerView.getGlobalVisibleRect(sourceRectHint)         
       setPictureInPictureParams(             
           PictureInPictureParams.Builder()                 
               .setSourceRectHint(sourceRectHint)                 
               .build()         
       )      
   }  
}
3.5.2.4 支持新手势

Android 12 现在支持对画中画窗口使用隐藏和“双指张合即可缩放”手势:

  • 如需隐藏窗口,用户可以将窗口拖动到左侧或右侧边缘。如需取消隐藏窗口,用户可以点按已隐藏窗口的可见部分或将其拖出。
  • 用户现在可以使用“双指张合即可缩放”手势调整画中画窗口的大小。

3.6 存储

Android 12 引入了对存储管理 API 的几项变更,下面几部分对此进行了介绍。

3.6.1 语音录音的新目录

系统会将存储在新Environment.DIRECTORY_RECORDINGS文件夹中的音频文件识别为录音。当您的应用对系统的媒体库执行查询时,您可以使用IS_RECORDING标志来检索录音。

3.6.2 媒体管理访问权限

用户可能会信任特定的应用来执行媒体管理,如频繁地修改媒体文件。如果您的应用以 Android 11(API 级别 30)或更高版本为目标平台且不是设备的默认图库应用,则每次您的应用尝试修改或删除文件时,您都必须向用户显示一个确认对话框。

如果您的应用以 Android 12 为目标平台,您可以请求用户向您的应用授予执行以下各项操作的权限,而无需针对每项文件操作提示用户:

为此,请完成以下步骤:

1.在应用的清单文件中声明新的MANAGE_MEDIA权限和READ_EXTERNAL_STORAGE权限。

为了调用 createWriteRequest() 而不显示确认对话框,请同时声明ACCESS_MEDIA_LOCATION权限。

2.在您的应用中,向用户显示一个界面,说明为什么他们可能需要向您的应用授予媒体管理访问权限。

3.调用ACTION_REQUEST_MANAGE_MEDIA intent 操作。这样会将用户引导至系统设置中的媒体管理应用屏幕。在此处,用户可以授予特殊应用访问权限。

3.6.3 应用存储访问权限

应用可以声明并创建一个自定义 activity,该 activity 在启动后可让用户管理应用存储在用户设备上的数据。应用可以在清单文件中使用android:manageSpaceActivity

属性声明此自定义“管理空间”activity。文件管理器应用可以启动此“管理空间”activity,即使应用未导出该 activity(即,该 activity 将android:exported设置为 false)也是如此。

在 Android 12 中,同时具有MANAGE_EXTERNAL_STORAGE权限和QUERY_ALL_PACKAGES权限的应用(如文件管理应用)可使用新的getManageSpaceActivityIntent()

将用户引导至其他应用的自定义“管理空间”activity(如果为该应用定义了一个)。

getManageSpaceActivityIntent() 方法接受软件包名称和请求代码,它返回以下某一项:

  • PendingIntent – 如果具有指定软件包名称的应用已定义自定义“管理空间”activity。调用 getManageSpaceActivityIntent() 方法的应用随后可以调用返回的 intent 以将用户引导至该自定义 activity。
  • null – 如果具有指定软件包名称的应用未定义“管理空间”activity。

3.6.4 扩展的文件访问权限支持

除了对 ExternalStorageProvider URI 的现有支持以外,getMediaUri()方法现在还支持 MediaDocumentsProvider URI。现在,系统在返回这些 URI 之前将其授予调用方。

此外,由createWriteRequest()授予的媒体 URI 现在支持File 类中的 API。这些 API 提供了读取、写入、重命名和删除文件的功能。

Android 12 应用适配指南 获取Android 12

Android 12 应用适配指南 获取Android 12

1.综述

本文档基于谷歌Android 12 Beta 1版本的变更输出,后续版本如有新的变更和特性,我们会刷新文档的相关章节内容,请开发者持续关注。

2.获取Android 12

2.1 小米机器升级Android 12指导

小米11 Ultra、小米11 Pro、小米11、Redmi K40 Pro 可以线刷Android 12测试版,您可以在我们的小米社区上下载ROM包手动升级。

2.2 Google原生机升级Android 12

开发者持有Pixel系列的机器可以直接ota升级,或者下载镜像升级,具体见链接:https://developer.android.google.cn/about/versions/12/download

2.3 Android 模拟器

  1. 依次点击 Tools > SDK Manager。
  2. 在 SDK Platforms 标签页中,选择 Android 12。
  3. 在 SDK Tools 标签页中,选择 Android SDK Build-Tools 31。
  4. 点击 OK 安装 SDK。

如需访问 Android 12 API 并测试应用与 Android 12 的兼容性,请打开模块级 ​build.gradle​ 或 ​build.gradle.kts​ 文件,并使用 Android 12 的值更新 ​compileSdkVersion​ 和 ​targetSdkVersion​:

android {
    compileSdkVersion("android-S")

    defaultConfig {
        targetSdkVersion("S")
    }
}

注意:如果您尚未完全支持 Android 12,您仍可以使用可调试的应用、Android 12 设备和兼容性框架来执行应用兼容性测试,而无需更改应用的 ​compileSdkVersion​ 或 ​targetSdkVersion​。

2.4 小米远程真机调试

小米云测平台已经提供关于小米11(Android 12)的远程真机服务,开发者可以直接通过以下链接进入远程真机进行试用体验:https://testit.miui.com/remote

Android 12应用适配指南

1.Android 12上的主要变更

1.1 兼容性

1.1.1 前台服务启动限制

说明:除少数特殊情况外,以Android 12为目标的应用程序无法在后台运行时启动前台服务。如果应用程序在后台运行时尝试启动前台服务,并且前台服务不满足以下特殊情况之一,则系统将抛出ForegroundServiceStartNotAllowedException异常。具体情况可查询  https://developer.android.com/about/versions/12/foreground-services 。

注意:如果一个应用程序调用Context.startForegroundService()启动另一个应用程序拥有的前台服务,则只有两个应用程序的API级别都为Android 12时,此页面上描述的限制才有效。

影响范围:targetSdkVersion为31的应用。

适配建议:如果您的应用想要在后台运行时启动前台服务但又不符合以上几种特例时,您可以尝试使用WorkManager启动优先级更高的后台任务。在Android 12中WorkManager将支持一种新作业-加急作业,它允许应用执行重要任务,同时使系统能够更好地控制对资源的访问权限。但是加急作业并不是万能的,在系统负载过高或者已超出加急作业配额限制的时候,系统会延迟启动新的加急作业。

1.1.2 前台服务通知延迟

为了在 Android 12 上提供针对短时间运行的前台服务的流畅体验,系统可以为某些前台服务延迟 10 秒显示前台服务通知。此更改使某些短期任务在显示通知之前完成。如果某项前台服务至少具有以下特征之一,则系统会在服务启动后立即显示相关通知:

1.该服务与包含操作按钮的通知相关联。

2.该服务的 foregroundServiceType 为mediaPlayback、mediaProjection 或 phoneCall。

3.该服务根据通知的类别属性中的定义,提供与通话、导航或媒体播放相关的用例。

注意:在未来的 Android 12 开发者预览版中,这些用例可能会发生变化。

4.该服务通过在设置通知时调用 setShowForegroundImmediately(),以停用行为变更。

1.1.3 待处理 intent 必须声明可变性

说明:如果您的应用以 Android 12 为目标平台,您必须为您的应用创建的每个 PendingIntent 对象指定可变性。这项额外的要求可提高应用的安全性。如需声明特定 PendingIntent 对象是否可变,请分别使用 PendingIntent.FLAG_MUTABLE 或 PendingIntent.FLAG_IMMUTABLE 标志。如果您的应用试图在不设置任何可变标志的情况下创建 PendingIntent 对象,系统会抛出 IllegalArgumentException。 

影响范围:targetSdkVersion为31的应用。

适配建议:您必须为您的应用创建的每个 PendingIntent 对象指定可变性。

1.1.4 非SDK接口名单更新

说明:Android 12相较于Android11有大量的非SDK接口名单更新,这些改变可分为两种: 

1.Android 11(API 级别 30)中不受支持的(已被列入灰名单)的非 SDK 接口现已在 Android 12 中被屏蔽;

2.在 Android 12 中被添加到 Android SDK 的非 SDK 接口。

各接口名单更新情况可查询https://developer.android.com/about/versions/12/non-sdk-12 。这些变更可能会对一些依赖了非SDK接口的应用带来不同程度的兼容性问题,特别是依赖了Android 11灰名单接口的应用,因为这些接口有一部分已经在Android 12中已归入屏蔽名单中。

影响范围:运行于Android 12上的所有应用。

适配建议:如果您的应用依赖于Android 12的受限接口,您应该开始计划升级SDK并迁移到SDK替代方案。

1.2 用户体验

每次Android系统的升级,几乎都会在交互体验上带来一些变化,此次Android 12也不例外。

1.2.1 接收内容的统一API

对于用户来说,目前还没什么方法在应用中插入和移动图片、视频和其他富有表现力的内容。为了使您的应用能够轻松接收富媒体内容,我们引入了全新的统一 API,便于您接受来自任何来源的内容。在Android 12上,您可以用粘贴板复制粘贴、键盘输入或者拖放图片或者视频等内容。

在Android 11以及之前的版本,每个界面机制(例如长按菜单或拖放)都有对应的 API。这意味着您必须单独与每个API集成,并为每种插入内容的机制添加类似的代码:

而在Android 12上,统一API会通过创建一个要实现的单一 API 来整合这些不同的代码路径,这样您就可以专注于应用特定的逻辑,而让平台处理其余的工作。

您可以向界面组件附加新接口 OnReceiveContentListener,并在通过任何机制插入内容时获得回调。此回调会成为您的代码处理接收所有内容(从纯文本和样式文本到标记、图片、视频、音频文件等)的唯一位置。

1.2.2 自定义通知变更

以前,自定义通知能够使用整个通知区域并提供自己的布局和样式。由此产生的反模式可能会令用户困惑,或在不同设备上引发布局兼容性问题。Android 12 改变了完全自定义通知的外观。

对于以 Android 12 为目标平台的应用,包含自定义内容视图的通知将不再使用完整通知区域;相反,系统会应用标准模板。此模板可确保自定义通知在所有状态下都与其他通知相同,例如,在收起状态下的通知图标和展开功能,以及在展开状态下的通知图标、应用名称和收起功能。

通过这种方式,Android 12 通过为用户提供可看到且熟悉的通知展开功能,使所有通知保持外观一致且易于浏览。

如果您的应用使用的是完全自定义的通知,我们建议您尽快使用新模板进行测试,并进行必要的调整:

1.启用自定义通知变更:

a.将应用的 targetSdkVersion 变更为 S 以启用新行为。

b.重新编译。

c.在搭载 Android 12 的设备或模拟器上安装您的应用。

2.测试所有使用自定义视图的通知,确保这些通知在通知栏中看起来符合预期。

3.请注意自定义视图规格。一般来说,提供给自定义通知的高度比之前小。在收起状态下,自定义内容的最大高度已从 106dp 减少到 48dp。此外,水平空间也减小了。

4.为了确保“浮动通知”状态看起来符合您的预期,请勿忘记将通知渠道的重要性提升至“高”(在屏幕中弹出)。

1.2.3 配套设备的权限授予

连接到手表时,面向Android 12及更高版本的开发者们现在可以使用配置文件将特定于设备类型的一组权限捆绑在一起,让用户在手机连接上设备时将一次性授予所有权限,从而简化了设置过程。用户删除应用程序或删除关联之后,系统也会删除相关权限。

1.3 图形、图像和媒体

1.3.1 更轻松的模糊,滤色器和其他效果

Android 12添加了新功能RenderEffect ,可将常见的图形效果(例如模糊,滤色器,Android着色器效果等)应用于Views和渲染层次结构。效果可以组合为链式效果(组成内部和外部效果)或混合效果。由于处理能力有限,部分的Android设备可能不支持该功能。通过调用View.setRenderEffect(RenderEffect),效果也可以适用于View的底层的RenderNode。

实施RenderEffect:view.setRenderEffect(RenderEffect.createBlurEffect(radiusX, radiusY, SHADER_TILE_MODE))。

1.3.2音频和触觉耦合效果

Android 12应用程序可以使用手机的振动器从音频会话中生成触觉反馈。这为更身临其境的游戏和音频体验提供了机会。例如,触觉增强的铃声可以帮助识别呼叫者,或者驾驶游戏可以模拟崎岖地形的感觉。

1.4 安全性与隐私

1.4.1 更安全地导出组件

说明:如果您的应用以 Android 12 为目标平台,且包含使用 intent 过滤器的 activity、服务或广播接收器,您必须为这些应用组件显式声明 android:exported 属性。如果 activity、服务或广播接收器使用 intent 过滤器,并且未显式声明 android:exported 的值,则您的应用将无法在搭载 Android 12 的设备上进行安装。

影响范围:targetSdkVersion为31的应用。

适配建议:您必须为包含intent过滤器的应用组件显式声明 android:exported 属性。

1.4.2 adb 备份限制

说明:为了保护私有应用数据,Android 12 变更了 adb backup 命令的默认行为。对于以 Android 12 为目标平台的应用,用户运行 adb backup 命令时,从设备导出的任何其他系统数据都不包含应用数据。

影响范围:targetSdkVersion为31的应用。

适配建议:如果您的测试或开发工作流程依赖于使用 adb backup 的应用数据,现在您可以选择通过在应用的清单文件中将 android:debuggable 设置为 true 来导出应用数据。

注意:为了帮助保护应用数据,请务必在发布应用前将 android:debuggable 设置为 false。

1.4.3 以不安全的方式启动嵌套 intent

说明:嵌套 intent 是在其他 intent 中作为 extra 传递的 intent。如果您的应用同时执行以下两项操作,就会发生 StrictMode 违规行为。

1.您的应用从已传递的 intent 的 extra 中解封嵌套 intent。

2.您的应用立即使用该嵌套 intent 启动应用组件,例如将 intent 传递给 startActivity()、startService() 或 bindService()。

为了提高平台安全性,Android 12 提供了一种调试功能,如果您的应用以不安全的方式启动嵌套 intent,此功能便会发出警告。如需检查您的应用中是否会以不安全的方式启动嵌套 intent,请在配置 VmPolicy 时调用 detectUnsafeIntentLaunch()。如果您的应用检测到 StrictMode 违规行为,您可能需要停止应用的执行以保护潜在的敏感信息。

影响范围:targetSdkVersion为31的应用。

适配建议:您的应用可能会启动嵌套 intent,以便在应用的各个组件之间导航,或代表其他应用执行操作。如需在这两种情况下最大限度地降低出现 StrictMode 违规行为的可能性,请执行以下操作:

1.嵌套 intent 的内部启动:确保这些组件不会被导出。

2.嵌套 intent 的跨应用启动:使用 PendingIntent 代替嵌套 intent。如此一来,当 PendingIntent 从包含它的 Intent 中解封时,应用组件可以使用调用进程的身份启动 PendingIntent。该配置允许提供程序应用向调用应用的任何组件(包括未导出的组件)发送回调。

1.4.4 应用无法关闭系统对话框

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

1.如果应用以 Android 12 为目标平台,则会发生 SecurityException。

2.如果应用以 Android 11或更低版本为目标平台,则系统不会执行 intent,并且打印log。

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

1.您的应用运行的是插桩测试。

2.您的应用以 Android 11 或更低版本为目标平台,并在抽屉式通知栏顶部显示一个窗口。

如果您的应用以 Android 12 为目标平台,在这种情况下您无需使用 ACTION_CLOSE_SYSTEM_DIALOGS。这是因为,如果在窗口位于抽屉式通知栏的顶部时您的应用调用 startActivity(),系统会自动关闭抽屉式通知栏。

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

影响范围:运行于Android 12上的所有应用。

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

说明:为了维持系统安全并保持良好的用户体验,Android 12 会阻止应用使用触摸事件,使用触摸事件时叠加层会以不安全的方式遮掩应用。换言之,系统会屏蔽穿透某些窗口的触摸操作。此变更会影响选择让触摸操作穿透其窗口的应用,例如使用 FLAG_NOT_TOUCHABLE 标志。包括但不限于以下示例:

1.需要 SYSTEM_ALERT_WINDOW 权限并使用 FLAG_NOT_TOUCHABLE 标志的叠加层,例如使用 TYPE_APPLICATION_OVERLAY 的窗口。

2.使用 FLAG_NOT_TOUCHABLE 标志的 activity 窗口。

3.消息框消息。

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

1.应用中的互动。您的应用会显示叠加层,并且只有当用户与您的应用进行互动时才会显示叠加层。

2.可信窗口。包括但不限于以下窗口:

    a.无障碍窗口

    b.输入法 (IME) 窗口

    c.Google 助理窗口

    注意:类型为 TYPE_APPLICATION_OVERLAY 的窗口不受信任。

3.不可见窗口。窗口的根视图是 GONE 或 INVISIBLE。

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

5.足够半透明的系统警报窗口。当组合后的不透明度小于或等于系统针对触摸的最大遮掩不透明度时,系统会将一组系统警报窗口视为足够半透明。如果系统屏蔽触摸操作,Logcat 会记录以下消息:Untrusted touch due to occlusion by PACKAGE_NAME。

影响范围:运行于Android 12上的所有应用。

1.4.6 隐藏应用程序覆盖窗口

为了使开发人员能够更好地控制用户与开发人员的应用进行交互时所看到的内容,Android 12引入了隐藏已授予SYSTEM_ALERT_WINDOW 权限的应用的叠加窗口的功能 。

声明 HIDE_OVERLAY_WINDOWS 许可后,应用程序可以调用 setHideOverlayWindows() 以指示当该应用程序自己的窗口可见时,所有TYPE_APPLICATION_OVERLAY类型的窗口都应隐藏。在显示敏感内容(例如交易确认)时,应用程序可能会选择执行此操作。

显示TYPE_APPLICATION_OVERLAY窗口类型的应用应考虑可能更适合其用例的替代方案,例如画中画或气泡。

1.4.7 安全锁屏通知操作

Android 12在Notification.Action.Builder中添加了新的setAuthenticationRequired 标志。您可以使用该标志为锁定设备上的通知添加额外的安全保护。

当通知操作(notificaiton action)中的setAuthenticationRequired值为true时,用户必须先完成身份认证之后才能在锁定设备上进行通知操作。在这之前,仅当用户通知操作启动Activity或直接答复时,系统才会要求在锁定的设备上进行身份验证。要实现此功能,请添加setAuthenticationRequired到通知操作中。

1.5 其他功能

1.5.1 圆角API

Android 12引入了RoundedCorner 和WindowInsets.getRoundedCorner(int position) API,提供了屏幕圆角的半径和中心点。使用这些API,您的应用程序可以避免UI元素在带有圆角的屏幕上被截断。这些API在非圆角屏幕的设备上不生效。

1.5.2 运动传感器的速率限制

为了保护用户的潜在敏感信息,如果您的应用以 Android 12 为目标平台,则系统会将来自某些运动传感器和位置传感器的数据刷新率限制为200 Hz。该数据包括由设备的加速度计,陀螺仪和地磁场传感器记录的值 。

如果您的应用程序以Android 12为目标平台并且需要以更高的速率收集运动传感器数据,则必须声明 HIGH_SAMPLING_RATE_SENSORS 权限。如果您的应用尝试在未声明此权限的情况下以更高的速率收集运动传感器数据, 系统会抛出SecurityException异常。

2.参考链接

[1]前台服务启动设置:https://developer.android.com/about/versions/12/foreground-services

[2]非SDK接口名单更新:https://developer.android.com/about/versions/12/non-sdk-12

[3]接收内容的统一API:https://developer.android.com/about/versions/12/features/unified-content-api

[4]自定义通知变更:https://developer.android.com/about/versions/12/behavior-changes-12#custom-notifications