### Android 11改动大全

> **背景: 码牛学院只为更好的你，Android11训练营预习资料，让你彻底理解Android11新特性**

#### **1.1分区存储**

   还记得在适配安卓10的时候设置requestLegacyExternalStorage为true来修改外部存储空间视图模型（true为 Legacy View，false 为 Filtered View）吗？这是重点：**当您将应用更新为以 Android 11 为目标平台后，将无法使用 requestLegacyExternalStorage 来停用分区存储**。

"大哥，这都停用了那我该怎么适配呢？"

“这孩子，猴急猴急的，这不就要说了嘛！”

##### **1.1.2 数据迁移到使用分区存储时可见的目录：**

在以 Android 11 为目标平台之前，应将数据迁移到与分区存储兼容的目录。在大多数情况下，可以将数据迁移到应用的**应用专用目录**（https://developer.android.google.cn/training/data-storage/app-specific）。

如果有要迁移的数据，当用户升级到以 Android 11 为目标平台的新版应用时，可以保留旧版存储模型。这样，用户就可以保留对应用之前用来保存数据的目录中存储的应用数据的访问权限。要启用旧版存储模型以进行升级，请在应用的清单中将 **preserveLegacyExternalStorage** 属性设为 true。

这里需要注意以下两点：

- 大多数应用都不需要使用 **preserveLegacyExternalStorage**。此标记仅适用于这样一种情况：你将应用数据迁移到了与分区存储兼容的位置，并且希望用户在更新你应用时保留对数据的访问权限。使用此标记会导致更难以测试分区存储对应用的用户有何影响，因为当用户更新应用时，它会继续使用旧版存储模型。

- 如果使用 **preserveLegacyExternalStorage**，则旧版存储模型只在用户卸载应用之前保持有效。如果用户在搭载 Android 11 的设备上安装或重新安装应用，则无论 preserveLegacyExternalStorage 的值是什么，应用都无法停用分区存储模型。

 

#### **1.2 管理设备存储空间**

如果你的应用是文件管理器应用并且在 Android 11 上运行，它就不能再删除其他应用的缓存文件，即使您的应用具有所有文件访问权限也是如此（清理文件的应用尤其需要注意，比如某某**管家、卫士**啥的）。相反，你应执行以下操作：

1. 通过调用 **ACTION_MANAGE_STORAGE** intent 操作来检查可用空间。
2. 如果设备上的可用空间不足，请提示用户同意让您的应用清除所有缓存。为此，请调用 **ACTION_CLEAR_APP_CACHE** intent 操作。

这里需要注意：**ACTION_CLEAR_APP_CACHE** intent 操作会**严重影响设备的电池续航****时间**，并且可能会从设备上**移除大量的文件**。

#### **1.3媒体文件访问权限**

这个很赞，Android 11 增加了以下访问媒体功能。

##### **1.3.1 执行批量操作**

为实现各种设备之间的一致性并增加用户便利性，Android 11 向 MediaStore API 中添加了多种方法。对于希望简化特定媒体文件更改流程（例如在原位置编辑照片）的应用而言，这些方法尤为有用。

添加的方法如下（https://developer.android.google.cn/reference/android/provider/MediaStore）：

- [createWriteRequest()]

  (android.content.ContentResolver, java.util.Collection)

  用户向应用授予对指定媒体文件组的写入访问权限的请求。

- [createFavoriteRequest()]

  (android.content.ContentResolver, java.util.Collection, boolean)

  用户将设备上指定的媒体文件标记为“收藏”的请求。对该文件具有读取访问权限的任何应用都可以看到用户已将该文件标记为“收藏”。

- [createTrashRequest()]

  (android.content.ContentResolver, java.util.Collection, boolean)

  用户将指定的媒体文件放入设备垃圾箱的请求。垃圾箱中的内容在特定时间段（默认为 7 天）后会永久删除。

- [createDeleteRequest()]

  (android.content.ContentResolver, java.util.Collection)

  用户立即永久删除指定的媒体文件（而不是先将其放入垃圾箱）的请求。

系统在调用以上任何一个方法后，会构建一个 PendingIntent 对象。应用调用此 intent 后，用户会看到一个对话框，请求用户同意应用更新或删除指定的媒体文件。

#### **使用原始路径访问文件**

从 Android 11 开始，具有 **READ_EXTERNAL_STORAGE** 权限的应用可以使用直接文件路径和原生库来读取设备的媒体文件。通过这项新功能，应用可以更顺畅地使用第三方媒体库。

##### **文件和目录访问限制**

以下与存储访问框架 (SAF) 相关的变更只有在应用以 Android 11 为目标平台时才会生效。

#### **访问目录**

无法再使用 ACTION_OPEN_DOCUMENT_TREE intent 操作来请求访问以下目录：

- Downloads 根目录。
- 设备制造商认为可靠的各个 SD 卡卷的根目录，无论该卡是模拟卡还是可移除的卡。

#### **访问文件**

无法再使用 ACTION_OPEN_DOCUMENT_TREE 或 ACTION_OPEN_DOCUMENT intent 操作来请求用户从以下目录中选择单独的文件：

- Android/data/ 目录及其所有子目录。
- Android/obb/ 目录及其所有子目录。

### **权限**

不管应用的目标 SDK 版本是什么，以下变更均会在 Android 11 中生效：

- **存储**运行时权限已重命名为**文件和媒体**。
- 如果应用未停用分区存储并且请求 READ_EXTERNAL_STORAGE 权限，则用户会看到不同于 Android 10 的对话框。该对话框会指示应用正在请求访问照片、视频、音频剪辑和文件。
- 用户可以在系统设置中查看哪些应用具有 READ_EXTERNAL_STORAGE 权限。在**设置 > 隐私 > 权限管理器 > 文件和媒体页面**上，具有该权限的每个应用都列在**允许存储所有文件下**。

这里要注意：如果应用以 Android 11 为目标平台，切记，对“所有文件”的这种访问权限是只读访问权限。要使用此应用读取和写入共享的存储空间中的所有文件，需要具有所有**文件访问权限**（https://developer.android.google.cn/preview/privacy/storage#all-files-access）。

### **所有文件访问权限**

某些应用的核心用例需要访问大量的文件，如文件管理操作或备份和恢复操作。这些应用可通过执行以下操作来获取“所有文件访问权限”：

1. 声明 **MANAGE_EXTERNAL_STORAGE** 权限。
2. 使用 **A****CTION_MANAGE_ALL_FILES_ACCESS_PERMISSION** intent 操作将用户引导至一个系统设置页面，在该页面上，用户可以为应用启用以下选项：**授予所有文件的管理权限**。

“所有文件访问权限”可授予以下权限：

- 对**共享的存储空间**内所有文件的读写访问权限。
- 对 **MediaStore.Files** 表的内容的访问权限。

应用可以使用 **MediaStore API** 或 **原始文件路径**(https://developer.android.google.cn/preview/privacy/storage#media-files-raw-paths)来访问这些文件。

如果应用使用**存储访问框架**，不能使用它来访问“所有文件访问权限”提供的其他文件和目录。获得此权限的应用仍然无法访问属于其他应用的**应用专用目录**。这些目录在存储卷上显示为 **Android/data/ 的子目录**。

#### **1.4 自定义消息框视图被屏蔽**

出于安全方面的考虑，同时也为了保持良好的用户体验，如果包含自定义视图的消息框是以 Android 11 为目标平台的应用从后台发送的，则系统会屏蔽这些消息框。请注意，仍允许使用文本消息框；此类消息框是使用 **Toast.makeText()** 创建的，并不调用 **setView()**。

如果您的应用仍尝试从后台发布包含自定义视图的消息框，则系统不会向用户显示相应的消息，而会执行以下操作：

\1. 显示以下消息框消息：

Background custom toast blocked for package package-name. See g.co/dev/toast.

\2. 在 logcat 中记录以下消息：

W/NotificationService: Blocking custom toast from package due to package not in the foreground

如果希望在消息框（文本消息框或自定义消息框）出现或消失时收到通知，请使用新的 **addCallback()** 方法。

这里需要注意，由于平台行为发生了变更，以 Android 11 为目标平台的应用会发现文本消息框受到以下负面影响：

- getView() 方法返回 null。

- 以下方法的返回值并不反映实际值，因此尽量别使用：

- - getHorizontalMargin()
  - getVerticalMargin()
  - getGravity()
  - getXOffset()
  - getYOffset()

- 以下方法是空操作，因此应用不应使用：

- - [setMargin()](float, float)
  - [setGravity()](int, int, int)

#### **1.5前台服务类型**

从 Android 9 开始，应用仅限于在前台访问摄像头和麦克风。为了进一步保护用户，Android 11 更改了前台服务访问摄像头和麦克风相关数据的方式。

如果应用以 Android 11 为目标平台并且在某项前台服务中访问这些类型的数据，则需要在该前台服务的声明的 foregroundServiceType 属性中添加新的 camera 和 microphone 类型。

来举个例子吧，如果应用中的某项前台服务需要访问与设备的位置信息和摄像头相关的数据，请按以下代码段所示声明该服务：

```php
<manifest>    <service ... android:foregroundServiceType="location|camera" /></manifest> 
```

#### **1.6 MAC 地址**

在以 Android 10（API 级别 29）及更低版本为目标平台的应用中，MAC 地址的随机分配是基于每个 SSID 进行的，因为 **Passpoint**(https://www.wi-fi.org/discover-wi-fi/passpoint) 可以连接到同一资料的不同 SSID。

而在以 Android 11（API 级别“R”）及更高版本为目标平台的应用中，Passpoint 网络的 MAC 地址随机分配将更改为针对每个完全限定域名 (FQDN) 进行分配。

对于以 API 级别“R”及更高级别为目标平台的应用，非特权应用将无法访问设备的 MAC 地址；只有具有 IPv4 地址的网络接口可见。会影响 **getifaddrs()** 和 **NetworkInterface.getHardwareAddress()** 方法，以及 **RTM_GETLINK** netlink 消息的发送。

下面列出了此变更会以哪些方式来影响应用：

- NetworkInterface.getHardwareAddress() 会针对每个接口返回 null。
- 应用无法对 NETLINK_ROUTE 套接字使用 bind() 函数。
- IP 命令不会返回有关接口的信息。

这些变更强制执行**不要使用 MAC 地址**(https://developer.android.google.cn/training/articles/user-data-ids#mac-addresses)中提供的指导。

请注意，大多数开发者应使用级别较高的 **ConnectivityManager** API 而不是级别较低的 NetworkInterface/getifaddrs() API。

#### **1.7 每个进程的网络访问控制**

从 Android 11 开始，处理敏感用户数据的应用可以向每个进程授予网络访问权限。通过明确指定允许哪些进程访问网络，您可以隔离不需要上传数据的所有代码。

虽然不能保证防止应用意外上传数据，但该功能可让降低应用中的错误导致数据泄露的几率。

下面显示了使用这项新功能的清单文件的示例：

```php
    <processes>        <process />        <deny-permission android:name="android.permission.INTERNET" />        <process android:process=":withoutnet1" />        <process android:process="com.android.cts.useprocess.withnet1">            <allow-permission android:name="android.permission.INTERNET" />        </process>        <allow-permission android:name="android.permission.INTERNET" />        <process android:process=":withoutnet2">            <deny-permission android:name="android.permission.INTERNET" />        </process>        <process android:process="com.android.cts.useprocess.withnet2" />    </processes> 
```

#### **1.8 一次性权限**

在 Android 11 中，每当应用请求与位置信息、麦克风或摄像头相关的权限时，应用都会获得临时的一次性权限。这个其实在苹果中也有类似的权限申请。

Android 11 不建议重复请求特定权限组中的权限。在您的应用安装到设备上后，如果用户在使用过程中两次针对某项特定的权限点按拒绝，此操作表示其希望以后请求相应权限组中的该权限时“不再询问”。

系统还对与点按拒绝选项相仿的操作的响应行为做出了定义：

- 如果用户按返回按钮关闭权限对话框，此操作不算“拒绝”操作。
- 如果用户使用 [requestPermissions()](android.app.Activity, java.lang.String[], int)从您的应用转到系统设置，然后按返回按钮，此操作就算是“拒绝”操作。

#### **1.9 悬浮窗口 intent 始终会将用户转至系统权限屏幕**

从 Android 11 开始，**ACTION_MANAGE_OVERLAY_PERMISSION** intent 始终会将用户转至顶级设置屏幕，用户可在其中授予或撤消应用的 **SYSTEM_ALERT_WINDOW** 权限。intent 中的任何 package: 数据都会被忽略。

在更低版本的 Android 中，**ACTION_MANAGE_OVERLAY_PERMISSION** intent 可以指定一个文件包，它会将用户转至应用专用屏幕来管理权限。

Android 11 不再支持此功能，而是必须由用户先选择要对其授予或撤消权限的应用。此变更可以让权限的授予更有目的性，从而达到保护用户的目的。

#### **1.10限制非 SDK 接口**

如果应用并非以 Android 11 为目标平台，那么其中一些变更可能不会立即产生影响。虽然目前仍然可以使用灰名单中的一些非 SDK 接口（取决于应用的**目标 API 级别**），但如果使用任何非 SDK 方法或字段，则应用无法运行的风险终归较高。

如果不确定自己的应用是否使用了非 SDK 接口，则可以**测试该应用**(https://developer.android.google.cn/distribute/best-practices/develop/restrictions-non-sdk-interfaces#test-for-non-sdk)，进行确认。

如果应用依赖于非 SDK 接口，则应该开始计划迁移到 SDK 替代方案。如果无法为应用中的某项功能找到使用非  

#### **1.11支持并发使用多个摄像头**

Android 11 添加了 API 来查询对同时使用多个摄像头（包括前置摄像头和后置摄像头）的支持。

要在运行应用的设备上检查支持情况，请使用以下方法：

- getConcurrentStreamingCameraIds() 可返回摄像头 ID 组合 Set，这些组合可与有保证的数据流组合并发进行流式传输（如果它们是由同一应用进程配置的）。
- isConcurrentSessionConfigurationSupported() 可查询摄像头设备是否可以并发支持相应的会话配置。