1、分区存储概述

分区存储是Android 10开始引进的Android系统存储管理机制,它允许App读取和写入App自身创建的文件而不需要任何存储权限。其中根据存储位置的不同,可以分为内部内部存储和外部存储。内部存储就不用多说了,而外部存储又分为私有空间和公共空间。私有存储空间位置是/sdcard/Android/data/包名,而公共空间则是相册、下载等。对我们开发者影响最大的就是对于公共存储空间的读写了,总结如下:

  1. 对于9.0及以下的版本,仍然使用READ和WRITE权限,之前怎么做,现在还是怎么做
  2. 对于10.0,可以在清单文件中加入以下代码变得跟9.0以前一样
    <application android:requestLegacyExternalStorage="true" ...>
    ...
    </application>
    

    但是不建议,因为这种方式在Android 11已经不行了,反正都要适配

  3. 对于Android 10和11,读写App自己创建的文件不需要任何存储权限,读取其他应用创建的文件需要READ权限,但是WRITE权限被废弃了,写入其他应用创建的文件需要用户的干预。

Android分区存储机制其实挺好的,让很多软件不能为所欲为,至少提高了“犯罪成本”。就是来得晚了一些,导致我们开发者要各种兼容…

2、读取

以读取相册(DCIM)为例

val uris = ArrayList<Uri>()
contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null,null, "${MediaStore.MediaColumns.DATE_ADDED} desc")?.use {// 这里的it是一个Cursorwhile (it.moveToNext()) {val id = it.getLong(it.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))val uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)uris.add(uri)}
}

这个contextResolver就是是Context.getContentResolver(),Actvity是Context子类,所以用kotlin就可以直接简写。
这样就拿到了相册里面的图片的Uri,它是“content://”形式的uri:

content://media/external/images/media/71

这种读取形式在低版本也是可用的,但是需要READ权限。在Android 10和11中,如果有READ权限,则可以读取到所有的图片文件的Uri,否则只能读取到App本身创建的文件Uri。
在拿到Uri后,我们可以通过流的形式读取它:

contentResolver.openInputStream(uri)?.use { // do sth
}

如果是用于展示图片,可以使用Glide等开源框架,它们本身就支持加载Uri。

3、写入

同样以写入相册(DCIM)为例:

val bitmap = BitmapFactory.decodeResource(resources, R.drawable.girl)
val values = ContentValues()
values.put(MediaStore.MediaColumns.DISPLAY_NAME, "test.png")
values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // RELATIVE_PATH需要API 29values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
} else {values.put(MediaStore.MediaColumns.DATA, "${Environment.getExternalStorageDirectory().path}/${Environment.DIRECTORY_DCIM}/test.png")
}
contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)?.also { uri ->contentResolver.openOutputStream(uri)?.use { os ->bitmap.compress(Bitmap.CompressFormat.PNG, 100, os)}
}

同样地,在低版本写入需要WRITE权限,在10和11上不需要。但是如果这个要修改其他App创建的文件,就需要写成这样

private var uri: Uri? = null // 通过某些操作获取这个uri并赋值
private fun change() {val temp = uri ?: returncontentResolver.openInputStream(temp)?.use { val bitmap = BitmapFactory.decodeStream(it)val values = ContentValues()values.put(MediaStore.MediaColumns.DISPLAY_NAME, "test.png")values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png")if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // RELATIVE_PATH需要API 29values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)} else {values.put(MediaStore.MediaColumns.DATA, "${Environment.getExternalStorageDirectory().path}/${Environment.DIRECTORY_DCIM}/test.png")}try {contentResolver.openOutputStream(temp)?.use { os ->bitmap.compress(Bitmap.CompressFormat.PNG, 80, os)Toast.makeText(this@MainActivity, "OK", Toast.LENGTH_SHORT).show()}} catch (e: Exception) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && e is RecoverableSecurityException) {startIntentSenderForResult(e.userAction.actionIntent.intentSender, 10086, null, 0, 0, 0)} else {throw e}}}
}override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)if(requestCode == 10086 && resultCode == RESULT_OK) {change()}
}

会弹出这样的弹窗

如果是对多个文件进行写入,在Android 11上可以这样写:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {val request = MediaStore.createWriteRequest(contentResolver, listOf(uri1, uri2))startIntentSenderForResult(request.intentSender, 10086, null, 0, 0, 0)
}

值得一提的是,用户如果卸载了App后再重新安装,即使是卸载前App自身创建的文件也需要相关权限。也就是说卸载重装之后,“同一个App”其实在系统眼里不是同一个App。

3、管理存储的权限

分区存储机制很好地规范了Android App的存储行为,让它们读自己该读的,写自己该写的。但是有的应用天生就需要对SD卡进行全方位的访问,比如各种文件浏览器、垃圾清理软件等等,虽然很多所谓的垃圾清理软件本身就是最该被清理的垃圾…对此,Android 11引入了一个新的权限:

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

有了这个权限,就可以跟以前的版本一样随意玩耍了。那么是不是可以直接申请这个权限就可以了呢?机智如我,是可以的,不过应用市场不让上架…所以大部分App是不允许使用这个权限的。如果要申请此权限,需要打开设置界面,让用户手动设置

val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
startActivityForResult(intent, 10010)

如果在manifest中添加了requestLegacyExternalStorage属性,还可以加上包名

val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
intent.data = Uri.parse("package:$packageName")
startActivityForResult(intent, 10010)

出现的界面长这样

Android分区存储相关推荐

  1. 【错误记录】Android 分区存储 错误 ( 文件格式不匹配 )

    文章目录 一.报错信息 二.解决方案 一.报错信息 Android 分区存储 , 将 图片文件 保存到 Movies 目录下报错 : 2021-05-18 14:31:50.691 1341-5448 ...

  2. android 分区存储适配总结

    android 分区存储适配总结 一.分区存储概念 二.分区适配方案 1. 应用分区存储的文件访问规定 (1).应用专属目录 (2).共享目录文件 2.MediaStore API介绍 3.Stora ...

  3. 强制开启Android 分区存储 沙盘文件系统

    为了测试Android 11下强制分区存储后的应用兼容问题,这里摸索了下目前的打开方式 1. 在AS里下载API 30的 虚拟机 2. 打开虚拟机,进入首页后,执行 adb shell sm set- ...

  4. Android 分区存储常见问题解答

    要在 Google Play 上发布,开发者需要将应用的 目标 API 级别 (targetSdkVersion) 更新到 API 级别 30 (Android 11) 或者更高版本.针对新上架的应用 ...

  5. 【错误记录】Android 分区存储下的 SD 卡应用专属外部存储空间目录访问 ( 需手动创建应用专属外部存储空间目录 )

    文章目录 一.报错信息 二.解决方案 一.报错信息 开发时 , 需要向外置 SD 卡中拷贝一些文件 , 应用读取这些文件 , 进行相关配置 ; 但是 Android 系统 , 并不会主动为应用创建文件 ...

  6. 【Android 文件管理】分区存储 ( 修改与删除图片文件 )

    文章目录 一.分区存储模式下使用 MediaStore 修改图片 二.分区存储模式下使用 MediaStore 删除图片 三.相关文档资料 Android 分区存储系列博客 : [Android 文件 ...

  7. 【Android 文件管理】分区存储 ( 创建与查询图片文件 )

    文章目录 一.分区存储模式下使用 MediaStore 插入图片 二.分区存储模式下使用 MediaStore 查询图片 三.相关文档资料 Android 分区存储系列博客 : [Android 文件 ...

  8. Android11(30)/Android10(29)分区存储-存储访问框架(SAF)

    概述 存储访问框架(SAF)是在Android 4.4(API 级别 19)引入的.借助 SAF,用户可轻松打开文档.图像及其他文件. 存储访问框架包含三部分: 文档提供程序 - 文档提供程序以 Do ...

  9. 结合Android去水印APP谈谈分区存储

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 前言 方便个人更新微信状态,上周花半天时间编写简单的抖音去水印AP ...

最新文章

  1. CSS 文字溢出显示省略号
  2. Java里边什么是值传递和引用传递?两个有什么区别
  3. Go语言构建json和解析json实例
  4. Oracle之数据操作__分组统计查询
  5. Vmware ESX server CPU掩码导致的挂起
  6. Linux 交换文件已存在解决办法
  7. 51nod 1126 求递推序列的第N项 思路:递推模拟,求循环节。详细注释
  8. 重磅:微信小程序开放公测了!
  9. Excel 使用VBA 使表格的值被修改后填充颜色标注
  10. Spring面试之bean作用域
  11. ubuntu 安装sql_在Ubuntu上进行SQL Server安装和故障排除
  12. 常用位操作技巧(Golang)
  13. Pikachu实验过程3(XSS的分析)
  14. mysql lru scan depth_如何解决mysql警告:“ InnoDB:page_cleaner:1000毫秒的预期循环用了XXX毫秒。设置可能不是最佳的”?...
  15. Leetcode 304.二维区域和检索-矩阵不可变
  16. 开源中国开源世界高峰论坛如期将至
  17. SQLServer 2008安装教程
  18. 【休憩时的练手】—— 制作简易的网易云音乐播放器
  19. AntennaHome Launch 5G Combo Internal PCB Antenna /5G 全频 PCB天线
  20. 用简单英语谈生意-介绍篇

热门文章

  1. js 条码枪扫描_js获取USB扫码枪数据
  2. 三相六脉冲全控整流的延时角与三相电压测量及锁相环的关系
  3. Termux中安装anconda
  4. 智慧电力可视化大屏,赋能虚拟电厂精准减碳
  5. swift 5.1 Json转换之Codable
  6. 给 Android 开发者的第一堂课
  7. PowerShell 和Microsoft Dynamics NAV / Business Central 关联
  8. SwiftUI 视图切换之自定义modal控制Card卡片效果
  9. MySQL赋权报错:’the right syntax to use near ‘identified by ‘password‘ with grant option‘
  10. 基于Java毕业设计校园闲置物品信息管理系统源码+系统+mysql+lw文档+部署软件