Android 11新特性汇总

本文内容来自公司项目适配Android-11的调研,内容未加整理。

以 Android 11 为目标平台的应用

只有指定了targetVersion=30时需要适配一下内容.

1.存储与访问机制变更

Android 中存储可以分为两大类:私有存储和共享存储

  • 私有存储 (Private Storage) : 每个应用在都拥有自己的私有目录,其它应用看不到,彼此也无法访问到该目录:
    • 内部存储私有目录 (/data/data/packageName)
    • 外部存储私有目录 (/sdcard/Android/data/packageName)
  • 共享存储 (Shared Storage) : 存储其他应用可访问文件, 包含媒体文件、文档文件以及其他文件,对应设备DCIM、Pictures、Alarms、Music、Notifications、Podcasts、Ringtones、Movies、Download等目录。

(1) 分区存储强制执行

安卓10中设置requestLegacyExternalStorage为true来修改外部存储空间视图模型(true为 Legacy View,false 为 Filtered View)但是当您将应用更新为以 Android 11 为目标平台后,将无法使用 requestLegacyExternalStorage 来停用分区存储。

分区存储是指应用对于文件的读写只能在沙盒环境中进行,对于读取媒体文件可以通过MediaStore进行访问。分区存储在Android10中已经开始推行。但在Android10中并没有强制分区存储,在targetSdkVersion = 29的情况下可以通过 :

1
android:requestLegacyExternalStorage="true"

设置不强制启动分区存储。从Android11开始,分区存储变为强制执行,当设置了targetSdkVersion = 30,则会强制开启分区存储。但是为了安全,Google提供了以下配置:

1
android:preserveLegacyExternalStorage="true"

在覆盖安装的时候可以暂时关闭分区存储。

1)访问专属目录

1
2
3
4
5
6
7

//分区存储空间
val file = File(context.filesDir, filename)

//应用专属外部存储空间
val appSpecificExternalDir = File(context.getExternalFilesDir(), filename)

2)访问公共媒体目录

1
2
3
4
5
6
7
8
9
10
11

val cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, "${MediaStore.MediaColumns.DATE_ADDED} desc")
if (cursor != null) {
while (cursor.moveToNext()) {
val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
val uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
println("image uri is $uri")
}
cursor.close()
}

Environment.getExternalStoragePublicDirectory(“”)方法在Android11中已被弃用,使用该方法会导致程序报错。

(2) SAF访问框架(Storage Access Framework)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "image/*"
startActivityForResult(intent, 100)

@RequiresApi(Build.VERSION_CODES.KITKAT)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (data == null || resultCode != Activity.RESULT_OK) return
if (requestCode == 100) {
val uri = data.data
println("image uri is $uri")
}
}

同时,Android11中还提供了两个Intent入口:

  • 调用ACTION_MANAGE_STORAGE intent 操作检查可用空间。

  • 调用ACTION_CLEAR_APP_CACHE intent 操作清除所有缓存。

对于像手机管理器的APP可以通过这两个Intent管理APP的缓存。

(3) 所有文件访问权限

针对文件管理器以及一些备份类的应用,它们需要获得共享存储的更广泛的访问权限。Android 11 里将会引入一个特别的权限叫做 MANAGE_EXTERNAL_STORAGE,该权限将授权读写所有共享存储内容,这也将同时包含非媒体类型的文件。但是获得这个权限的应用还是无法访问其他应用的应用专属目录 (app-specific directory),无论是外部存储还是内部存储。

使用MANAGE_EXTERNAL_STORAGE权限:

1
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />     
1
2
3
4
5
6
7
8
9

val intent = Intent()

intent.action= Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION

startActivity(intent) //判断是否获取MANAGE_EXTERNAL_STORAGE权限:

val isHasStoragePermission= Environment.isExternalStorageManager()

(4) 媒体文件访问权限

Android 10 中要求所有应用都使用 MediaStore API 来访问照片、视频和音乐文件,我们也将继续秉承这个原则。但是我们也知道,很多深度依赖基于原始文件路径 API 的应用和第三方库是很难切换到使用文件描述符 (File Descriptor) 的。因此在 Android 11 里,依赖原始文件路径的 API 和库可以再次使用了。您需要在应用的 Manifest 文件里添加 requestLegacyExternalStorage 属性,以保证 Android 10 的用户也可以使用该特性。

在实际的运行中,依赖原始文件路径的 I/O 请求会被重定向到使用 MediaStore API,当使用这种方式访问本应用存储空间之外的文件时,这次重定向会造成性能影响。而且直接使用原始文件路径,并不会比使用 MediaStore API 有更多优势,因此我们强烈建议直接使用 MediaStore API。

在 Android 10 中,应用在对每一个文件请求编辑或删除时都必须得到用户的确认。而在 Android 11 中,应用可以一次请求修改或者删除多个媒体文件。系统的默认图库应用 (Gallery) 将不再展示这些对话框。我们希望这项改进能够使用户体验更加顺畅。

1) 执行批量操作

Android 11 向 MediaStore API 中添加了多种方法,用于简化特定媒体文件更改流程(例如在原位置编辑照片),分别是:

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

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

  • createTrashRequest()用户将指定的媒体文件放入设备垃圾箱的请求。垃圾箱中的内容会在系统定义的时间段后被永久删除。

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

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
val urisToModify = listOf(uri,uri,...)
val editPendingIntent = MediaStore.createWriteRequest(contentResolver,
urisToModify)

// Launch a system prompt requesting user permission for the operation.
startIntentSenderForResult(editPendingIntent.intentSender, EDIT_REQUEST_CODE,
null, 0, 0, 0)


override fun onActivityResult(requestCode: Int, resultCode: Int,
data: Intent?) {
when (requestCode) {
EDIT_REQUEST_CODE ->
if (resultCode == Activity.RESULT_OK) {
/* Edit request granted; proceed. */
} else {
/* Edit request not granted; explain to the user. */
}
}
}

2)直接文件路径和原生库访问文件

Android11又恢复了使用直接文件路径访问访问媒体文件!也就是除了 MediaStore API之外还有两种方式可以访问媒体文件:

  • File API。
  • 原生库,例如 fopen()。

(5) 文档访问权限

在分区存储权限中说过可以使用SAF来访问公共目录,但Android11中部分目录不能访问,如下:

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

  • 内部存储卷的根目录。
  • 设备制造商认为可靠的各个 SD 卡卷的根目录,无论该卡是模拟卡还是可移除的卡。可靠的卷是指应用在大多数情况下可以成功访问的卷。
  • Download 目录。

无法再使用 ACTION_OPEN_DOCUMENT_TREEACTION_OPEN_DOCUMENT intent 操作请求用户从以下目录中选择单独的文件:

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

2.位置权限更新

(1)单次访问权限

在 Android 11 及更高版本中,每当应用请求在前台访问位置信息时,系统权限对话框都包含一个名为仅限这一次的选项。通过这一选项,用户可以更好地控制应用何时有权访问位置信息。

####(2) 在后台访问位置信息的权限

Android 11 更改了应用中的功能获取后台位置信息访问权限的方式。

(1)从Android10系统开始,需要申请后台位置权限(ACCESS_BACKGROUND_LOCATION),并且只有用户点了始终允许之后才能获得后台位置权限。Android11设备上对于后台位置权限再次收紧,表现在系统对话框上不在提示始终允许字样,而是提供了位置权限设置入口,需要用户到设置页面允许后才能获得后台位置权限。

(2)在targetVersion小于30时候,可以前台位置权限与后台位置权限一起申请,并且对话框提供了文字说明,表示需要随时获取用户位置信息。,进入设置选择始终允许即可。但targetVersion设置为30时,必须单独申请后台位置权限,而且必须在获取前台位置权限之后,并且无任何提示,需要开发者自行设置提示样式。

3.电话号码相关权限

Android11更改了读取电话号码时与电话相关的权限。如果您的应用以 Android 11 为目标平台,并且需要访问以下列表中显示的电话号码 API,则必须请求 READ_PHONE_NUMBERS权限,而不是 READ_PHONE_STATE 权限。

也就是当用到这两个API的时候,原来的READ_PHONE_STATE权限不管用了,需要READ_PHONE_NUMBERS权限才行。

需要做的改动:

  1. 更改 READ_PHONE_STATE 的声明,以使您的应用仅在 Android 10(API 级别 29)及更低版本中使用该权限。
  2. 添加 READ_PHONE_NUMBERS 权限。

清单文件配置如下:

1
2
3
4
5
6
7
8

<manifest>
<!-- Grants the READ_PHONE_STATE permission only on devices that run
Android 10 (API level 29) and lower. -->
<uses-permission android:name="READ_PHONE_STATE"
android:maxSdkVersion="29" />
<uses-permission android:name="READ_PHONE_NUMBERS" />
</manifest>

4.自动重置权限

如果用户几个月未与应用互动,系统会自动重置应用的敏感权限。

如果应用以 Android 11 为目标平台并且数月未使用,系统会通过自动重置用户已授予应用的运行时敏感权限来保护用户数据。此操作与用户在系统设置中查看权限并将应用的访问权限级别更改为拒绝的做法效果一样。如果应用已遵循有关在运行时请求权限的最佳做法,那么您不必对应用进行任何更改。这是因为,当用户与应用中的功能互动时,您应该会验证相关功能是否具有所需权限。

请求用户停用自动重置功能

如果需要,您可以要求用户阻止系统重置应用的权限。如果用户希望应用主要在后台运行,即使用户不与应用互动,应用也能正常工作,那么此做法就非常有用。

5.应用打包与安装

(1) 压缩的资源文件

如果以 Android 11(API 级别 30)或更高版本为目标平台的应用包含压缩的 resources.arsc 文件或者如果此文件未按 4 字节边界对齐,应用将无法安装。如果存在其中任意一种情况,系统将无法对此文件进行内存映射。无法进行内存映射的资源表必须读入 RAM 中的缓冲区,从而给系统造成不必要的内存压力,并大大增加设备的 RAM 使用量。

(2) 需要使用V2签名

对于以 Android 11(API 级别 30)为目标平台,且目前仅使用 APK 签名方案 v1 签名的应用,现在还必须使用 APK 签名方案 v2或更高版本进行签名。用户无法在搭载 Android 11 的设备上安装或更新仅通过 APK 签名方案 v1 签名的应用。

6.软件包可见性

当应用查询设备上已安装应用的列表时,系统会过滤返回的列表。

Android 11 更改了应用查询用户已在设备上安装的其他应用以及与之交互的方式。使用新的 元素,应用可以定义一组自身可访问的其他应用。通过告知系统应向您的应用显示哪些其他应用,此元素有助于鼓励最小权限原则。此外,此元素还可帮助 Google Play 等应用商店评估应用为用户提供的隐私权和安全性。

也就是说,Android11中,如果你想去获取其他应用的信息,比如包名,名称等等,不能直接获取了,必须在清单文件中添加<queries>元素,告知系统你要获取哪些应用信息或者哪一类应用。

1
2
3
4
5
6
7
8

val pm = this.packageManager
val listAppcations: List<ApplicationInfo> = pm
.getInstalledApplications(PackageManager.GET_META_DATA)
for (app in listAppcations) {
Log.e("lz",app.packageName)
}

上述代码在Android11上只能获取到自己APP的信息,查询不到其他应用信息。如果需要查询其他APP,需要添加元素,有两种方式:

(1)元素中加入具体包名

1
2
3
4
5
6
7
8
9

<manifest package="com.example.game">
<queries>
<package android:name="com.example.store" />
<package android:name="com.example.services" />
</queries>
...
</manifest>

(2)元素中加入固定过滤的intent

1
2
3
4
5
6
7
8
<manifest package="com.example.game">
<queries>
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="image/jpeg" />
</intent>
</queries>
</manifest>

同时,Google还添加了QUERY_ALL_PACKAGES的权限,需要在清单文件中加入该权限后可以获取到APP的所有应用列表。

7. 5G功能支持

(1) 针对 5G 的模拟器支持

Android 11 添加了 5G API,使您的应用能够添加各种先进的功能。如需在添加这些功能时对其进行测试,您可以使用 Android SDK 模拟器的新功能。这项新功能是在模拟器版本 30.0.22 中添加的。选择 5G 网络设置可将 TelephonyDisplayInfo 设为 OVERRIDE_NETWORK_TYPE_NR_NSA,修改带宽估算值,还允许您设置按流量计费性,以验证您的应用是否会对 NET_CAPABILITY_TEMPORARILY_NOT_METERED 状态的变化做出适当的响应。

(2) 5G功能

Android 11 引入了以下功能变更和增强功能

  • 按流量计费性
  • 5G检测
  • 带宽估算

检查按流量计费性

NET_CAPABILITY_TEMPORARILY_NOT_METERED 是 Android 11 中添加的一项功能,可根据移动网络运营商提供的信息,告知您正在使用的网络是否不按流量计费。

该新标记与 NET_CAPABILITY_NOT_METERED 一起使用。该现有标记指示网络是否始终不按流量计费,并且同时适用于 WLAN 和移动网络连接。

这两个标记之间的区别在于,在网络类型不变的情况下,NET_CAPABILITY_TEMPORARILY_NOT_METERED 可能会发生变化。以 Android 11 为目标平台的应用可以使用 NET_CAPABILITY_TEMPORARILY_NOT_METERED 标记。在搭载 Android 9 及更低版本的设备上,操作系统不会报告该标记。对于在 Android 10 上运行的应用,此标记可能可用,具体取决于运行应用的设备。

一旦确定当前网络暂时或永久不按流量计费,您便可以显示分辨率更高的内容(如 4k 视频)、上传日志、备份文件,以及主动下载内容。

使用在网络回调中收到的 NetworkCapabilites 对象来检查以下代码的输出:

1
2
3
4
5
6
7
8
9
10
val manager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
manager.registerDefaultNetworkCallback(object : ConnectivityManager.NetworkCallback() {
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
super.onCapabilitiesChanged(network, networkCapabilities)
//true 代表连接不按流量计费
val isNotFlowPay=networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) ||
networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)
}
})

如果值为 true,则可以将网络视为不按流量计费。

注册网络回调

使用 ConnectivityManager.registerDefaultNetworkCallback() 注册一个网络回调,以监听 NetworkCapabilities 何时发生更改。您可以通过替换 NetworkCallback 中的 [onCapabilitiesChanged()](https://developer.android.com/reference/android/net/ConnectivityManager.NetworkCallback#onCapabilitiesChanged(android.net.Network, android.net.NetworkCapabilities)) 方法来检测 NetworkCapabilities 的更改。

registerDefaultNetworkCallback() 会使注册的回调在注册后立即触发,从而为应用提供有关当前状态的信息。将来的回调对于应用在状态从不按流量计费更改为按流量计费或者从按流量计费更改为不按流量计费时采取适当的措施至关重要。

5G检测

从 Android 11 开始,可以使用基于回调的 API 调用来检测设备是否连接到了 5G 网络。可以检查连接的是 5G NR(独立)网络,还是 NSA(非独立)网络。

通过TelephonyManager的监听方法并传入 LISTEN_DISPLAY_INFO_CHANGED,以确定用户是否连接到了 5G 网络。替换 onDisplayInfoChanged() 方法,以确定应用连接到的网络类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

private fun getNetworkType(){
val tManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
tManager.listen(object : PhoneStateListener() {

@RequiresApi(Build.VERSION_CODES.R)
override fun onDisplayInfoChanged(telephonyDisplayInfo: TelephonyDisplayInfo) {
if (ActivityCompat.checkSelfPermission(this@Android11Test2Activity, android.Manifest.permission.READ_PHONE_STATE) != android.content.pm.PackageManager.PERMISSION_GRANTED) {
return
}
super.onDisplayInfoChanged(telephonyDisplayInfo)

when(telephonyDisplayInfo.networkType) {
TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO -> showToast("高级专业版 LTE (5Ge)")
TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA -> showToast("NR (5G) - 5G Sub-6 网络")
TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE -> showToast("5G+/5G UW - 5G mmWave 网络")
else -> showToast("other")
}
}

}, PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED)
}

返回类型 网络
OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO 高级专业版 LTE (5Ge)
OVERRIDE_NETWORK_TYPE_NR_NSA NR (5G) - 5G Sub-6 网络
OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE 5G+/5G UW - 5G mmWave 网络

带宽估测

带宽估测使用您在确定按流量计费性时使用的 NetworkCapabilities 对象。您可以使用该对象获取带宽估测值。

带宽估测方法 getLinkDownstreamBandwidthKbps()getLinkUpstreamBandwidthKbps() 的可靠性和准确性在 Android 11 中得到了改进,这是因为,为了适应 5G 而进行了框架支持的升级和平台/调制解调器问题修复。

带宽默认值仅提供关于应用启动的指导。这应该可以帮助您处理“空闲时启动”的情况。您的应用应衡量用户开始与其互动后的性能,并动态地调整其流式传输行为。例如,您可以根据启动时的带宽估测来选择要提供的视频分辨率。随着用户使用应用,继续检查估测值;随着其连接类型和强度的变化,相应地调整应用的行为。

8.媒体Intent打开相机

从Android11开始,只有预装的系统相机可以相应以下Intent事件:

  • android.media.action.VIDEO_CAPTURE
  • android.media.action.IMAGE_CAPTURE
  • android.media.action.IMAGE_CAPTURE_SECURE

也就是使用Intent将无法唤起第三方相机,如果需要打开第三方相机官方给的建议是可以通过为intent设置软件包名称或组件来使这些intent变得明确。

9.前台服务类型

从 Android 9 开始,应用仅限于在前台访问摄像头和麦克风。为了进一步保护用户,Android 11 更改了前台服务访问摄像头和麦克风相关数据的方式。如果您的应用以 Android 11 为目标平台并且在某项前台服务中访问这些类型的数据,您需要在该前台服务的声明的 foregroundServiceType 属性中添加新的 camera 和 microphone 类型。

在Android10的时候,对于前台定位服务就必须加上android:foregroundServiceType="location",现在Android11上又增加了两个权限限制,一个是摄像头一个是麦克风。

所以总结下来就是,应用某项前台服务需要访问位置信息、摄像头和麦克风,那么就要在清单文件中这样添加:

1
2
3
4
5

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

有的朋友可能测试发现,不加foregroundServiceType的前提下,让Activity启动了一个前台服务,并在服务里去获取定位,竟然可以获取到定位信息,难道官方说错了?

其实这是因为你并没有让前台服务单独运行,你可以试着在Activity启动Service后,进入Home界面,然后过几秒再请求位置,就请求不到了。但是不会崩溃,因为这个被系统设置的权限类别为MODE_IGNORED,也就是静默失败模式。

所以为了保险起见,只要前台服务涉及到了这三个功能,就在清单文件加上android:foregroundServiceType

10.Toast行为变更

(1)自定义Toast被屏蔽

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

如果您的应用仍尝试从后台发布包含自定义视图的消息框,系统不会向用户显示相应的消息,而是会在 logcat 中记录以下消息:

1
2

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

如果应用在后台时弹出自定义toast则会有相关警告,但不会crash。

(2) 消息框回调

如果您希望在消息框(文本消息框或自定义消息框)出现或消失时收到通知,请使用 Android 11 中添加的 addCallback() 方法。

11.allowBackup

如果您的应用以 Android 11 为目标平台,您将无法再使用 allowBackup 属性停用应用文件的设备到设备迁移。系统会自动启用此功能。不过,即使您的应用以 Android 11 为目标平台,您也可以通过将 allowBackup 属性设置为 false 来停用应用文件的云端备份和恢复。

android:allowBackup属性

  • 代表是否允许应用参与备份和恢复基础架构。如果将此属性设为 false,则永远不会为该应用执行备份或恢复,即使是采用全系统备份方法也不例外(这种备份方法通常会通过 adb 保存所有应用数据)。此属性的默认值为 true。

所以这里是不能停用文件的设备到设备迁移,但是可以停用云端备份和恢复

二、行为变更:所有应用

此模块的修改内容针对所有项目在Android11手机上存在的改动,与targetSdkVersion无关。

1.数据访问审核

为了让应用及其依赖项访问用户私密数据的过程更加透明,Android 11 引入了数据访问审核功能。借助此流程得出的见解,您可以更好地识别和纠正可能出现的意外数据访问。

哪些范畴属于用户私密数据呢?其实就是危险权限的调用,所以这个功能就是提供了可以监听危险权限调用的监听。主要涉及到的方法是AppOpsManager.OnOpNotedCallback。无论是应用本身,还是依赖库或者SDK中的代码,只要访问到私密数据(危险权限),都会回调给我们。

对于工程庞大或者使用较多SDK的工程比较适合用上这个功能,让自己应用的私有数据管理更加透明规范,否则对于私有数据的使用和管理并不全面和方便。而且还可以对权限使用添加归因,也就是一个tag,标志权限用到了什么地方。方便回调的时候知晓哪里使用了私有数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test1)

//创建归因(attribute)
attributionContext = createAttributionContext("shareLocation")

//监听事件
val appOpsCallback = object : AppOpsManager.OnOpNotedCallback() {
private fun logPrivateDataAccess(
opCode: String, attributionTag: String, trace: String) {
Log.i(TAG, "Private data accessed. " +
"Operation: $opCode\n " +
"Attribution Tag:$attributionTag\nStack Trace:\n$trace")
}

override fun onNoted(syncNotedAppOp: SyncNotedAppOp) {
syncNotedAppOp.attributionTag?.let {
logPrivateDataAccess(syncNotedAppOp.op,
it,
Throwable().stackTrace.toString())
}
}

override fun onSelfNoted(syncNotedAppOp: SyncNotedAppOp) {
syncNotedAppOp.attributionTag?.let {
logPrivateDataAccess(syncNotedAppOp.op,
it,
Throwable().stackTrace.toString())
}
}

override fun onAsyncNoted(asyncNotedAppOp: AsyncNotedAppOp) {
asyncNotedAppOp.attributionTag?.let {
logPrivateDataAccess(asyncNotedAppOp.op,
it,
asyncNotedAppOp.message)
}
}
}

//开启私密数据监听
val appOpsManager =
getSystemService(AppOpsManager::class.java) as AppOpsManager
appOpsManager.setOnOpNotedCallback(mainExecutor, appOpsCallback)

btn1.setOnClickListener {
getLocation()
}
}

fun getLocation() {
val locationManager = attributionContext.getSystemService(
LocationManager::class.java) as LocationManager
if (!checkPermission()) {
return
}
val location: Location? = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
if (location != null) {
showToast("${location.latitude}")
}
}

该例子主要展示了一个获取位置信息的功能,如果调用到getLocation方法,就会触发onNoted回调,回调信息包括危险权限code以及归因。

其中OnOpNotedCallback 一共三个回调方法:

  • onNoted 正常情况下都会回调到该方法
  • onAsyncNoted 如果数据访问并非发生在应用调用API期间,就会调用onAsyncNoted(),比如一些监听器的回调。
  • onSelfNoted 在极少数情况下,如果应用将自身的UID传递到 noteOp(),需要调用 onSelfNoted()。

2.单次授权

在 Android 11 中,每当应用请求与位置信息、麦克风或摄像头相关的权限时,面向用户的权限对话框会包含仅限这一次选项。如果用户在对话框中选择此选项,系统会向应用授予临时的单次授权。

就是在申请与位置信息、麦克风或摄像头相关的权限时,系统会自动提供一个单次授权的选项,只供这一次权限获取。然后用户下次打开app的时候,系统会再次提示用户授予权限。这个影响应该不大,只要我们每次使用的时候都去判断权限,没有就去申请即可。

3.权限对话框的可见性

Android 11 建议不要请求用户已选择拒绝的权限。在应用安装到设备上后,如果用户在使用过程中屡次针对某项特定的权限点按拒绝,此操作表示其希望“不再询问”。

这个都算不上改动,只是官方的一个良好建议。建议在用户多次拒绝之后,不要再展示权限申请。

4.数据访问审核

为了让应用及其依赖项访问用户私密数据的过程更加透明,Android 11 引入了数据访问审核功能。借助此流程得出的见解,您可以更好地识别可能出现的意外数据访问。您的应用可以注册 AppOpsManager.OnOpNotedCallback 实例,该实例可在每次发生以下任一事件时执行相应操作:

  • 应用的代码访问私密数据。为了帮助您确定应用的哪个逻辑部分调用了事件,您可以按归因标记审核数据访问。
  • 依赖库或 SDK 中的代码访问私密数据。

5.永久SIM卡标识符

在 Android 11 及更高版本中,使用 getIccId() 方法访问不可重置的 ICCID 受到限制。该方法会返回一个非 null 的空字符串。如需唯一标识设备上安装的 SIM 卡,请改用 getSubscriptionId() 方法。订阅 ID 会提供一个索引值(从 1 开始),用于唯一识别已安装的 SIM 卡(包括实体 SIM 卡和电子 SIM 卡)。除非设备恢复出厂设置,否则此标识符的值对于给定 SIM 卡是保持不变的。

6.非 SDK 接口限制

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

如果您的应用并非以 Android 11 为目标平台,那么其中一些变更可能不会立即对您产生影响。不过,虽然您目前可以使用一些非 SDK 接口(具体取决于应用的目标 API 级别),但只要您使用任何非 SDK 方法或字段,应用无法运行的风险终归较高。

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

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

4.Scudo Hardened Allocator

Android 11 在内部使用 Scudo Hardened Allocator 为堆分配提供服务。Scudo 能够检测并减轻某些类型的内存安全违规行为。如果您在原生代码崩溃报告中发现与 Scudo 相关的崩溃(例如 Scudo ERROR:),请参阅 Scudo 问题排查文档。

Scudo是一种动态的用户模式内存分配器,旨在抵御与堆相关的漏洞,同时保持良好的性能。它是一个开源的项目。 Android 11中,将采用这个新的heap分配器,性能更好,更安全。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

开源库推荐
1.BannerViewPager

一个基于ViewPager2实现的具有强大功能的无限轮播库。支持多种页面切换效果和指示器样式。

2.ViewPagerIndicator

一个适用于ViewPager和ViewPager2的指示器,支持多种滑块样式及滑动模式