本文原作者: 积木zz,原文发布于: 码上积木
https://mp.weixin.qq.com/s/ihn0uijiNBGRoJX_gpvTrA
前言
Android 11
的到来,到时候迎接我的就是客户的指责,甚至老板的一封休书了😂。
Android 11
到底改了些什么,以及最重要的,我们需要怎么
适配
?targetversion 不改到 30,是不是就不用适配了呢?
以下我分为两部分讲述,分别是
Android 11
为目标版本的应用 (
targetSdkVersion>=30
才有影响)⭐
Android 11
设备上适配改动 (无论 targetSdkVersion 是多少,只要在
Android 11 设备
上运行的应用都有影响)
targetSdkVersion>=30
的模块呢?因为一般来说为了 Google 为了让我们更长时间适应新的内容以及保障线上应用的稳定,都会把改动大的,需要花时间适配的内容放到新的
targetSdkVersion
对应的应用上,如果你暂时没有适配
targetSdkVersion30
的需求,也可以看看第二模块,看看是否有涉及你的应用相关内容。GOGOGO!
适配 targetSdkVersion30
targetSdkVersion 30
或者以上才生效。
“对外部存储目录的访问仅限于应用专属目录,以及应用已创建的特定类型的媒体。
”
Android 10
就已经推行了,简单的说,就是应用对于文件的读写只能在沙盒环境,也就是属于自己应用的目录里面读写。其他媒体文件可以通过
MediaStore
进行访问。
targetSdkVersion = 29
应用中,设置
android:requestLegacyExternalStorage="true"
,就可以不启动分区存储,让以前的文件读取正常使用。但是
targetSdkVersion = 30
中不行了,强制开启分区存储。
android:preserveLegacyExternalStorage="true"
,暂时关闭分区存储,好让开发者完成数据迁移的工作。为什么是暂时呢?因为只要
卸载重装
,就会失效了。以下是关于分区存储会遇到的
所有情况
,给大家罗列出来了,先上代码:
fun saveFile() {
if (checkPermission()) {
//getExternalStoragePublicDirectory被弃用,分区存储开启后就不允许访问了
val filePath = Environment.getExternalStoragePublicDirectory("").toString() + "/test3.txt"
val fw = FileWriter(filePath)
fw.write("hello world")
fw.close()
showToast("文件写入成功")
}
}
targetSdkVersion = 28
,运行后正常读写。targetSdkVersion = 29
,不删除应用,targetSdkVersion 由 28 修改到 29,覆盖安装,运行后正常读写。targetSdkVersion = 29
,删除应用,重新运行,读写报错,程序崩溃 (open failed: EACCES (Permission denied))targetSdkVersion = 29
,添加android:requestLegacyExternalStorage="true"(不启用分区存储),读写正常不报错targetSdkVersion = 30
,不删除应用,targetSdkVersion 由 29 修改到 30,读写报错,程序崩溃 (open failed: EACCES (Permission denied))targetSdkVersion = 30
,不删除应用,targetSdkVersion 由 29 修改到 30,增加 android:preserveLegacyExternalStorage="true",读写正常不报错targetSdkVersion = 30
,删除应用,重新运行,读写报错,程序崩溃 (open failed: EACCES (Permission denied))
ok,那到底应该怎么改呢?三种方法访问文件:
//分区存储空间
val file = File(context.filesDir, filename)
//应用专属外部存储空间
val appSpecificExternalDir = File(context.getExternalFilesDir(), filename)
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()
}
3)SAF (存储访问框架--Storage Access Framework)
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")
}
}
具体还有很多操作可以看看网上关于分区存储的资料,因为 Android 10 已经出来很久了,所以资料还是很多的,这里推荐几篇:
说到这里可能又有人问了,那我的应用就是个手机管理器,总不能不让我清其他应用的缓存了吧,有办法!Android 提供了两个 intent 入口:
调用ACTION_MANAGE_STORAGE intent
操作检查可用空间。
调用ACTION_CLEAR_APP_CACHE intent
操作清除所有缓存。
就来不及了
。
“为了在保证用户隐私的同时可以更轻松地访问媒体,Android 11 增加了以下功能。执行批量操作和使用直接文件路径和原生库访问文件。
”
1)执行批量操作
这里的批量操作指的是 Android 11 向 MediaStore API
中添加了多种方法,用于简化特定媒体文件更改流程 (例如在原位置编辑照片),分别是:
createWriteRequest()
用户向应用授予对指定媒体文件组的写入访问权限的请求。
createFavoriteRequest()
用户将设备上指定的媒体文件标记为 "收藏" 的请求。对该文件具有读取访问权限的任何应用都可以看到用户已将该文件标记为 "收藏"。
createTrashRequest()
用户将指定的媒体文件放入设备垃圾箱的请求。垃圾箱中的内容会在系统定义的时间段后被永久删除。
createDeleteRequest()
用户立即永久删除指定的媒体文件 (而不是先将其放入垃圾箱) 的请求。
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)直接文件路径和原生库访问文件
没错!Android 11 又恢复了使用直接文件路径
访问访问媒体文件!哈哈,这样就方便多了。也就是除了 MediaStore API
之外还有两种方式可以访问媒体文件:
File API。
原生库,例如 fopen()。
Android 10
咋办呢??要不就用
MediaStore
,要不就直接把分区存储关了吧 (requestLegacyExternalStorage=true)
MANAGE_EXTERNAL_STORAGE
这不来了吗。这个权限就是用来获取
所有文件
的管理权限。🌰:
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
val intent = Intent()
intent.action= Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION
startActivity(intent)
//判断是否获取MANAGE_EXTERNAL_STORAGE权限:
val isHasStoragePermission= Environment.isExternalStorageManager()
电话号码相关权限 ⭐
“Android 11 更改了您的应用在读取电话号码时使用的与电话相关的权限。
”
具体改了什么呢?其实就是两个 API:
TelecomManager 类中的 getLine1Number()
方法
TelecomManager 类中的 getMsisdn()
方法
READ_PHONE_STATE
权限不管用了,需要
READ_PHONE_NUMBERS
权限才行。
targetSdkVersion
修改到 30,然后运行一个获取电话号码的程序:
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.READ_PHONE_STATE), 100)
btn2.setOnClickListener {
val tm = this.applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
val phoneNumber = tm.line1Number
showToast(phoneNumber)
}
java.lang.SecurityException: getLine1NumberForDisplay: Neither user 10151 nor current process has android.permission.READ_PHONE_STATE, android.permission.READ_SMS, or android.permission.READ_PHONE_NUMBERS
Andmanifest.xml
中注册好权限,并且添加动态权限申请:
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.READ_PHONE_STATE,Manifest.permission.READ_PHONE_NUMBERS), 100)
READ_PHONE_NUMBERS
这一个权限:
<uses-permission android:name="android.permission.READ_PHONE_STATE" android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
“从 Android 11 开始,已弃用自定义消息框视图。如果您的应用以 Android 11 为目标平台,包含自定义视图的消息框在从后台发布时会被屏蔽
”
自定义消息框视图
啊?我说英文你就知道了,英文是
custom toast views
,也就是自定义 toast。简单写个代码:
Toast toast = new Toast(context);
toast.setDuration(show_length);
toast.setView(view);
toast.show();
Handler().postDelayed({
IToast.show("你好,我是自定义toast")
}, 3000)
W/NotificationService: Blocking custom toast from package com.example.studynote due to package not in the foreground
“对于以 Android 11 (API 级别 30) 为目标平台,且目前仅使用 APK 签名方案 v1 签名的应用,现在还必须使用 APK 签名方案 v2 或更高版本进行签名。用户无法在搭载 Android 11 的设备上安装或更新仅通过 APK 签名方案 v1 签名的应用。
”
targetSdkVersion
修改到 30,那么你就必须要加上 v2 签名才行。否则无法安装和更新。
从 Android 11 开始,只有预装的系统相机应用可以响应以下 intent 操作:
intent
唤起照相机,使用
VIDEO_CAPTURE
的 action,只有系统的相机能够响应,而第三方的相机应用不会响应了。
val intent=Intent()
intent.action=android.provider.MediaStore.ACTION_IMAGE_CAPTURE
startActivity(intent)
//无法唤起第三方相机了,只能唤起系统相机
intent
设置软件包名称或组件来使这些 intent 变得明确。
“Android 11 添加了在您的应用中支持 5G 的功能
”
新的 Android 11 也是支持了5G 相关的
一些功能,包括:
检测是否连接到了 5G 网络
检查按流量计费性
TelephonyManager
的监听方法:
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)
}
流量计费
的,否则 5G 的流量可不是开玩笑的。
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
,则将连接视为不按流量计费。
“在搭载 Android 11 的设备上,当应用中的某项功能请求在后台访问位置信息时,用户看到的系统对话框不再包含用于启用后台位置信息访问权限的按钮。如需启用后台位置信息访问权限,用户必须在设置页面上针对应用的位置权限设置一律允许选项。
”
什么意思呢?主要涉及到两点:
从 Android 10 系统的设备开始,就需要请求后台位置权限(ACCESS_BACKGROUND_LOCATION)
,并选择Allow all the time (始终允许)
才能获得后台位置权限。Android 11 设备上再次加强
对后台权限的管理,主要表现在系统对话框
上,对话框不再提示始终允许字样,而是提供了位置权限的设置入口,需要在设置页面选择始终允许
才能获得后台位置权限。
在搭载Android 11 系统
的设备上,targetVersion 小于 11 的时候,可以前台后台位置权限一起申请,并且对话框提供了文字说明,表示需要随时获取用户位置信息,进入设置选择始终允许
即可。但是 targetVersion 为 30 的时候,你必须单独申请
后台位置权限,而且要在获取前台权限之后,顺序不能乱
。并且无任何提示,需要开发者自己设计提示样式。
Android 10 设备
,申请前台和后台位置权限 (任意 targetSdkVersion):
requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_BACKGROUND_LOCATION), 100)
Android 11 设备
,targetSdkVersion<=29(Android 10),申请前台和后台位置权限:
requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_BACKGROUND_LOCATION), 100)
Android 11 设备
,targetSdkVersion=30(Android 11),申请前台和后台位置权限:
requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_BACKGROUND_LOCATION), 100)
Android 11设备
,targetSdkVersion=30(Android 11),先申请前台位置权限,后申请后台位置权限:
requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION), 100)
requestPermissions(arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION), 100)
所以,该怎么适配呢?
targetSdkVersion<30
情况下,如果你之前就有判断过前台和后台位置权限,那就无需担心,没有什么需要适配。
targetSdkVersion>30
情况下,需要分开申请前后台位置权限,并且对后台位置权限申请做好说明和引导,当然也是为了更好的服务用户。
val permissionAccessCoarseLocationApproved = ActivityCompat
.checkSelfPermission(this, permission.ACCESS_COARSE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
if (permissionAccessCoarseLocationApproved) {
val backgroundLocationPermissionApproved = ActivityCompat
.checkSelfPermission(this, permission.ACCESS_BACKGROUND_LOCATION) ==
PackageManager.PERMISSION_GRANTED
if (backgroundLocationPermissionApproved) {
//前后台位置权限都有
} else {
//申请后台权限
if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.R){
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
200)
}else{
AlertDialog.Builder(this).setMessage("需要提供后台位置权限,请在设置页面选择始终允许")
.setPositiveButton("确定", DialogInterface.OnClickListener { dialog, which ->
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
200)
}).create().show()
}
}
} else {
if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.R){
//申请前台和后台位置权限
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_BACKGROUND_LOCATION),
100)
}else{
//申请前台位置权限
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION),
100)
}
}
“Android 11 更改了应用查询用户已在设备上安装的其他应用以及与之交互的方式。使用新的
”元素,应用可以定义一组自身可访问的其他应用。通过告知系统应向您的应用显示哪些其他应用,此元素有助于鼓励最小权限原则。此外,此元素还可帮助 Google Play 等应用商店评估应用为用户提供的隐私权和安全性。
Android 11 中
,如果你想去获取其他应用的信息,比如包名,名称等等,不能直接获取了,必须在清单文件中添加
<queries>
元素,告知系统你要获取哪些应用信息或者哪一类应用。
val pm = this.packageManager
val listAppcations: List<ApplicationInfo> = pm
.getInstalledApplications(PackageManager.GET_META_DATA)
for (app in listAppcations) {
Log.e("lz",app.packageName)
}
Android 11
版本,只能查询到自己应用和系统应用的信息,查不到其他应用的信息了。怎么呢?添加
<queries>
元素,两种方式:
<manifest package="com.example.game">
<queries>
<package android:name="com.example.store" />
<package android:name="com.example.services" />
</queries>
...
</manifest>
intent
<manifest package="com.example.game">
<queries>
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="image/jpeg" />
</intent>
</queries>
</manifest>
浏览器或者设备管理器
咋办呢?我就要获取所有包名啊?放心,Android 11 还引入了
QUERY_ALL_PACKAGES
权限,清单文件中加入即可。但是
Google Play
可不一定能滥用哦,它为需要 QUERY_ALL_PACKAGES 权限的应用会提供相关指南,但是还没出来,具体要看后面的消息了。
“为让开发者有时间进行测试,以下与存储访问框架 (SAF) 相关的变更只有在应用以 Android 11 为目标平台时才会生效。
”
上文存储的时候说过可以通过SAF(存储访问框架--Storage Access Framework)
来访问公共目录,但是 Android 11 再次升级,部分目录和文件不能访问了,具体如下:
无法再使用 ACTION_OPEN_DOCUMENT_TREE intent
操作请求访问以下目录:
无法再使用 ACTION_OPEN_DOCUMENT_TREE
或 ACTION_OPEN_DOCUMENT intent
操作请求用户从以下目录中选择单独的文件:
Android/data/ 目录及其所有子目录。
Android/obb/ 目录及其所有子目录。
“以 Android 11 为目标平台的应用现在必须具备 Manifest.permission.WRITE_APN_SETTINGS 特权,才能读取或访问电话提供程序 APN 数据库。如果在不具备此权限的情况下尝试访问 APN 数据库,会生成安全异常。
”
问题来了,APN 是啥?
Manifest.permission.WRITE_APN_SETTINGS
权限就不能读取 APN 数据库了,但是!这个权限很早之前就被限定只有系统程序才能申请这个权限了,现在这个特权没理解到是什么意思,难道系统程序都不能随便申请了?有
大神
可以评论区留言告知。
“从 Android 11 开始,您的无障碍服务无法在运行时声明与系统的 "无障碍" 按钮的关联。如果您将 AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON 附加到 AccessibilityServiceInfo 对象的 flags 属性,框架就不会将 "无障碍" 按钮回调事件传递给您的服务。 ”
AccessibilityServiceInfo
要设置 flag 为
FLAG_REQUEST_ACCESSIBILITY_BUTTON,getAccessibilityButtonController
方法获取辅助功能按钮控制器,并且可用于查询辅助功能按钮的状态并注册监听器以进行交互和辅助功能按钮的状态更改。
flagRequestAccessibilityButton
标记声明您的无障碍服务与 "无障碍" 按钮的关联。
“如果您的应用以 API 级别 30 或更高级别为目标平台,在搭载 Android 6.0 (API 级别 23) 或更高版本的设备上会停用 Firebase JobDispatcher 和 GcmNetworkManager API 调用。
”
WorkManager
,这个国内是可以用的,属于 jetpack 组件,主要用于调度和执行可延期的后台工作。
“如果您的应用以 Android 11 为目标平台,您将无法再使用 allowBackup 属性停用应用文件的设备到设备迁移。系统会自动启用此功能。不过,即使您的应用以 Android 11 为目标平台,您也可以通过将 allowBackup 属性设置为 false 来停用应用文件的云端备份和恢复。
”
android:allowBackup 属性
代表是否允许应用参与备份和恢复基础架构。如果将此属性设为 false,则永远不会为该应用执行备份或恢复
,即使是采用全系统备份方法也不例外 (这种备份方法通常会通过 adb 保存所有应用数据)。此属性的默认值为 true。
设备到设备
迁移,但是可以停用
云端备份和恢复
“如果应用以 Android 11 为目标平台并且数月未使用,系统会通过自动重置用户已授予应用的运行时敏感权限来保护用户数据。此操作与用户在系统设置中查看权限并将应用的访问权限级别更改为拒绝的做法效果一样。如果应用已遵循有关在运行时请求权限的最佳做法,那么您不必对应用进行任何更改。这是因为,当用户与应用中的功能互动时,您应该会验证相关功能是否具有所需权限。
”
每次需要调用权限
的时候都会去判断,那么就不会有什么问题。
Settings.ACTION_APPLICATION_DETAILS_SETTINGS action
的 Intent 将用户定向到系统设置中应用的页面。
isAutoRevokeWhitelisted()
方法。如果此方法返回 true,代表系统不会自动重置应用的权限。
“从 Android 9 开始,应用仅限于在前台访问摄像头和麦克风。为了进一步保护用户,Android 11 更改了前台服务访问摄像头和麦克风相关数据的方式。如果您的应用以 Android 11 为目标平台并且在某项前台服务中访问这些类型的数据,您需要在该前台服务的声明的 foregroundServiceType 属性中添加新的 camera 和 microphone 类型。
”
位置信息、摄像头和麦克风
,那么就这样添加:
<manifest>
<service ...
android:foregroundServiceType="location|camera|microphone" />
</manifest>
适配 Android 11 手机
Android 11
手机上存在的改动,与
targetSdkVersion
无关。
“为了让应用及其依赖项访问用户私密数据的过程更加透明,Android 11 引入了数据访问审核功能。借助此流程得出的见解,您可以更好地识别和纠正可能出现的意外数据访问。
”
AppOpsManager.OnOpNotedCallback
。无论是应用本身,还是依赖库或者 SDK 中的代码,只要访问到私密数据 (危险权限),都会回调给我们。
透明规范
,否则对于私有数据的使用和管理并不全面和方便。而且还可以对权限使用
添加归因
,也就是一个 tag,标志权限用到了什么地方。方便回调的时候知晓哪里使用了
私有数据
。
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 以及归因。
onNoted
正常情况下都会回调到该方法
onAsyncNoted
如果数据访问并非发生在应用调用API期间,就会调用onAsyncNoted(),比如一些监听器的回调。
onSelfNoted
在极少数情况下,如果应用将自身的UID传递到 noteOp(),需要调用 onSelfNoted()。
Private data accessed. Operation: android:coarse_location
Attribution Tag:shareLocation
Stack Trace:
[Ljava.lang.StackTraceElement;@14f5a16
android:coarse_location
以及归因
shareLocation
“在 Android 11 中,每当应用请求与位置信息、麦克风或摄像头相关的权限时,面向用户的权限对话框会包含仅限这一次选项。如果用户在对话框中选择此选项,系统会向应用授予临时的单次授权。
”
位置信息、麦克风或摄像头
相关的权限时,系统会自动提供一个
单次授权
的选项,只供这一次权限获取。然后用户下次打开 app 的时候,系统会再次提示用户授予权限。这个影响应该不大,只要我们每次使用的时候都去判断权限,没有就去申请即可。放一张新版本权限获取样式:
“Android 11 建议不要请求用户已选择拒绝的权限。在应用安装到设备上后,如果用户在使用过程中屡次针对某项特定的权限点按拒绝,此操作表示其希望 "不再询问"。
”
良好建议
。建议在用户多次拒绝之后,不要再展示权限申请。
“Android 11 在内部使用 Scudo Hardened Allocator 为堆分配提供服务。Scudo 能够检测并减轻某些类型的内存安全违规行为。如果您在原生代码崩溃报告中发现与 Scudo 相关的崩溃 (例如 Scudo ERROR:),请参阅 Scudo 问题排查文档。
”
Scudo
是一种动态的用户模式内存分配器,旨在抵御与堆相关的漏洞,同时保持良好的性能。它是一个开源的项目。Android 11 中,将采用这个新的
heap 分配器
,性能更好,更安全。
“Android 10 引入了 fdsan (文件描述符排错程序)。fdsan 检测错误处理文件描述符所有权的错误,例如 use-after-close 和 double-close。在 Android 11 中,fdsan 的默认模式发生了变化。现在,fdsan 会在检测到错误时中止,而以前的行为则是记录警告并继续。
”
fdsan
是啥?先要了解 fd 是啥
Unix/Linux
系统文件操作的相关概念,它在形式上是一个非负整数。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。系统的进程也就是使用了
这个 fd
来标示打开的文件,有了它就能对文件做各种操作,获得文件的各种相关信息了。
fdsan
也就是检测文件处理中发生的一些错误。
“为了更好地保护用户,Android 11 将每个用户的应用使用情况统计信息存储在凭据加密存储空间中。
”
UsageStatsManager
,
UsageStatsManager
是 Android 提供统计应用使用情况的服务。通过这个服务可以获取指定时间区间内应用使用统计数据、组件状态变化事件统计数据以及硬件配置信息统计数据。
queryAndAggregateUsageStats
方法,可以获取指定时间区间内使用统计数据,以应用包名为键值进行数据合并。
isUserUnlocked()
方法返回 true 的时候,才能正常访问这些数据。也就是以下两种情况:
JobScheduler
任务调度器,可以在设备空闲时做一些任务处理。Android 11 中如果你设置为
debug模式
(debuggable 清单属性设置为 true),超出速率限制的
JobScheduler API
调用将返回
RESULT_FAILURE
。这个有什么用呢?应该可以帮助我们发现一些性能问题,感兴趣的可以自己试试。
WorkManager
也是用到了 JobScheduler,不熟悉的同学可以去了解下,
JobScheduler
是由 SystemServer 进程启动的一个系统服务,所以才可以有这么大的权限。
“在以前的 Android 版本中,框架会向未正确处理基于点击的无障碍操作的微件分派触摸事件。通常,这些视图会直接处理触摸事件,而不是注册点击监听器。为了在正确定义无障碍操作的应用中创建更一致的行为,Android 11 绝不会分派触摸事件。相反,系统会完全依赖于基于点击的无障碍操: ACTION_CLICK 和 ACTION_LONG_CLICK。此更改会影响屏幕阅读器的行为。
”
Android
手机上有个预安装的屏幕阅读服务,叫做
TalkBack
,为视力障碍人士或者视力状态不佳的老年人提供。那我们应用为了让这个阅读器能够读懂你的自定义 view 操作,必须给与自定义控件定义处理程序,包括
点击,长按
等操作。原来版本可能对于
OnTouchListener
也支持无障碍触摸事件,而在
Android 11
中,必须专门制定点击或者长按事件才行了。给个🌰:
class TriSwitch(context: Context) : Switch(context) {
// 0, 1, or 2.
var currentState: Int = 0
private set
init {
updateAccessibilityActions()
}
private fun updateAccessibilityActions() {
ViewCompat.replaceAccessibilityAction(this, ACTION_CLICK,
action-label) {
view, args -> moveToNextState()
})
}
private fun moveToNextState() {
currentState = (currentState + 1) % 3
}
}
TriSwitch
,继承自 Switch,由于和
Switch
的点击效果不一样,所以必须通过替换
ViewCompat.replaceAccessibilityAction()
来重新定义相应的无障碍操作。
“Android 11 包含更新后的受限制非 SDK 接口列表 (基于与 Android 开发者之间的协作以及最新的内部测试)。在限制使用非 SDK 接口之前,我们会尽可能确保提供公开替代方案。
”
总结
一路分析下来也可以看到,如果是重要的改动,特别是涉及到崩溃的改动还是放到了targetSdkVersion=30
的内容中,这也是每次 Android 发版的一个潜规则吧,为了最大程度不影响已上线的 app 所作出的举动。
最好的体验
。而且各大应用市场也都会建议或者强制应用升级
targetSdkVersion
,以便适配最新的手机。
长按右侧二维码
查看更多开发者精彩分享
"开发者说·DTalk" 面向中国开发者们征集 Google 移动应用 (apps & games) 相关的产品/技术内容。欢迎大家前来分享您对移动应用的行业洞察或见解、移动开发过程中的心得或新发现、以及应用出海的实战经验总结和相关产品的使用反馈等。我们由衷地希望可以给这些出众的中国开发者们提供更好展现自己、充分发挥自己特长的平台。我们将通过大家的技术内容着重选出优秀案例进行谷歌开发技术专家 (GDE) 的推荐。
点击屏末 | 阅读原文 | 即刻报名参与 "开发者说·DTalk"