0像素或1像素窗口适配说明

1.前言

很多App因业务或软件本身的需要,会起一个0像素或者1像素的窗口,例如调起1个像素点的 Activity 来提高进程优先级,达到保活的效果。我们为此专门做了优化,但是由于系统整体架构的限制,仍然会有一定的问题,这需要App也同步做一些优化来规避。

2.原因

  • 0像素窗口会导致系统没有 FocusWindow,造成key事件无法发给app。
  • 1像素窗口会导致该窗口成为 TouchedWindow,造成其他窗口不能滑动和点击。

3.建议

窗口 height=1,width=1,同时设置 FLAG_NOT_TOUCHABLE flag。

MIUI Android P 适配指南

1. 综述

欢迎加入MIUI Android P适配计划。谷歌官方已于2018年8月7号发布了Android P,代号为Pie(馅饼) 。MIUI也于同天发布了基于Android P的MIUI系统(下文将简称为MIUI P)。本文档将介绍如何将您的应用适配到MIUI P,防止应用出现运行时错误而造成损失。

重要声明:

  • 若您的应用未适配MIUI P,用户仍可以在应用商店下载您的应用,但会收到应用未适配Android P的相关提示。为确保您的应用能够正常运行在MIUI P的版本上,请尽快完成适配。
  • 为确保您的应用能够在第一时间适配MIUI P,我们强烈建议您提前适配Android P,适配方法请参考Google官方文档。您的应用如果已适配Android P,原则上将不会在MIUI P上出现Android P兼容性问题。若仍存在,请尽快通过本文最后一节所述的方式将问题反馈给我们,我们会尽快解决。

2. MIUI P包含的内容

MIUI P是支持下一代Android系统的版本,它包含了最新的Android P API及相关系统组件。除此之外,还包括了MIUI为小米手机用户深度订制、精心打造的UI、系统功能、系统应用等等。具体内容请参考MIUI的主页

3. 获取MIUI P

3.1. 自动升级

MIUI P正式发布后,您即可通过我们推送的系统更新升级到MIUI P的版本。

3.2. ROM包升级

您可以在我们的官方网站上下载ROM包手动升级。具体操作步骤请参考升级手册

3.3. 原生Android P获取

您可以通过此Google官方文档获取到Android P,开始Android P的适配工作。

4. 迁移指南

MIUI P(Android API 级别 P)引入了行为变更以及您的应用中可加以利用的新功能和 API。本节概述了迁移指南。您可以通过以下步骤,将应用迁移到 MIUI P 。

4.1. 确保兼容 Android P

验证您的应用能够在Android P平台上全功能运行。在此阶段,您不需要使用新的 API,也不需要更改应用的 targetSdkVersion,但可能需要进行一些细微的更改。具体请参考Android P行为变更

4.2. 确保兼容 MIUI P

您也可以直接在MIUI P上验证您的应用。同样,在此阶段,您不需要使用新的 API,也不需要更改应用的 targetSdkVersion,但可能需要进行一些细微的更改。

若您的应用已经能够在Android P上全功能运行,原则上也可以在MIUI P上全功能运行。但您仍需测试验证。

4.3. 更新您的目标版本并使用 MIUI P 功能

当您准备好利用平台的新功能时,将 targetSdkVersion 更新至“28”,验证应用是否仍可按预期方式运行,然后开始使用新的 API。

5. 新功能和API

MIUI P为用户和开发者引入众多新特性和新功能,具体的功能和API的变化可点击这里

本文重点介绍面向开发者的新功能。要了解新API,请阅读 API 差异报告或访问Android API 参考 — 为醒目起见,将突出显示新API。 请务必查阅 Android P 行为变更以了解平台变更可能给您的应用带来哪些方面的影响。

6. 重点适配问题

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

6.1. 使用了第三方加固工具

由于加固工具和系统耦合紧密,因此若您的应用使用了第三方加固工具,请尽快更新您所使用的加固工具以适配Android P。我们也会尽快推动加固厂商适配Android P。

在我们的测试中,存在兼容性问题的第三方加固工具有:

  • 梆梆加固
  • 360加固
  • 阿里聚安全
  • 百度加固

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

6.2. 依赖org.apache.http.legacy.boot.jar包

Android Runtime虚拟机实例初始化时会加载init.environ.rc中配置的jar包。Android P中init.environ.rc已移除了/system/framework/org.apache.http.legacy.boot.jar,该jar包内的类将不会被加载。如果您的应用使用了Apahce Http Client的类,且没有手动加载该jar包,则应用会报ClassNotFoundException异常,无法正常运行。因此,若您的应用依赖此包,请务必尽早适配。也请关注您使用的第三方SDK是否有依赖该jar包,如果有,请关注SDK的适配情况,并在SDK适配后第一时间更新SDK。

6.3. 使用了第三方SDK

一些第三方SDK也存在Android P的兼容性问题,导致应用无法运行。在我们的测试中,存在兼容性问题的SDK有:

  • 联通支付SDK
  • 腾讯广点通SDK

若您的应用使用了包括这两款在内的SDK,请务必关注相应厂商的适配进度,以便第一时间更新SDK以适配Android P。

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 13 应用适配指南4,安卓13新功能和API

Android 13 应用适配指南4,安卓13新功能和API

4.迁移指南

每次发布新的 Android 版本时,我们都会推出一些全新的功能并引入一些行为变更,目的就在于提高 Android 的实用性、安全性和性能。在许多情况下,您的应用都可以直接使用并完全按预期运行;而在其他的一些情况下,您可能需要对应用进行更新以适应这些平台变更。

源代码发布到 AOSP(Android 开源平台)后,用户随之就可能开始使用新平台。因此,应用必须做好准备,让用户能够正常使用,最好还能利用新的功能和 API 发挥新平台的最大优势。

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

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

4.1 确保与 Android 13 兼容

您必须测试现有应用在 Android 13 上的运行情况,以确保更新到最新版 Android 的用户获得良好的体验。有些平台变更可能会影响应用的行为方式,因此,必须尽早进行全面测试并对应用进行任何必要的调整。

您通常可以调整应用并发布更新,而无需更改应用的targetSdkVersion。同样,您应该也不需要使用新的 API 或更改应用的compileSdkVersion,但是这一点可能要取决于应用的构建方式及其所使用的平台功能。

在开始测试之前,请务必熟悉行为变更:所有应用。即使您不更改应用的targetSdkVersion,这些变更也可能会影响您的应用。

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

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

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

4.2 更新应用的目标平台并使用新 API 进行构建

发布应用的兼容版本后,下一步是通过更新targetSdkVersion并利用 Android 13 的新 API 和功能来添加对 Android 13 的全面支持。准备就绪后,您即可开始进行这些更新,请注意以新平台为目标平台方面的Google Play 要求

当您计划全面支持 Android 13 时,请查看行为变更:以Android 13为目标平台的应用。这些针对性的行为变更可能会导致需要解决的功能问题。在某些情况下,这些变更需要进行大量开发工作,因此我们建议您尽早了解并解决这些问题。为帮助确定影响您的应用的具体行为变更,请使用兼容性切换开关来测试已启用所选变更的应用。

以下是全面支持 Android 13 的步骤。

a. 获取 SDK,更改目标平台,使用新 API 进行构建

如需开始针对 Android 13 全面支持进行测试,请使用最新预览版的 Android Studio 下载 Android 13 SDK,以及所需的任何其他工具。接下来,更新应用的targetSdkVersion和compileSdkVersion,然后重新编译应用。如需了解详情,请参阅SDK 设置指南

b. 测试 Android 13 应用

编译应用并将其安装到搭载 Android 13 的设备上后,请开始测试,以确保应用能够在 Android 13 上正常运行。某些行为变更仅在您的应用以新平台为目标平台时才适用,因此您需要在开始之前阅读本文的行为变更。

与基本兼容性测试一样,完成所有流程和功能以查找问题。将测试重点放在以Android 13为目标平台的行为变更上。您还可以根据核心应用质量指南测试最佳做法检查您的应用。请务必查看并测试可能适用的受限非 SDK 接口的使用。留意突出显示这些访问权限的 logcat 警告,并使用 StrictMode 方法detectNonSdkApiUsage()以编程方式捕获它们。最后,请务必完整测试应用中的库和 SDK,确保它们在 Android 13 上按预期运行,并遵循隐私权、性能、用户体验、数据处理和权限方面的最佳做法。如果您遇到问题,请尝试更新到最新版本的 SDK,或联系 SDK 开发者寻求帮助。

c. 使用应用兼容性切换开关进行测试

Android 13 包含兼容性切换开关,可让您更轻松地在您的应用中测试针对性的行为变更。对于可调试的应用,切换开关可让您:

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

在您准备更改应用的目标平台时,或者在您积极开发以便支持 Android 13 时,切换开关将十分有用。如需了解详情,请参阅兼容性框架变更 (Android 13)

5.重点适配问题

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

5.1 Native Crash问题

移动安全联盟msa的SDK崩溃问题,特征:linker64

04-24 18:24:19.863 8983 8983 F DEBUG : Cmdline: xxx
04-24 18:24:19.863 8983 8983 F DEBUG : pid: 8470, tid: 8470, name: xxx >>> xxx <<<
04-24 18:24:19.863 8983 8983 F DEBUG : uid: 10195
04-24 18:24:19.864 8983 8983 F DEBUG : tagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE)
04-24 18:24:19.864 8983 8983 F DEBUG : pac_enabled_keys: 0000000000000000
04-24 18:24:19.864 8983 8983 F DEBUG : signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x000000704a6bd764
04-24 18:24:19.864 8983 8983 F DEBUG : x0 00000031b8a0a445 x1 0000007fdc63abc8 x2 0000000000000000 x3 0000007fdc63abb8
04-24 18:24:19.864 8983 8983 F DEBUG : x4 0000007049d551d0 x5 0000000000000000 x6 fefefefefefefeff x7 7f7f7f7f7f7f7f7f
04-24 18:24:19.864 8983 8983 F DEBUG : x8 0000000000428290 x9 000000704857c2e4 x10 0000000000000070 x11 0000000000000000
04-24 18:24:19.864 8983 8983 F DEBUG : x12 000000704cac0950 x13 f43ca3815c475fbd x14 0000000000000006 x15 ffffffffffffffff
04-24 18:24:19.865 8983 8983 F DEBUG : x16 0000000000000001 x17 000000704cbaa880 x18 0000006c8f0bdf90 x19 000000704b984018
04-24 18:24:19.865 8983 8983 F DEBUG : x20 0000007fdc63abc8 x21 00000031b8a0a445 x22 00000000b8a0a445 x23 0000000000000000
04-24 18:24:19.865 8983 8983 F DEBUG : x24 0000000000000000 x25 000000704b8abdd0 x26 000000704cbdd3a8 x27 000000704cbdf000
04-24 18:24:19.865 8983 8983 F DEBUG : x28 0000000000000000 x29 0000007fdc63aac0
04-24 18:24:19.866 8983 8983 F DEBUG : lr 000000704cb05130 sp 0000007fdc63aac0 pc 000000704cb0515c pst 0000000060001000
04-24 18:24:19.866 8983 8983 F DEBUG : backtrace:
04-24 18:24:19.866 8983 8983 F DEBUG : #00 pc 000000000005215c /apex/com.android.runtime/bin/linker64 (__dl__ZNK6soinfo10gnu_lookupER10SymbolNamePK12version_info+112) (BuildId: 5349b396f749a8df4f77c0798d1b2185)
04-24 18:24:19.866 8983 8983 F DEBUG : #01 pc 0000000000043d04 /apex/com.android.runtime/bin/linker64 (__dl__ZL24dlsym_handle_lookup_implP19android_namespace_tP6soinfoS2_PS2_R10SymbolNamePK12version_info+408) (BuildId: 5349b396f749a8df4f77c0798d1b2185)
04-24 18:24:19.867 8983 8983 F DEBUG : #02 pc 000000000003d834 /apex/com.android.runtime/bin/linker64 (__dl__Z8do_dlsymPvPKcS1_PKvPS_+904) (BuildId: 5349b396f749a8df4f77c0798d1b2185)
04-24 18:24:19.867 8983 8983 F DEBUG : #03 pc 0000000000038300 /apex/com.android.runtime/bin/linker64 (__dl__Z10dlsym_implPvPKcS1_PKv+92) (BuildId: 5349b396f749a8df4f77c0798d1b2185)
04-24 18:24:19.867 8983 8983 F DEBUG : #04 pc 0000000000001070 /apex/com.android.runtime/lib64/bionic/libdl.so (dlsym+20) (BuildId: 72dd43ea80dff185706bb3bdfcfaf051)
04-24 18:24:19.867 8983 8983 F DEBUG : #05 pc 000000000045ae84 /apex/com.android.art/lib64/libart.so (art::SharedLibrary::FindSymbolWithoutNativeBridge(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)+72) (BuildId: a479d08d252964da32f296b9ff5cf3ce)
04-24 18:24:19.867 8983 8983 F DEBUG : #06 pc 00000000004591d8 /apex/com.android.art/lib64/libart.so (art::JavaVMExt::FindCodeForNativeMethod(art::ArtMethod*)+1532) (BuildId: a479d08d252964da32f296b9ff5cf3ce)
04-24 18:24:19.867 8983 8983 F DEBUG : #07 pc 00000000006da6bc /apex/com.android.art/lib64/libart.so (artFindNativeMethodRunnable+328) (BuildId: a479d08d252964da32f296b9ff5cf3ce)
04-24 18:24:19.867 8983 8983 F DEBUG : #08 pc 00000000006da9d8 /apex/com.android.art/lib64/libart.so (artFindNativeMethod+592) (BuildId: a479d08d252964da32f296b9ff5cf3ce)
04-24 18:24:19.867 8983 8983 F DEBUG : #09 pc 000000000020f5f8 /apex/com.android.art/lib64/libart.so (art_jni_dlsym_lookup_stub+72) (BuildId: a479d08d252964da32f296b9ff5cf3ce)
04-24 18:24:19.868 8983 8983 F DEBUG : #10 pc 000000000021a154 /apex/com.android.art/lib64/libart.so (art_quick_generic_jni_trampoline+148) (BuildId: a479d08d252964da32f296b9ff5cf3ce)
04-24 18:24:19.868 8983 8983 F DEBUG : #11 pc 000000000021076c /apex/com.android.art/lib64/libart.so (art_quick_invoke_stub+556) (BuildId: a479d08d252964da32f296b9ff5cf3ce)
04-24 18:24:19.868 8983 8983 F DEBUG : #12 pc 000000000027a364 /apex/com.android.art/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+188) (BuildId: a479d08d252964da32f296b9ff5cf3ce)
04-24 18:24:19.868 8983 8983 F DEBUG : #13 pc 0000000000610d30 /apex/com.android.art/lib64/libart.so (art::JValue art::InvokeVirtualOrInterfaceWithJValues<art::ArtMethod*>(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, art::ArtMethod*, jvalue const*)+464) (BuildId: a479d08d252964da32f296b9ff5cf3ce)
04-24 18:24:19.868 8983 8983 F DEBUG : #14 pc 000000000046dfb0 /apex/com.android.art/lib64/libart.so (art::JNI<false>::CallBooleanMethodA(_JNIEnv*, _jobject*, _jmethodID*, jvalue const*)+600) (BuildId: a479d08d252964da32f296b9ff5cf3ce)
04-24 18:24:19.868 8983 8983 F DEBUG : #15 pc 00000000000323a4 <anonymous:6cfd9a9000>

如您使用了此SDK,请联系SDK提供方。

Android 13 应用适配指南3,安卓13新功能和API

Android 13 应用适配指南3,安卓13新功能和API

3.1.1.3 豁免

系统会针对特定类型的应用提供几种级别的豁免,下面几部分将介绍这些豁免。

豁免针对的是应用而不是进程。如果系统豁免了应用中的一个进程,则该应用中的所有其他进程也会被豁免。注意

:即使是设备上预装的应用,也必须至少满足下面一个部分中的条件才能获得豁免。

a. 完全不会显示在 FGS 任务管理器中

以下应用可以运行前台服务,而完全不会显示在任务管理器中:

b. 免于被用户停止当以下类型的应用运行前台服务时,它们会显示在 FGS 任务管理器中,但应用名称旁边没有可以供用户按的停止按钮:

3.1.1.4 测试

如需测试应用在用户停止应用的过程中和之后的行为是否符合预期,请在终端窗口中运行以下 adb 命令:

adb shell cmd activity stop-app PACKAGE_NAME

3.1.2 使用 JobScheduler 改进预提取作业处理

利用 JobScheduler,应用可使用JobInfo.Builder.setPrefetch()将特定作业标记为“预提取”作业,这意味着,理想情况下这些作业应该在应用下一次启动前提前一点运行,以提升用户体验。过去,JobScheduler 仅使用该信号让预提取作业有机会使用免费或多余的数据。

在 Android 13 中,系统现在会尝试确定应用下次启动的时间,并根据该估算值运行预提取作业。应用应尝试使用预提取作业来完成他们想要在下次应用启动前完成的任何工作。

3.1.2.1 代码示例
JobScheduler mJobScheduler = (JobScheduler) this.getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder mBuilder = new JobInfo.Builder(mJobId++, new ComponentName(this,MyJobService.class));
mBuilder.setPrefetch(true) //赋值为true
mJobScheduler.schedule(mBuilder.build());

3.1.3 电池资源利用率

Android 13 引入了以下省电措施:

  • 更新了有关系统何时将您的应用放入“受限”应用待机模式存储分区的规则
  • 对于您的应用在以下情况下可以执行的操作制定了新限制:用户因您应用的后台电池用量过高而将其置于“受限”状态。
  • 新增了系统通知,用于就后台电池用量过高长时间运行的前台服务向用户发出警告。
3.1.3.1 关于应用何时会进入“受限”应用待机模式存储分区的规则更新

除非您的应用符合豁免条件,否则当您的应用出现以下任何行为时,系统就会将其放入受限存储分区:

  • 用户有 8 天没有与您的应用互动。如果用户与另一个绑定到您应用的服务的应用互动,系统也会将您的应用视为“使用过”。

注意

:设备处于关闭状态的时间不会计入互动限制。

  • 您的应用在 24 小时内调用了过多的广播绑定
  • 您的应用在 24 小时内消耗了大量的设备电池电量。对于低 RAM 设备,此阈值可能会有所不同。

在衡量应用对设备电池续航时间的影响时,系统会考虑应用在几个不同方面执行的操作,包括:

  • 作业(包括加急作业)
  • 广播接收器
  • 后台服务
  • 系统是否已缓存应用的进程

用户互动可让您的应用退出“受限”存储分区:

当用户与您的应用互动时(包括通过以下几种方式互动),系统会将应用从受限存储分区中移出,并放入另一个应用待机模式存储分区

  • 用户点按您的应用发送的通知。

注意

:如果用户在不点按通知的情况下滑开通知,系统不会

将该操作视为与您的应用“互动”。

  • 用户在属于您应用的 widget 中执行操作。
  • 用户通过按媒体按钮影响您应用的前台服务。
  • 用户在与 Android Automotive OS 互动时连接到您的应用(您的应用在此过程中使用了前台服务或CONNECTION_TYPE_PROJECTION)。
  • 您的应用在画中画 (PiP) 模式下可见。
  • 您的应用是屏幕上当前运行的应用之一。(主要适用于大屏设备。)
3.1.3.2 关于受限后台电池用量的新限制

注意

:只有在您的应用以 Android 13 为目标平台时,此变更才会生效。

使用现有 Android 版本的用户能够调整应用在后台运行时可以执行的工作量。系统设置中的电池用量页面上会显示以下选项:

  • 无限制:允许后台工作,这可能会消耗更多电量。
  • 优化(默认):根据用户与应用互动的方式,优化应用执行后台工作的能力。
  • 受限:更注重延长设备的电池续航时间,而不是实现应用的全面功能。对于应用可以在后台执行的操作施加了更多限制。

注意

:如果用户在将您的应用置于“受限”状态之后启动了该应用,系统会暂时将该应用视为处于“优化”状态。当用户停止与您的应用互动并开始与另一个应用互动时,系统会将您的应用重新置于“受限”状态。

自 Android 9(API 级别 28)起,处于“受限”状态的应用具有以下限制:

  • 无法启动前台服务
  • 现有的前台服务会从前台移除
  • 不会触发闹钟
  • 不会执行作业

当您的应用以 Android 13 为目标平台时,除非应用因其他原因启动,否则系统不会传送以下任何广播:

  • BOOT_COMPLETED
  • LOCKED_BOOT_COMPLETED
3.1.3.3 与后台电池用量过高相关的系统通知

Android 13 引入了一个新的系统通知,当您的应用在 24 小时内消耗了大量设备电池电量时,就会显示该通知。这个新通知会针对搭载 Android 13 的设备上的所有应用显示,无论应用采用何种目标 SDK 版本都不例外。注意

:如果系统在应用显示与前台服务相关的通知时检测到应用的电池用量较高,系统会等到用户关闭通知,或前台服务完成,并且仅在您的应用继续消耗大量设备电池电量时才会显示该通知。

在衡量应用对设备电池续航时间的影响时,系统会考虑应用在几个不同方面执行的操作,包括:

如果针对您的应用显示了此通知,则至少在 24 小时后,才会再次在同一设备上显示该通知。

3.1.3.4 与长时间运行的前台服务相关的系统通知

如果系统检测到您的应用长时间运行某项前台服务(在 24 小时的时间段内至少运行 20 小时),便会发送通知邀请用户与前台服务 (FGS) 任务管理器互动。该通知包含以下内容:

 APP is running in the background for a long time. Tap to review.

注意

:如果系统针对您的应用显示了此通知,则至少在 30 天后才会再次显示类似通知。如果前台服务的类型为FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK或FOREGROUND_SERVICE_TYPE_LOCATION

,系统将不会显示此通知。

此外,如果您的应用在 24 小时内运行超过 4 小时的前台服务属于FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK或FOREGROUND_SERVICE_TYPE_LOCATION类型,则系统不会针对您的应用启动的任何前台服务发送长时间运行通知。

3.1.3.5 豁免

存在以下情况的应用不会受到 Android 13 中引入的任何省电措施的影响:

在以下情况下,您的应用可以免于进入“受限”应用待机模式存储分区,并可以绕过“8 天无活动”触发器:

在以下情况下,您的应用可以不受 Android 13 中引入的大多数省电措施的影响,但系统仍然会发送针对长时间运行的前台服务的通知

3.1.3.6 测试

以下几个部分将介绍几种测试 Android 13 中引入的省电措施会对应用产生何种影响的方式。

禁止在后台使用

如需禁止您的应用在后台运行,请在终端窗口中运行以下命令:

adb shell cmd appops set PACKAGE_NAME RUN_ANY_IN_BACKGROUND deny

将应用放入受限存储分区

如需强制系统将您的应用放入受限存储分区,请在终端窗口中运行以下命令:

adb shell am set-standby-bucket PACKAGE_NAME restricted

3.2 行为变更:以Android 13为目标平台的应用

3.2.1 通知运行时权限

Android 13 中引入了新的运行时权限,用于从应用发送非豁免通知:POST_NOTIFICATIONS。此更改有助于用户专注于最重要的通知。

强烈建议您尽快以 Android 13 为目标平台,以获享此功能提供的额外控制和灵活性。如果您继续以 12L(API 级别 32)或更低版本为目标平台,您将失去在应用功能环境中请求权限

的机会。

应用的影响总结如下:

Target SDK应用权限弹窗临时授权
Android 13 pre新安装创建第一个通知渠道时
现有首次启动Activity一直有效,直到权限弹窗中明确选择
Android 13新安装应用自行控制
现有应用自行控制首次启动Activity为止
3.2.1.1 使用新权限

如需向应用请求新的通知权限,请将应用更新为以 Android 13 为目标平台,并完成与请求其他运行时权限类似的流程,如以下几个部分所述。需要在应用的清单文件中声明的权限会显示在以下代码段中:

<manifest ...>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>    <application ...>
        ...
    </application>
</manifest>
3.2.1.2 应用功能取决于用户在权限对话框中所做的选择

在此对话框中,用户可执行以下操作:

注意

:如果用户点按不允许

,即使仅点按一次,系统也不会再次提示用户,除非他们卸载并重新安装您的应用。

下面几个部分介绍了根据用户操作的不同,应用会有哪些不同的行为表现。

a. 用户选择“允许”如果用户选择允许选项,您的应用可以执行以下操作:

b. 用户选择“不允许”如果用户选择不允许选项,您的应用将无法发送通知。除了几个特定角色之外,所有通知渠道都会被屏蔽。这类似于用户在系统设置中手动关闭应用的所有通知后发生的行为。

c. 用户滑开对话框

如果用户滑开对话框(即他们既没有选择允许,也没有选择不允许),会发生以下行为:

3.2.1.3 对新安装的应用的影响

如果用户在搭载 Android 13 的设备上安装您的应用,应用的通知默认处于关闭状态。在您请求新的权限且用户向您的应用授予该权限之前,您的应用都将无法发送通知。

权限对话框的显示时间取决于应用的目标 SDK 版本:

  • 如果您的应用以 Android 13 或更高版本为目标平台,应用将可以完全自行控制权限对话框的显示时间。您可以借此机会向用户说明应用需要此权限的原因,进而鼓励他们授予该权限。
  • 如果您的应用以 12L(API 级别 32)或更低版本为目标平台,系统会在您创建第一个通知渠道时显示权限对话框。这通常是在应用启动时。
3.2.1.4 对现有应用更新的影响

注意

:请考虑下面这种情形,即应用安装在搭载 12L 或更低版本的旧设备上,用户在该设备上允许接收通知,但想淘汰该设备。用户现在使用的是搭载 Android 13 的新设备,并通过备份和恢复功能恢复了应用。

在这种情况下,系统会将您的应用视为“现有应用”,因此该应用会获得发送通知的临时权限。

为最大限度地减少与新通知权限相关的中断,系统会自动向系统升级到 Android 13 之前用户设备上已安装的所有符合条件的应用临时授予新通知权限。此临时授权的持续时间取决于应用的目标 SDK 版本:

  • 如果应用以 Android 13 或更高版本为目标平台,则临时授权将持续到应用首次启动 activity 为止。

您的应用可以完全自行控制权限对话框的显示时间。您可以借此机会向用户说明应用需要此权限的原因,进而鼓励他们授予该权限。

  • 如果您的应用以 12L 或更低版本为目标平台,则临时授权将一直有效,直到用户在通知权限运行时对话框中明确选择一个选项。也就是说,如果用户在未做出选择的情况下关闭了权限提示,系统会保留应用的临时授权。
3.2.1.5 获得临时授权的资格要求

您的应用要获得临时授权必须满足以下条件:应用必须已具有通知渠道,并且用户未在搭载 12L 或更低版本的设备上明确停用应用的通知。

如果用户在搭载 12L 或更低版本的设备上停用了应用的通知,当设备升级到 Android 13 或更高版本后,该停用会继续有效。

3.2.1.6 豁免

媒体会话有关的通知不受此行为变更的影响。

3.2.1.7 最佳做法

本部分将介绍几种在应用中最有效地使用新通知权限的方式。

a. 更新应用的目标 SDK 版本

为了让应用更灵活地显示权限对话框,请将应用更新为以 Android 13 为目标平台。

b. 等待一段时间再显示通知权限提示

等到用户熟悉您的应用之后,再请求他们授予任何权限。

新用户可能想要探索您的应用,并切身体会每项通知请求可以带来的好处。您可以通过用户操作触发权限提示。下面列举了几个适合显示通知权限提示的时机:

  • 用户点按“提醒铃铛”按钮时。
  • 用户选择关注他人的社交媒体帐号时。
  • 用户提交外卖订单时。

下图显示了请求通知权限的建议工作流程。或者,您也可以设置一个请求以在应用启动时显示,但仅在应用第 3 次或第 4 次启动后才显示。

c. 在上下文中请求权限

在应用内请求通知权限时,请在正确的上下文中请求,以便用户明确了解通知的用途以及应该选择接收通知的原因。例如,电子邮件应用可能包含为每封新邮件发送通知的选项,或仅为用户是唯一收件人的邮件发送通知的选项。

借此机会明确向用户表明您的意图,有助于鼓励用户向您的应用授予通知权限。

d. 检查您的应用能否发送通知

用户必须为您的应用启用通知,您的应用才能发送通知。要确认用户是否已启用通知,请调用areNotificationsEnabled()

e. 以负责任的方式使用权限

获得发送通知的许可后,请负责任地使用该权限。用户可以查看您的应用每天发送的通知数量,并且可以随时撤消该权限

3.2.2 针对附近 Wi-Fi 设备的新运行时权限

Android 13 引入了NEARBY_WIFI_DEVICES运行时权限,该权限属于NEARBY_DEVICES权限组,适用于会管理设备与附近 Wi-Fi 接入点连接情况的应用。借助此权限,您可以更轻松地说明应用为何访问附近的 Wi-Fi 设备;在以前的 Android 版本中,这类应用需要声明ACCESS_FINE_LOCATION权限。

如果您的应用以 Android 13 为目标平台并调用多个不同的 Wi-Fi API,则必须从用户处获得这项新权限。

如果您的应用尝试在未获得适当权限的情况下调用 Wi-Fi API,则会发生SecurityException

3.2.2.1 受影响的用例

这项新权限会影响几个不同的 Wi-Fi 用例,包括以下用例:

  • 查找或连接到附近的设备,如打印机或媒体投射设备。通过该工作流,您的应用可以完成以下类型的任务: 通过带外方式(例如通过 BLE)接收 AP 信息。
  • 使用仅限本地使用的热点,通过 Wi-Fi 感知和连接功能发现并连接到设备。
  • 通过 Wi-Fi 直连发现和连接到设备。
  • 发起与已知 SSID(例如汽车或智能家居设备)的连接。
  • 开启仅限本地使用的热点。
  • 连接到附近的 Wi-Fi 感知设备。
3.2.2.2 该权限属于“附近的设备”权限组

NEARBY_WIFI_DEVICES权限是附近的设备

权限组的一部分。此权限组在 Android 12(API 级别 31)中添加,还包含与蓝牙和超宽带相关的权限。如果您的应用请求此权限组中的多项权限,用户会看到一个运行时对话框,其中会请求用户批准您的应用访问附近的设备。在系统设置中,用户必须以组的形式启用和停用附近的设备权限;例如,针对给定应用,用户无法既停用其 Wi-Fi 访问权限,但又保持启用其蓝牙使用权限。

3.2.2.3 坚定地声明您的应用不会推导物理位置

在以 Android 13 为目标平台时,请考虑您的应用是否会通过 Wi-Fi API 推导物理位置;如果不会,则应坚定声明此情况。如需做出此声明,请在应用的清单文件usesPermissionFlags属性设为neverForLocation,如以下代码段所示。此过程类似于您声明绝不会将蓝牙设备信息用于获取位置信息时的过程:

<manifest ...>
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"                     android:usesPermissionFlags="neverForLocation" />    <application ...>
        ...
    </application>
</manifest>
3.2.2.4 保持后向兼容性

由于NEARBY_WIFI_DEVICES权限仅适用于 Android 13 或更高版本,因此您应保留对ACCESS_FINE_LOCATION的所有声明,以便在您的应用中提供向后兼容性。不过,只要您坚定声明应用不会使用 Wi-Fi API 推导物理位置信息,就可以将此权限的最高 SDK 版本设为32,如下以下代码段所示:

<manifest ...>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
android:maxSdkVersion="32" />    <application ...>
        ...
    </application>
</manifest>
3.2.2.5 某些 API 仍需要位置信息权限

有几个 Wi-Fi API 仍然需要ACCESS_FINE_LOCATION权限才能获取位置信息,就像它们在 12L 及更低版本上的一样。示例包括WifiManager类中的以下方法:

3.2.2.6 检查需要新权限的 API

如果您的应用以 Android 13 或更高版本为目标平台,您必须声明NEARBY_WIFI_DEVICES权限才能调用以下任何 Wi-Fi API:

3.2.3 在后台使用身体传感器需要新的权限

Android 13 中引入了“在使用时”访问身体传感器(例如心率、体温和血氧饱和度)的概念。此访问模式与Android 10(API 级别 29)系统为位置信息引入的模式非常相似。

如果您的应用以 Android 13 为目标平台,并且在后台运行时需要访问身体传感器信息,那么除了现有的BODY_SENSORS权限外,您还必须声明新的BODY_SENSORS_BACKGROUND权限。注意

:这是受到“硬性限制”的权限,除非设备的安装程序针对您的应用将该权限列入了许可名单,否则您的应用将无法获得此权限。如需了解详情,请参阅有关受限权限的指南。

3.2.4 intent 过滤器会屏蔽不匹配的 intent

当您的应用向以 Android 13 或更高版本为目标平台的其他应用的导出组件发送 intent 时,仅当该 intent 与接收应用中的<intent-filter>元素匹配时,系统才会传送该 intent。换言之,系统会屏蔽所有不匹配的 intent,但以下情况除外:

  • 发送给其他应用的未声明任何 intent 过滤器的组件的 intent。
  • 发送给您应用中的其他组件的 intent。
  • 由系统发送的 intent。
  • 由具有根级特权的用户发送的 intent。

3.2.5 更安全地导出上下文注册的接收器

为了帮助提高运行时接收器的安全性,Android 13 允许您指定您应用中的特定广播接收器是否应被导出以及是否对设备上的其他应用可见。如果导出广播接收器,其他应用将可以向您的应用发送不受保护的广播。此导出配置在以 Android 13 或更高版本为目标平台的应用中可用,有助于防止一个主要的应用漏洞来源。

在以前的 Android 版本中,设备上的任何应用都可以向动态注册的接收器发送不受保护的广播,除非该接收器受签名权限的保护。

要实现此安全增强措施,请执行以下操作:

a. 启用DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED兼容性框架更改。

b. 在应用的每个广播接收器中,明确指明其他应用是否可以向其发送广播,如以下代码段所示:

// This broadcast receiver should be able to receive broadcasts from other apps.
// This option causes the same behavior as setting the broadcast receiver's
// "exported" attribute to true in your app's manifest.
context.registerReceiver(sharedBroadcastReceiver, intentFilter,
    RECEIVER_EXPORTED);
// For app safety reasons, this private broadcast receiver should **NOT**
// be able to receive broadcasts from other apps.
context.registerReceiver(privateBroadcastReceiver, intentFilter,
    RECEIVER_NOT_EXPORTED);

注意

:如果启用了DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED

兼容性框架更改,则必须

为每个广播接收器指定RECEIVER_EXPORTED或RECEIVER_NOT_EXPORTED。否则,当您尝试注册广播接收器时,系统会抛出SecurityException

为了帮助检测这种情况,Android Studio 将会采用一个 lint 规则,而即将发布的ContextCompat将会检查接收器配置是否正确。

Android 13 应用适配指南2,安卓13新功能和API

2.新功能和API

2.1 照片选择器

Android 13 包含对新照片选择器工具的支持。此工具为用户提供了一种安全的内置媒体文件选择方式,让其无需向应用授予对整个媒体库的访问权限。注意

:即将发布的 Google Play 系统更新预计会包含与照片选择器有关的新功能。在一项此类更新中,该库将增加对以 Android 11(API 级别 30)或更高版本为目标平台的应用(不包括 Android Go 设备)的支持。

2.1.1 选择媒体

照片选择器提供了一个可浏览、可搜索的界面,其中按日期(从最近到最早)顺序向用户呈现其媒体库中的文件。您可以指定用户只能看到照片或只能看到视频,并且默认情况下,允许的媒体选择量上限设置为 1。

2.1.2 定义分享限制

应用可以声明android.provider.extra.PICK_IMAGES_MAX的值,该值表示在向用户显示时照片选择器中显示的媒体文件数量上限。例如,如果您提示用户为其帐号选择要求的个人资料照片,请将一张照片设置为分享要求上限。注意

:如果您选择的上限为 1 张,照片选择器会以半屏模式打开。

如需在单选模式下启动照片选择器,请执行以下操作:

// Launches photo picker in single-select mode.
// This means that the user can select one photo or video.
Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
startActivityForResult(intent, PHOTO_PICKER_REQUEST_CODE);

2.1.3 选择多张照片或多个视频

如果应用的用例需要用户选择多张照片或多个视频,您可以使用EXTRA_PICK_IMAGES_MAX extra 指定照片选择器中应显示照片的数量上限,如以下代码段中所示:

// Launches photo picker in multi-select mode.
// This means that user can select multiple photos/videos, up to the limit
// specified by the app in the extra (10 in this example).
final int maxNumPhotosAndVideos = 10;
Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, maxNumPhotosAndVideos);
startActivityForResult(intent, PHOTO_PICKER_MULTI_SELECT_REQUEST_CODE);

请注意,可指定为文件数量上限的最大数字存在平台限制。如需访问此限制,请调用MediaStore#getPickImagesMaxLimit()。

2.1.4 处理照片选择器结果

照片选择器启动后,使用新的ACTION_PICK_IMAGES intent 来处理结果。该选择器会返回一组 URI:

// onActivityResult() handles callbacks from the photo picker.
@Override
protected void onActivityResult(
    int requestCode, int resultCode, final Intent data) {
    if (resultCode != Activity.RESULT_OK) {
        // Handle error
        return;
    }
    switch(requestCode) {
        case REQUEST_PHOTO_PICKER_SINGLE_SELECT:
            // Get photo picker response for single select.
            Uri currentUri = data.getData();
            // Do stuff with the photo/video URI.
            return;
        case REQUEST_PHOTO_PICKER_MULTI_SELECT:
            // Get photo picker response for multi select
            for (int i = 0; i < data.getClipData().getItemCount(); i++) {
                Uri currentUri = data.getClipData().getItemAt(i).getUri();
                // Do stuff with each photo/video URI.
            }
            return;
    }
}

默认情况下,照片选择器会既显示照片又显示视频。您还可以在 setType() 方法中设置 MIME 类型,以便按“仅显示照片”或“仅显示视频”进行过滤。例如,如需在照片选择器中仅显示视频,请将video/*传入setType():

// Launches photo picker for videos only in single select mode.
Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
intent.setType("video/*");
startActivityForResult(intent, );
// Apps can also change the mimeType to allow users to select
// images only - intent.setType("images/*");
// or a specific mimeType - intent.setType("image/gif");

注意:您的应用不能持续访问从此 intent 返回的 URI。当应用的进程结束后,该应用将无法访问 URI。

2.2 应用内语言选择器

在许多情况下,多语言用户会将其系统语言设置为某一种语言(例如英语),但又想为特定应用选择其他语言(例如荷兰语、中文或印地语)。为了帮助应用为这些用户提供更好的体验,Android 13 针对支持多种语言的应用引入了以下新功能:

使用自定义应用内语言选择器的应用应当使用这些新 API,以确保无论用户通过何种方式选择其语言偏好设置,都能获得一致的用户体验。这些新的 API 还有助于减少样板代码的编写。

为了向后兼容以前的 Android 版本,我们还会从Appcompat 1.6.0-alpha01开始在 AndroidX 中提供这些 API。

  • 允许用户为每个应用选择首选语言的系统设置

不支持多种语言的应用不受这些变更的影响。

2.2.1 API 实现

对于当前未使用自定义应用内语言选择器的应用,无需执行任何其他操作。

对于具有或想要使用应用内语言选择器的应用,请使用这些新 API(而非应用的自定义逻辑)来处理相关设置和获取用户对应用的首选语言设置。

使用 AndroidX 支持库来实现

为了向后兼容以前的 Android 版本,建议使用 AndroidX 支持库来实现应用内语言选择器。使用Appcompat 1.6.0-alpha01或更高版本中提供的setApplicationLocales()方法。

例如,如需设置用户的首选语言,您需要让用户在语言选择器中选择语言区域,然后在系统中设置该值:

LocaleListCompat appLocale = LocaleListCompat.forLanguageTags("xx-YY");
// Call this on the main thread as it may require Activity.restart()
AppCompatDelegate.setApplicationLocales(appLocale);

使用 Android 框架 API 来实现

您还可以通过setApplicationLocales()getApplicationLocales()方法,使用 Android 框架 API 来实现应用内语言选择器。

例如,如需设置用户的首选语言,您需要让用户在语言选择器中选择语言区域,然后在系统中设置该值:

// 1. Inside an activity, in-app language picker gets an input locale "xx-YY"
// 2. App calls the API to set its localem
Context.getSystemService(LocaleManager.class).setApplicationLocales(newLocaleList(Locale.forLanguageTag("xx-YY")));
// 3. The system updates the locale and restarts the app, including any configuration updates
// 4. The app is now displayed in "xx-YY" language

如需获取用户当前的首选语言以显示在语言选择器中,您的应用可以从系统中取回该值:

// 1. App calls the API to get the preferred locale
LocaleList currentAppLocales = mContext.getSystemService(LocaleManager.class).getApplicationLocales();
// 2. App uses the returned LocaleList to display languages to the user

2.2.2 面向用户的系统设置

用户可以通过新的系统设置为每个应用选择首选语言。他们可以通过以下两种方式访问这些设置:

  • 通过系统设置访问

设置 > 系统 > 语言和输入法 > 应用语言 >(选择应用)

  • 通过应用设置访问

设置 > 应用 >(选择一款应用)> 语言

已知问题

在测试应用时,有一些已知问题需要注意。

  • 可用语言列表中可能不包含您的应用支持的语言。
  • 如果您的应用使用拆分 APK,当应用语言区域发生变化时,系统不会自动下载这些 APK。
  • 现在的界面只是一个初步版本,在后续版本中会不断更改。

2.2.3 DEMO演示

2.3 可由开发者降级的权限

从 Android 13 开始,应用可以撤消先前由系统或用户授予的运行时权限。此 API 可以帮助应用保护用户的隐私。

如需撤消特定运行时权限,请将该权限的名称传入revokeSelfPermissionOnKill()。如需同时撤消一组运行时权限,请将这组权限的名称传入revokeSelfPermissionsOnKill()。撤消是异步发生的,会终止与应用的 UID 相关联的所有进程。

系统只有在安全的情况下才会触发撤消操作。具体而言,当有应用组件仍在前台运行,或者有另一个应用正在访问您应用的组件(如 content provider)时,不会发生撤消。如果您想立即撤消权限,可以调用exit()。但是,对exit()进行此类调用可能会导致当前正在访问您应用的其他应用出现未定义的行为或崩溃。注意

:为了让系统设置表明您的应用不会访问特定权限组中的数据,您必须撤消该权限组中的所有

权限。在这种情况下,调用revokeSelfPermissionsOnKill()

会很有帮助。

2.4 可编程的着色器

Android 13 添加了对可编程RuntimeShader对象的支持,其行为是使用 Android 图形着色语言 (AGSL) 定义的。AGSL 与 GLSL 共用大部分语法,但可用于 Android 渲染引擎中以自定义 Android 画布中的绘制行为以及过滤 View 内容。Android 在内部使用这些着色器来实现涟漪效果模糊以及拉伸滚动,并且 Android 13 让您能为应用制作类似的高级效果。改写自此GLSL 着色器的 AGSL 动画着色器:

基于可编程着色器实现的一个简单的绘制效果如下:

2.5 更快断字

断字让分行的文本更易于阅读,并且有助于使界面更具自适应性。在 Android 13 中,我们将断字性能优化了多达 200%,因此您现在可以在TextView中启用断字功能,这几乎不影响渲染性能。如需启用更快断字功能,请在setHyphenationFrequency()中使用新的fullFastnormalFast频率。

2.5.1 API变化

Android 13在已有断字模式基础上新增两种Fast模式:fullFastnormalFast,在原有基础上测量更快。

2.5.2 使用建议

在布局中添加属性android:hyphenationFrequency:

<!--少量快速断字模式-->
<TextView android:hyphenationFrequency="fullFast"/>
<!--标准快速断字模式-->
<TextView android:hyphenationFrequency="normalFast"/>

代码中直接设置:

//标准快速断字模式
mTextView.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL_FAST)
//少量快速断字模式
mTextView.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL_FAST)

3.影响应用的行为变更

3.1 行为变更:所有应用

3.1.1 前台服务 (FGS) 任务管理器

无论应用采用何种目标 SDK 版本,Android 13 都允许用户从抽屉式通知栏中停止前台服务。这项新功能称为前台服务 (FGS) 任务管理器,它会显示当前正在运行前台服务的应用列表。此列表的标签为使用中的应用。每个应用旁边都有一个停止按钮。

下图说明了搭载 Android 13 的设备上的 FGS 任务管理器的工作流程:

3.1.1.1 用户操作会停止您的整个应用

当用户在 FGS 任务管理器中按您应用旁边的停止按钮时,系统会停止您的整个应用,而不仅仅是正在运行的前台服务。

比较“向上滑动”用户操作和“强行停止”用户操作的行为:

请参见下表中 FGS 任务管理器与现有功能的比较:从最近用过屏幕“向上滑动”和“强行停止”出现异常的应用

用户从 FGS 任务管理器停止应用时,不会发送任何回调。

当用户按停止按钮时,系统不会向您的应用发送任何回调。当应用启动备份时,建议您检查一下现有ApplicationExitInfoAPI 中的新REASON_USER_REQUESTED原因,这可能会很有帮助。

3.1.1.2 与长时间运行的前台服务相关的系统提示

如果系统检测到您的应用长时间运行某项前台服务(在 24 小时的时间段内至少运行 20 小时),便会发送通知邀请用户与 FGS 任务管理器互动。详细了解就长时间运行的前台服务提醒用户的新系统通知

Android 13 应用适配指南

1.获取Android 13

1.1 小米手机升级Android13

如果您已错过小米社区发布的关于MIUI基于Android 13的升级公开招募,可前往开发者内测申请权限,或请您联系自己公司商务或运营同学,联系我们,我们会为您开通有关权限并指导升级。

目前支持升级Android 13的MIUI开发版的机型有(有序更新中):

小米:小米12、小米12 Pro(高通平台)

Redmi: K50G(电竞版)、K40S、Note 11T Pro/Pro+

1.2 小米云测平台已上线

今年我们提供了足量的云测设备来供开发者适配,小米云测平台

提示:如果发现云测平台没有权限,可以通过小米云测平台权限申请

1.3 Google原生机升级Android 13

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

1.3 Android 模拟器

在 Android Studio 中,您可以按如下方式安装 Android 13 SDK:

  • 依次点击 Tools > SDK Manager。
  • 在“SDK Platforms”标签页中,选择 Android 13。
  • 在 SDK Tools 标签页中,选择 Android SDK Build-Tools 33。

点击OK安装 SDK。

如需访问 Android 13 API 并测试您的应用与 Android 13 的兼容性,请打开模块级build.gradle或build.gradle.kts文件,并使用 Android 13 所对应的值对它们进行更新:如何设置这些值的格式取决于您所使用的 Android Gradle 插件 (AGP) 版本。

注意:如果您尚未准备好完全支持 Android 13,您仍然可以使用可调试的应用、Android 13 设备和兼容性框架来执行应用兼容性测试,而无需更改应用以使其与预览版 SDK 兼容或以此为目标平台。

小米开发平台 关于无GPS硬件机型的位置服务适配说明

小米开发平台 关于无GPS硬件机型的位置服务适配说明

1.背景

由于部分WiFi版本的小米平板(例如:小米平板5 WiFi版等)无GPS硬件,即使用户开启位置服务,应用也无法获取到GPS Provider,部分依赖于GPS位置服务的应用可能会提示“未开启位置服务或出现其他问题”,用户体验较差。

2.适配说明

针对无GPS硬件的机型,应用可以通过检测Location Proveders中是否包含”gps”来判断是否有GPS硬件。

另外,可以通过检测Network Provider状态来判断用户是否打开位置服务。

参考Demo如下:

LocationManager lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
// 无GPS设备通过判断Network Provider来判断位置服务是否打开
// 其他设备仍然通过GPS Provider来判断位置服务是否打开
if (!isGPSExist(lm) && isNetworkLocationAble(lm) || isGpsAble(lm)) {
    // 用户已开启位置服务
    // 应用自身定位逻辑
} else {
    // 提醒用户未开启位置服务,引导用户至位置服务设置页面
    openLocationService();
}

// 应用可以通过以下方式判断是否有GPS硬件
public static boolean isGPSExist(LocationManager lm) {
    return lm.getAllProviders().contains("gps");
}

private boolean isGpsAble(LocationManager lm){
    return lm.isProviderEnabled(LocationManager.GPS_PROVIDER);
}

private boolean isNetworkLocationAble(LocationManager lm){
     return lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
}

private void openLocationService()(){
    Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
    startActivity(intent);
}

3.优化建议

如果应用对GPS有强依赖,建议应用优化新增相关友好提示。

小米开发平台openGL ES glFinish使用规范

小米开发平台openGL ES glFinish使用规范

1.背景

在高通最新的平台上,为了进一步提升性能,启用了 CompressTexturesOnUpload 功能,如果应用不遵循 openGL glFinish 规范,会​进一步增加应用渲染数据出现异常的概率。

2.规范说明

应用在修改更新纹理后,openGL 规范要求应用程序执行完 glFinish 后, 其他地方才能去操作使用此纹理(注意高通平台目前只需要 glFlush, 但对于 openGL 规范,需要执行 glFinish,详见 OpenGL ES 3 specification section D “Propagating changes to objects”)。

3.案例分析

  • 美图秀秀相机预览人像扭曲
  • 美颜相机预览画面偏暗
  • 问题原因

应用的 thread 6 和 thread 7 共享 GL Context,然后 thread 6 对 thread 7 上传的纹理(texture13)进行采样,但是在 thread7 中,纹理更新时没有 进行 glFinish 或 glFlush,导致数据渲染出现问题。所以 thread7 应该在纹理更新后调用 glFinish 或 glFlush,然后 thread 6 再使用 texture13 来渲染。

小米开发平台应用删除前台服务通知导致的闪退问题说明

小米开发平台应用删除前台服务通知导致的闪退问题说明

  • 近期发现较多应用出现闪退现象,具体trace如下:
AndroidRuntime: FATAL EXCEPTION: main
AndroidRuntime: Process: com.xxx.app, PID: 25339
AndroidRuntime: java.lang.SecurityException: Not allowed to delete channel XXX with a foreground service
AndroidRuntime:         at android.os.Parcel.createExceptionOrNull(Parcel.java:2376)
AndroidRuntime:         at android.os.Parcel.createException(Parcel.java:2360)
AndroidRuntime:         at android.os.Parcel.readException(Parcel.java:2343)
AndroidRuntime:         at android.os.Parcel.readException(Parcel.java:2285)
AndroidRuntime:         at android.app.INotificationManager$Stub$Proxy.deleteNotificationChannel(INotificationManager.java:4040)
AndroidRuntime:         at android.app.NotificationManager.deleteNotificationChannel(NotificationManager.java:909)
AndroidRuntime:         at androidx.core.app.NotificationManagerCompat.deleteNotificationChannel(SourceFile:2)
AndroidRuntime:         at com.gyf.cactus.ext.c$a.run(SourceFile:1)
AndroidRuntime:         at android.os.Handler.handleCallback(Handler.java:938)
AndroidRuntime:         at android.os.Handler.dispatchMessage(Handler.java:99)
AndroidRuntime:         at android.os.Looper.loop(Looper.java:236)
AndroidRuntime:         at android.app.ActivityThread.main(ActivityThread.java:8142)
AndroidRuntime:         at java.lang.reflect.Method.invoke(Native Method)
AndroidRuntime:         at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:656)
AndroidRuntime:         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:967)
AndroidRuntime: Caused by: android.os.RemoteException: Remote stack trace:
AndroidRuntime:         at com.android.server.notification.NotificationManagerService$10.enforceDeletingChannelHasNoFgService(NotificationManagerService.java:3427)
AndroidRuntime:         at com.android.server.notification.NotificationManagerService$10.deleteNotificationChannel(NotificationManagerService.java:3440)
AndroidRuntime:         at android.app.INotificationManager$Stub.onTransact(INotificationManager.java:1737)
AndroidRuntime:         at android.os.Binder.execTransactInternal(Binder.java:1160)
AndroidRuntime:         at android.os.Binder.execTransact(Binder.java:1129)
  • 原因是受Google的一笔安全校验Patch影响:https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java;l=3413
private void enforceDeletingChannelHasNoFgService(String pkg, int userId,
                String channelId) {
            if (mAmi.hasForegroundServiceNotification(pkg, userId, channelId)) {
                Slog.w(TAG, "Package u" + userId + "/" + pkg
                        + " may not delete notification channel '"
                        + channelId + "' with fg service");
                throw new SecurityException("Not allowed to delete channel " + channelId
                        + " with a foreground service");
            }
        }

也就是Google限制了存在前台服务时,不允许应用删除与前台服务绑定的channel。

以上,请开发者检查处理,感谢支持!

小米开发平台 应用频繁调用getSubscriberId/getDeviceId优化说明

应用频繁调用getSubscriberId/getDeviceId优化说明

getSubscriberId/getDeviceId返回null情况说明

Android 10隐私权变更应用无法通过以下方法获取设备标识符:

Build

getSerial()

TelephonyManager 

getImei()
getDeviceId()
getMeid()
getSimSerialNumber()
getSubscriberId()

具体参见:https://developer.android.com/about/versions/10/privacy/changes?hl=zh-cn

由于Android 10隐私权限变更导致应用无法获取设备标识符,应用无法获取设备标识符后一直频繁调用相关接口尝试获取,从而导致系统相关进程binder占满,引发一系列问题:如手机重启,应用黑屏卡顿等问题。因此建议App优化,不要频繁调用以上接口。

小米开发平台应用调用Camera预览效果适配说明

小米开发平台应用调用Camera预览效果适配说明

1.应用中拍摄或扫一扫功能预览页面出现拉伸现象

由于不同手机屏幕尺寸比例不尽相同,或许应用在调用Camera预览实现方法不标准或第三方SDK维护不及时,提供的预览窗口宽高比例和Camera输出图像的宽高比例不一致,以至于应用在使用拍摄或扫一扫功能时预览页面就可能出现拉伸现象。

在此建议应用开发者使用的预览窗口宽高比例和Camera输出图像的宽高比例尽量保持一致,这样预览显示的画面即可以正常显示。为了解决应用频繁适配工作,希望开发者参考标准的Camera预览使用示例实现功能。

2.Camera预览接口使用示例

2.1.获取Camera支持的预览图像输出分辨率

//Camera API1
Camera mCamera = Camera.open(cameraId);
List<Size> supportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes()
 
//Camera API2
Size[] supportedPreviewSizes = CameraManager.getCameraCharacteristics(cameraId).get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputSizes(klass)

2.2.根据预览窗口Surface的宽高来选取最佳Camera图像输出尺寸

  • Camera API1:
//获取预览窗口的宽高
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // We purposely disregard child measurements because act as a
    // wrapper to a SurfaceView that centers the camera preview instead
    // of stretching it.
    final int width = resolveSize(getSuggestedMinimumWidth(),
            widthMeasureSpec);
    final int height = resolveSize(getSuggestedMinimumHeight(),
            heightMeasureSpec);
    setMeasuredDimension(width, height);
 
    if (mSupportedPreviewSizes != null) {
        mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width,
                height);
    }
 
    if (mCamera != null) {
      Camera.Parameters parameters = mCamera.getParameters();
      parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
 
      mCamera.setParameters(parameters);
    }
}
//得到最佳Camera图像输出尺寸
public Camera.Size getOptimalSize(@NonNull List<Camera.Size> sizes, int w, int h) {
    final double ASPECT_TOLERANCE = 0.1;
    double targetRatio = (double) w / h;
    Camera.Size optimalSize = null;
    double minDiff = Double.MAX_VALUE;
 
    int targetHeight = h;
 
    for (Camera.Size size : sizes) {
      double ratio = (double) size.width / size.height;
      if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
      if (Math.abs(size.height - targetHeight) < minDiff) {
        optimalSize = size;
        minDiff = Math.abs(size.height - targetHeight);
      }
    }
 
    if (optimalSize == null) {
      minDiff = Double.MAX_VALUE;
      for (Camera.Size size : sizes) {
        if (Math.abs(size.height - targetHeight) < minDiff) {
          optimalSize = size;
          minDiff = Math.abs(size.height - targetHeight);
        }
      }
    }
 
    return optimalSize;
  }

详细实现见:https://cs.android.com/android/platform/superproject/+/master:development/samples/HoneycombGallery/src/com/example/android/hcgallery/CameraFragment.java

  • Camera API2:
//获取预览窗口的宽高
private final TextureView.SurfaceTextureListener mSurfaceTextureListener
        = new TextureView.SurfaceTextureListener() {
 
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
        openCamera(width, height);
    }
 
    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
        configureTransform(width, height);
    }
 
    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
        return true;
    }
 
    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture texture) {
    }
 
};
//得到最佳Camera图像输出尺寸
private static Size chooseOptimalSize(Size[] choices, int textureViewWidth,
        int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {
 
    // Collect the supported resolutions that are at least as big as the preview Surface
    List<Size> bigEnough = new ArrayList<>();
    // Collect the supported resolutions that are smaller than the preview Surface
    List<Size> notBigEnough = new ArrayList<>();
    int w = aspectRatio.getWidth();
    int h = aspectRatio.getHeight();
    for (Size option : choices) {
        if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
                option.getHeight() == option.getWidth() * h / w) {
            if (option.getWidth() >= textureViewWidth &&
                option.getHeight() >= textureViewHeight) {
                bigEnough.add(option);
            } else {
                notBigEnough.add(option);
            }
        }
    }
 
    // Pick the smallest of those big enough. If there is no one big enough, pick the
    // largest of those not big enough.
    if (bigEnough.size() > 0) {
        return Collections.min(bigEnough, new CompareSizesByArea());
    } else if (notBigEnough.size() > 0) {
        return Collections.max(notBigEnough, new CompareSizesByArea());
    } else {
        Log.e(TAG, "Couldn't find any suitable preview size");
        return choices[0];
    }
}

详细实现见:https://cs.android.com/android/platform/superproject/+/master:development/samples/browseable/Camera2Basic/src/com.example.android.camera2basic/Camera2BasicFragment.java

2.3.将获取到的最佳图像输出尺寸设置到Camera

//Camera API1
mCameraParameters.setPreviewSize(size.getWidth(), size.getHeight())
 
//Camera API2
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight())

小米开发平台Audio接口使用规范

小米开发平台Audio接口使用规范

1. 关于setSpeakerphoneOn方面的使用

问题描述:

在MIUI的项目开发过程中,经常收到用户反馈通话声音会自动外放,经分析排查发现均是第三方应用在通话期间调用了setSpeakerphoneOn(true)方法引起的,影响用户正常通话。

建议:

音频类应用可以利用AudioFocus机制或者监听电话状态来进行控制处理,在手机通话期间不要调用setSpeakerphoneOn方法,从而避免对正常通话的影响。

2. 关于setMode的使用

问题描述:

当前很多APP,多见于社交或需要队友语音的游戏。使用这个接口来设置手机通话模式,改变手机声音的输出设备。当电话模式处于“AudioManager.MODE_IN_COMMUNICATION”或“AudioManager.MODE_IN_CALL”时,声音会默认从听筒发声。且用户默认音量调节类型会变为通话音量类型。

建议:

开发过程中,setMode(AudioManager.MODE_IN_COMMUNICATION)和setMode(AudioManager.MODE_NORMAL)务必成对使用。需要考虑应用异常的情况下,也能够将电话模式重置为normal状态。

3. 关于getStreamVolume的使用

使用建议:

通过该方法得到的音量值受多方因素影响。需要充分考虑当前设备的audiopolicy route策略,选中不同的设备,得到的音量值也不一样。此外该方法受静音模式影响,如果手机处于静音状态,无论音量值为多少,得到的index均为0。如果需要不想受静音状态影响,建议使用getLastAudibleStreamVolume。

4. setStreamVolume音量设置会导致媒体音退出静音状态

问题描述:

  • MIUI允许用户在静音开启时静止媒体音。但静止状态时,一旦触发音量增操作,媒体音会立刻退出静音状态。有许多视频播放或直播app,会在退出app时保存当前音量值,重开app时将音量值restore。这个操作经常会错误的将用户的静音状态解除。
  • app会catch按键时间,调用setStreamVolume来控制媒体音量。某些情况会导致音量调节异常。

建议:

  • 当应用返回时,通过isStreamMute或getRingerMode方法获取手机当前状态,如果处于媒体音或者铃声状态处于静音,则不执行setStreamVolume操作。
  • 音量的控制尽可能的交由系统处理,少用setStreamVolume设置音量。

小米开发平台 禁止应用获取MAC地址说明

小米开发平台 禁止应用获取MAC地址说明

一、变更说明:

在Android 11及以上机型中,应用无法通过

NetworkInterface#getHardwareAddress

二、获取到设备mac地址

对于targetSdkVersion<30的App,将会返回一个占位符02:00:00:00:00:00;

对于targetSdkVersion>=30的App,将会直接返回null(与原生特性一致)。

三、应用适配

本功能自身无需适配,但需要开发者检查验证是否影响应用正常使用,并进行相应调整。

四、功能体验

若想体验该功能,机型和版本要求如下:

小米11-21.7.13以上的开发版

小米开发平台设备标识体系说明

小米开发平台设备标识体系说明

1.前言

《移动智能终端补充设备标识体系规范》由小米起草,并与中国信息通信研究院共同研究制定。此标准旨在规范移动智能终端补充设备标识体系的体系架构、功能要求、接口要求以及安全要求,使设备生产企业统一开发接口,为移动应用开发者提供统一调用方式,方便移动应用接入、减小维护成本。移动智能终端补充设备标识体系架构共涉及四类实体,包括开发者、开发者开发的应用软件、移动智能终端设备的操作系统、用户及用户使用的设备。为保护用户用户的隐私和标识设备的唯一性,根据不同使用对象和不同用途,基于移动智能终端设备,分别生成设备唯一标识符、匿名设备标识符、开发者匿名设备标识符和应用匿名设备标识符,将这四个设备标识符构成补充设备标识体系。

2.获取移动智能终端补充设备标识SDK

2.1获取移动安全联盟MSA提供的统一SDK,适用于含小米在内的多家手机厂商

获取地址:http://msalliance.icoc.bz/col.jsp?id=120

2.2获取小米提供的SDK,仅适用于小米手机

IdentifierManager#isSupported  判断是否支持获取设备标识

IdentifierManager#getUDID 获取设备唯一标识符

IdentifierManager#getOAID 获取匿名设备标识符

IdentifierManager#getVAID 获取开发者匿名设备标识符

IdentifierManager#getAAID 获取应用匿名设备标识符

小米大屏设备适配说明4

小米大屏设备适配说明4

5.4.1 如何支持分屏

支持分屏的方式非常简单,只需要声明一个属性。从 7.0 开始,Android 新增了一个 Activity 属性: resizeableActivity ,以声明该 Activity 是否支持多窗口显示。

android:resizeableActivity=["true" | "false"]

如果这个属性设为 true ,Activity 就可以在分屏模式下显示;设为 false ,Activity 则不会在分屏模式下显示,而是会占满整个屏幕。因此开发者可以根据具体场景,仅让部分 Activity 支持分屏。

若开发者没有为 Activity 声明该属性,Android 会根据应用的 targetSDKVersion 及 Activity 的 screenOrientation 属性来综合判断是否可以在分屏显示。关于判断的详细逻辑,可以参考官方文档:Configuring Your App for Multi-Window Mode

5.4.2 如何进一步优化分屏模式

配置 resizeableActivity 的属性,是适配分屏的最简单方式,但如果想要提供更好的使用体验,需要开发者做一定优化工作。下面是我们了解到的一些案例(测试机型:Nexus 6 Andriod 7.1),开发者可以根据自己的业务需求,做不同程度的优化。

5.4.3 减少不可滑动的页面/控件

在分屏过程中,屏幕高度只有原来的一半,如果有太多的控件不响应滑动事件,那么用户将无法上下滚动应用页面,甚至无法进行下一步操作。这类页面,最常见于Splash screen、登录注册页、音乐播放页、大图区域、弹窗等。

由于用户可以自由调整分屏的窗口比例,因此开发者只要减少了不可滑动的控件,分屏的可用性就会大大提高,是性价比非常高的优化方案。

5.4.4 尽可能使用相对位置,以兼容多种窗口尺寸

分屏时,屏幕的高度和宽度会发生变化,因此在书写控件布局时,尽量使用相对位置,以避免窗口大小改变时,控件无法显示或显示不全。这也是一种性价比很高的优化方案,可以保证用户在分屏时能正常使用应用。

5.4.5注意多窗口下 Activity 的生命周期

视频、直播等类别的应用需要特别关注这一点。Android 7.0 在分屏时会同时运行两个应用,其中用户最后操作的那个应用会处于 Resumed 状态,另一个则会处于 Paused 状态。

这会带来一些问题,以视频应用为例,如果开发者在 onPaused 中处理视频的 「暂停/播放」,那在分屏时,就会因为用户操作另一个应用,导致视频停止播放。因此我们建议开发者在 onStart/onStop 中处理视频的「暂停/播放」,或者特殊处理分屏时的 Paused 状态。详见官方文档:Multi-Window Lifecycle

5.4.6处理 Configuration Changes

由于分屏过程中,允许用户调整窗口的大小,这就会导致 Configuration 的改变。Android 的默认处理方式是 relaunch 整个 Activity,从而出现页面闪一下的问题。如果想避免闪一下的问题,建议开发者自己处理 Configuration 的变化。

5.4.7给内容更多空间

分屏后,屏幕空间变得非常小了,为了给内容让出更多空间,应尽量减少常驻控件。一种解决办法是在浏览内容时,隐藏底部tab等常驻控件,用户回滚时再出现,以展示更多的内容。

5.4.8为分屏定制新的布局(动态布局)

动态布局指根据当前的窗口大小,重新调整页面的布局。这是一项锦上添花的优化项,开发者可以酌情考虑是否添加此优化。

5.4.9 MIUI 分屏支持哪些设备

搭载 Android 7.0 或以上的 MIUI 手机均支持分屏。MIUI 也将尝试将分屏移植至 Android 6.0。因此将有数千万的新老设备支持分屏。

5.4.10 分屏如何调试

MIUI 的分屏方案完全兼容 Android 7.0,因此可以在任意运行 Android 7.x 的设备上调试,无需为 MIUI 作特别处理。同时,以上提到的案例均能在任意 Android 7.x 设备复现。调试过程中,建议开启以下设置项:「开发者选项 > 强制将活动设为可调整大小」,然后重启手机,之后系统就会强制应用进入分屏模式,以方便开发者观察应用在分屏时的表现。

6、开发与测试方式

6.1 模拟器调试

1)小米折叠屏可以在Android Studio里面选择模拟折叠设备进行参照:

2)平板设备可以在Android Studio里面选择模拟平板设备进行参照:

6.2 命令模拟调试

1)可以通过如下adb命令来模拟小米MIX FOLD 2折叠屏手机切换折叠和展开的状态:

//切换到展开状态

adb shell wm size 1914×2160

//切换到折叠状态

adb shell wm size 1080×2520

2)可以通过如下adb命令来切换到平板设备的分辨率,例如:

//切换到2560×1600分辨率

adb shell wm size 1600×2560

6.3 云测平台真机测试

可以使用 小米云测平台

以折叠屏为例,点击小米 MIX Fold 2机器,进入以下页面:

右侧可以上传apk文件,左侧真机可以实时操作

6.4 测试用例建议

6.4.1 用例1

在折叠屏的展开态下打开应用,应用的界面显示正常

1.测试步骤

(1)在折叠屏展开时,打开应用,观察各个页面的显示情况;

2.预期结果

(1)应用的所有页面均显示正常,没有任何变形、截断、模糊、缺失等问题;

6.4.2 用例2

在折叠屏的折叠态下打开应用,应用的界面显示正常

1.测试步骤

(1)在折叠屏折叠时,打开应用,观察各个页面的显示情况;

2.预期结果

(1)应用的所有页面均显示正常,没有任何变形、截断、模糊、缺失等问题;

6.4.3 用例3

在折叠屏的展开态下打开应用,应用的界面功能交互正常

1.测试步骤

(1)在折叠屏展开时,打开应用,依次点击各个页面的按钮;

2.预期结果

(1)应用的所有页面按钮均可以正常响应,没有任何无反应/crash/anr等问题;

6.4.4 用例4

在折叠屏的展开态和折叠态之间切换时,应用的界面显示和功能交互均正常

1.预制条件:应用已经适配了连续性

2.测试步骤

(1)在折叠屏展开时,打开应用;

(2)切换到折叠态,观察页面显示是否正常;

(3)点击页面的各个按钮,观察功能交互是否正常;

(4)在折叠态下打开应用;

(5)切换到展开态,观察页面显示是否正常;

(6)点击页面的各个按钮,观察功能交互是否正常。

3.预期结果

(1)应用在状态切换过程中,没有任何页面闪退重启、变形、截断、模糊、缺失等问题;

(2)应用界面的所有按钮功能均交互正常。

6.4.5 用例5

在分屏模式下,应用的界面显示和功能交互均正常

1.预制条件:应用支持分屏

2.测试步骤

(1)打开应用,进入分屏模式;

(2)观察页面显示是否正常;

(3)点击页面的各个按钮,观察功能交互是否正常。

3.预期结果

(1)应用在分屏模式下,没有任何页面变形、截断、模糊、缺失等问题;

(2)应用界面的所有按钮功能均交互正常。

6.4.6 用例6

进入应用的小窗模式,应用的界面显示和功能交互均正常

1.预制条件:应用支持小窗

2.测试步骤

(1)进入应用的小窗模式;

(2)观察页面显示是否正常;

(3)依次点击各个页面的按钮,观察功能交互是否正常。

3.预期结果

(1)应用在小窗模式下,没有任何页面变形、截断、模糊、缺失等问题;

(2)应用的所有页面按钮均可以正常响应,没有任何无反应/crash/anr等问题;

小米大屏设备适配说明3

小米大屏设备适配说明3

3.5 H5页面适配

我们推荐项目使用响应式设计,实现同一页面自动适配多种屏幕尺寸的效果。

3.5.1 元标签设置

通常情况下,开发者应使用元标签将视口大小调整为屏幕宽度,以确保元素尺寸按照预期显示。

<meta name="viewport" content="width=device-width,initial-scale=1">

开发者可以提供额外的参数来限制用户的缩放行为,一般来说这样做可以优化普通用户体验,减少误操作,但会影响视力障碍用户进行缩放操作,造成访问性问题。

<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">

3.5.2 使用相对 CSS 长度单位

我们推荐开发者在响应式开发中尽量使用相对长度单位,包括:em、rem、%、vw、vh。以下是这些单位的一般使用场景。

em尺寸与当前字体大小相关
rem尺寸相对固定,基于断点与屏幕宽度成阶梯性相关
%尺寸与父元素相关
vw区别于 rem,尺寸与屏幕宽度成线性相关
vh尺寸与屏幕高度成线形相关

上述的只是一般情况,实际开发中使用哪个单位还需视设计要求而定,合理使用长度单位能起到事半功倍的效果。

3.5.3 布局方式

在响应式开发中,开发者应优先使用以下方式进行布局,构建适应性更强的页面。

A.Flexbox(弹性布局)

使用display: flex 可令容器元素启用弹性布局。即使在不确定容器内元素尺寸的情况下,开发者也可以灵活控制元素的大小比例和对齐方式。

参考资料

B.Grid(栅格布局)

Flexbox 可以很好地解决同一行/列内元素的布局问题,Grid 则更适合用于拥有多行/列的二维布局。使用display: grid 可令容器元素启用栅格布局。开发者可灵活控制行列间距、大小比例、对齐方式。

参考资料

3.5.4 CSS 媒体查询

使用 CSS 媒体查询技术,可以使同一元素的样式在不同设备下有不同的表现。通常,我们使用设备屏幕宽度作为判断设备类型的依据,以下是我们推荐的断点划分规则。

屏幕宽度范围设备类型
320 px < width ≤ 480px手机竖屏、折叠屏小屏竖屏
480 px < width ≤ 680px折叠屏展开竖屏
680 px < width ≤ 960px手机横屏、折叠展开横屏、折叠屏小屏横屏
1200px < width桌面端设备

一般情况下,建议开发者使用媒体查询统一控制根字体大小,简化适配流程。

html {  
  font-size: 16px;
}
@media screen and (min-width: 480px) {
  html {
    font-size: 17px;
  }
}
@media screen and (min-width: 680px) {
  html {
    font-size: 18px;
  }
}

3.5.5 Javascript 媒体查询

在一些特殊情况下,页面需要根据断点进行更加复杂的调整。如页面文案,交互逻辑等需要随断点而变化。此时需要使用 Javascript 进行更加细微的调整,我们推荐开发者使用matchMedia 方法进行断点识别,断点划分方案建议与 CSS 方案保持一致。

参考资料

3.5.6 其他建议

1)使用响应式图片,用于图片进行尺寸匹配的场景。

img {
  /* 按比例拉伸或缩放至父元素宽度 */
  width: 100%  }
img {
 /* 按比例缩放至父元素宽度,不拉伸 */
  max-width: 100%  
}
img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  /* 按比例拉伸或缩放至父元素宽高 */
  object-fit: cover;
  object-position: center;
}

2)为块级元素添加 max-width 而非 width,避免在更小尺寸的显示设备上宽度溢出。

4、平行窗口适配指南

推荐采用Android 12L方案进行适配,您可自主定义activity在左右分屏间的跳转方式。

详细参考Android适配文档SampleActivity Embedding 适配指南

平行窗口适配情况标记

应用主动适配12L平行窗口,需要在AndroidMainfest.xml文件设置标记。

<!-- support 12L Activity Embedding for large screen -->
        <meta-data
            android:name="embedded"
            android:value="true" />

5、进阶开发适配指南(小窗、迷你窗及分屏)

5.1 小窗迷你窗功能介绍

5.1.1 小窗

在多任务处理场景下,小窗意在解决临时使用某个应用的场景,例如:在使用游戏应用时,不便离开,但想发消息给朋友,此时就可以借助小窗打开第二应用

5.1.2 迷你窗

迷你小窗意在解决应用临时挂机的场景,例如:等待网约车、等待游戏更新、观看直播等

5.2 小窗迷你窗适配指南

小窗适配技术文档

MIUI的小窗是基于Android的多窗口Freeform方案实现的。

小窗目前主要问题是应用兼容性导致的一系列问题,内容显示不全、Touch事件不响应等等,其实根本原因是应用没有很好的支持、适配多窗口、多分辨率,如下是小窗适配的一些参考性适配指南。

多窗口适配支持文档

支持不同屏幕分辨率的开发技巧文档

5.2.1 常用适配方法

(1) 如何支持小窗

支持小窗的方式非常简单,只需要声明一个属性,直接在AndroidManifest.xml中 配置属性android:resizeableActivity=”true” ,不支持小窗直接配置为false,系统默认是支持小窗的。

(2)判断自己是否在小窗  
Activity.onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration  newConfig)  
Activity.isInMultiWindowMode()   判断是否处于多窗口                                                    
Activity.getWindowingMode()  当返回5时表示小窗,需要反射调用
String config = context.getResources().getConfiguration().toString();
boolean isInFreeformWindow = config.contains("freeform");
(3)获取小窗的位置/大小等信息

context.getResources().getConfiguration().windowConfiguration.getBounds(),其中Configuration对象里也有小窗的模式以及bounds,所以我们也需要重写ConfigurationChanged方法,并对界面布局做相应调整,如切换布局、调整控件位置和间距等。

(4)Activity大小切换时不重启适配

强烈建议应用在Activity窗口大小切换时不重启适配,遵循google规范,在android:configChanges 属性增加 screenSize|screenLayout|orientation|smallestScreenSize,并在Activity的onConfigurationChanged回调中更新宽高刷新各个子布局。

(5)特别需要注意的适配点,焦点窗口适配

迷你小窗和小窗都有可能成为不是焦点窗口(Focus Window),其中迷你小窗一定是没有焦点的,小窗和小窗下面的全屏应用是可以随意进行焦点切换的,touch在哪个窗口,哪个窗口就是焦点窗口,所以就要保证应用在没有焦点的前提下也是可以正常运行的,特别是视频类app。关键方法:Activity.onWindowFocusChanged(boolean hasFocus)

(6)获取屏幕宽高有些APP会跟随屏幕大小动态计算View的大小,但是有些接口在多窗口形式下面并不试用,要注意以下几点:
  • 在获取Display 对象 或者获取 Resources对象时使用Activity的Context 不要使用Application的Context;
  • 获取Resources对象时不要用context.getResources().getSystem()用context.getResources()就行;
  • Display下有getMetrics getRealMetrics getSize getRealSize四个方法, getRealMetrics和 getRealSize 永远会返回屏幕的宽高,无论应用是否在多屏下, 而getMetrics和getSize会可以获取Activity的大小。
(7)获取View的位置

获取View的位置有两个方法:

  • View.getLocationInWindow(int[] outLocation) 返回距离View所在的Window左上角的距离;
  • View.getLocationOnScreen 返回距离屏幕左上角的距离(由于在小窗模式下应用的位置可以移动, 所以该返回值有时并非是你想要的,所以要慎用)。

5.2.2 其他说明

全局自由窗口功能在MIUI12安卓Q版本以上支持。

如果应用想要启用小窗或者分屏模式下的拖拽功能,请参考如下google资料进行适配:

Android Developer上的拖放文档

AOSP里的DragAndDrop样例应用

github Demo下载连接

5.3 分屏功能介绍

分屏场景意在解决长时间并行使用两个应用时的场景,例如:一边听网课,一边记笔记等;一边看视频,一边逛知乎、微博等

5.4 分屏适配技术文档

https://dev.mi.com/console/doc/detail?pId=1161