前言
最近看到一条新闻,Android 11(version 30,Andorid R)最终Beta版 如期发布,看到这个新闻我知道我不能再拖了,再不好好准备好迎接Android11
的到来,到时候迎接我的就是客户的指责,甚至老板的一封休书了 😂。
今天就和大家一起看看Android11
到底改了些什么,以及最重要的,我们需要怎么适配?targetversion不改到30,是不是就不用适配了呢?
以下我分为两部分讲述,分别是
- 以
Adnroid11
为目标版本的应用(targetSdkVersion>=30
才有影响)⭐ - 所有应用在
Android11
设备上适配改动(无论targetSdkVersion是多少,只要在Android11设备
上运行的应用都有影响)
为什么先说targetSdkVersion>=30
的模块呢?因为一般来说为了Google为了让我们更长时间适应新的内容以及保障线上应用的稳定,都会把改动大的,需要花时间适配的内容放到新的targetSdkVersion
对应的应用上,如果你暂时没有适配targetSdkVersion30
的需求,也可以看看第二模块,看看是否有涉及你的应用相关内容。GOGOGO!
Tips:此适配文章会不间断更新,根据Android11
发布进度调整,欢迎点赞关注。(打⭐的格外注意哦)
适配targetSdkVersion30
此模块的修改内容只针对targetSdkVersion 30
或者以上才生效。
分区存储强制执行⭐
对外部存储目录的访问仅限于应用专属目录,以及应用已创建的特定类型的媒体。
关于分区存储,在Android10
就已经推行了,简单的说,就是应用对于文件的读写只能在沙盒环境,也就是属于自己应用的目录里面读写。其他媒体文件可以通过MediaStore
进行访问。
但是在android10的时候,Google还是为开发者考虑,留了一手。在targetSdkVersion = 29
应用中,设置android:requestLegacyExternalStorage="true"
,就可以不启动分区存储,让以前的文件读取正常使用。但是targetSdkVersion = 30
中不行了,强制开启分区存储。
当然,作为人性化的android,还是为开发者留了一小手,如果是覆盖安装呢,可以增加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("文件写入成功")
}
}
分情况运行:
1) targetSdkVersion = 28
,运行后正常读写。
2) targetSdkVersion = 29
,不删除应用,targetSdkVersion 由28修改到29,覆盖安装,运行后正常读写。
3) targetSdkVersion = 29
,删除应用,重新运行,读写报错,程序崩溃(open failed: EACCES (Permission denied))
4) targetSdkVersion = 29
,添加android:requestLegacyExternalStorage=“true”(不启用分区存储),读写正常不报错
5) targetSdkVersion = 30
,不删除应用,targetSdkVersion 由29修改到30,读写报错,程序崩溃(open failed: EACCES (Permission denied))
6) targetSdkVersion = 30
,不删除应用,targetSdkVersion 由29修改到30,增加android:preserveLegacyExternalStorage=“true”,读写正常不报错
7) targetSdkVersion = 30
,删除应用,重新运行,读写报错,程序崩溃(open failed: EACCES (Permission denied))
ok,那到底应该怎么改呢?三种方法访问文件:
1)应用专属目录
//分区存储空间
val file = File(context.filesDir, filename)
//应用专属外部存储空间
val appSpecificExternalDir = File(context.getExternalFilesDir(), filename)
2)访问公共媒体目录文件
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()
}
- 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")
}
}
具体还有很多操作可以看看网上关于分区存储的资料,因为Android10已经出来很久了,所以资料还是很多的,这里推荐几篇
访问应用专属文件
Android 10适配要点,作用域存储
AndroidQ(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(reques