数据存储简述
名称 | 作用 | 详细说明 |
应用专属存储空间 | 存储应用专属的文件 | 专属存储空间可以在内部存储和外部存储为用户开辟专属的目录,可以用来存储其它用户不能访问的文件,可以通过File api 访问,应用被卸载后文件同时被删除,访问不需要权限 |
共享存储 | 存储所有应用共享的数据 | 存储您的应用打算与其他应用共享的文件,包括媒体、文档和其他文件,可以使用MediaStoreapi 访问,应用卸载数据仍然存在,访问需要申请存储权限 |
外部存储空间的使用
使用外部专属存储空间
在android4.4-android9
的版本之中,应用被分配了外部专属存储空间,这个空间无需权限即可访问
,如果应用想访问其它应用的外部专属存储空间(分区存储
),那么需要申请存储权限。
android10
以上的版本中,应用的外部存储空间成为了应用的私有空间,任何应用不可以访问其它应用的专属存储空间
- 特性
外部专属存储空间位于应用的外部存储中,我们可以在不申请权限的情况下访问外部专属存储空间,外部专属存储空间可以在应用卸载后被删除,专属空间目录媒体文件通常不应该被相册等媒体应用收录(也就是说相册中不会展示这个目录下的图片)(我们自定义的图片选择器也不应该扫描这个目录下的图片)
在低于 android10 的版本中,这个目录是对所有应用可见的
- 访问方式
获取目录路径:
getExternalFilesDir(""):
/storage/emulated/0/Android/data/com.ananananzhuo.storage10demo/filesgetExternalFilesDirs():
获取目录下所有文件的列表
- 使用 demo
val file = File(getExternalFilesDir(""),"安安安安安卓.txt") val fos = FileOutputStream(file) fos.write("公众号:安安安安安卓".toByteArray(Charsets.UTF_8)) fos.close() 复制代码
上面的代码中会在外部专属存储目录(/storage/emulated/0/Android/data/com.ananananzhuo.storage10demo/files
)下创建一个安安安安安卓.txt
的文件,这里我们没有声明和申请任何权限。执行代码后去看文件选择器中的展示如下:
在外部存储中访问非私有目录则必须声明申请权限下面我们写一个反面案例
- 代码
ItemData(title = "访问外部存储空间",{ val file = File(Environment.getExternalStorageDirectory().absolutePath) val fileAn = File(file,"安安安.txt") logEE(file.absolutePath) val fos = FileOutputStream(fileAn) fos.write("公众号:安安安安安卓".toByteArray(Charsets.UTF_8)) fos.close() }) 复制代码
- 运行后崩溃日志 提示没有权限
将媒体文件存储在外部专属存储目录的媒体文件夹中
将图片存储到图片文件夹中
- 代码
val picFile = getExternalFilesDir(Environment.DIRECTORY_PICTURES)?.apply { if (!exists()) { mkdirs() } } val file = File(picFile,"pic.jpg") FileOutputStream(file).apply { write(12) flush() close() } 复制代码
- 结果
成功存储文件
媒体文件夹还有其他选项:
- Environment.DIRECTORY_MOVIES
- Environment.DIRECTORY_DOWNLOADS
- Environment.DIRECTORY_DOCUMENTS
- Environment.DIRECTORY_SCREENSHOTS
存储媒体的时候我们应该尽量使用 Environment 中的字符串常量获取媒体文件目录
android10 以上版本能否使用 Environment.getExternalStorageDirectory 存取文件
高于android10
的版本无法使用getExternalStorageDirectory api
操作文件,因为 api 已经被删除,低于android10
可以继续使用该 api
那么高于android10
的版本我们应该如何访问这部分的文件呢?别急,后续的共享存储
会详细说明
- 代码
- 效果图
android10以上版本如何获取sd卡中普通目录的图片
我们可以通过SAF在android11以上版本获取普通目录图片(即非专属目录,非共享目录的图片)
本例中的方法不适用于android10
以下系统版本(强行使用会崩溃
)
- 代码
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_directory_scan11) findViewById<Button>(R.id.btn_secletdirectory).setOnClickListener { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT) intent.addCategory(Intent.CATEGORY_OPENABLE) intent.type = "image/jpeg" startActivityForResult(intent, 100)//跳转系统浏览器 } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) data?.data?.let { iv.setImageURI(it)//通过url处理图片 } } 复制代码
- 效果
内部存储
使用 openFileOutput 和 openFileInput 操作内部存储
将一个文件存储到内部存储的 files 目录下
- 代码
openFileOutput("安安安安卓openfileoutput.txt", MODE_PRIVATE).apply { write("openFileOutput方法输入流".toByteArray(Charsets.UTF_8)) flush() close() } 复制代码
- 执行后文件查看
openFileInput 方法和 openFileOutput 一样的使用方式
fileList 获取内部存储全部文件路径
- 代码
ItemData(title = "获取目录中所有文件路径",{ fileList().forEach { logEE("文件路径:$it") } }) 复制代码
- 输出日志
E/安安安安卓: 文件路径:安安安安卓openfileoutput.txt 复制代码
getCacheDir 获取内部存储缓存文件
内部存储缓存中的文件可能会在应用被卸载后被删除,也可能在未卸载前被删除(内部存储空间不足的情况)。所以我们使用内部存储空间缓存文件的时候需要先判断文件是否存在
context.getCacheDir() 复制代码
共享存储空间(android10 以上版本)
媒体
android10
以上将媒体文件文件按类型保存在公共目录上,可以使用 MediaStore 访问媒体文件
一下表格列举所有共享媒体文件类型
媒体类型 | 位置 | 备注 |
图片 | 存储在 Pictures 和 DICM 目录中,系统将这些文件存放在 MediaStore.Images 中 | 无 |
视频 | 存储 DICM、Movies、Pictures 目录中,系统将这些文件添加到 MediaStore.VIDEO 表中 | 无 |
音频文件 | 存储在 Alarms、Audiobooks、Music、Notifications、Podcasts、Ringtones 目录中,系统将这些文件添加到 MediaStore.Audio 表中 | 无 |
下载文件 | 存储在 Download 目录下,系统将这些文件添加到 MediaStore.Download 表中 | 低于 android10 的版本中不可用 |
文件集合 | 存在于 MediaStore.Files 表中 | 如果使用了分区存储,这个集合只会显示本应用创建的照片、视频、音频文件 |
开启分区存储权限,媒体的处理
如果应用使用分区存储,您需要在应用的清单中声明 ACCESS_MEDIA_LOCATION 权限,然后在运行时申请此权限。申请方法后面会讲
编写一个相册
- 首先需要获取共享存储中所有的图片地址,代码如下
private fun getGallaryData(): MutableList<Image> { val cursor = contentResolver.query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, "${MediaStore.Images.Media.DISPLAY_NAME} ASC" ) val tempdatas = mutableListOf<Image>() cursor?.use { val idColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media._ID) val nameColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME) while (it.moveToNext()) { val id = it.getLong(idColumn) val contentUrl = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id) val name = it.getString(nameColumn) val image = Image(contentUrl, "") tempdatas.add(image) logEE("文件名:${contentUrl.path}") } it.close() } return tempdatas } 复制代码
- 在协程中获取图片 uri,并展示到适配器上
private fun initData() { lifecycleScope.launch(Dispatchers.IO) { datas.addAll(getGallaryData()) withContext(Dispatchers.Main) { recycle.adapter?.notifyDataSetChanged() } } } 复制代码
- 效果
关于android11版本MANAGE_EXTERNAL_STORAGE权限
android10
的版本是不能访问所有文件的,可能google也意识到这是不合理的,所以在android11
的版本上重新支持了所有文件的访问
在android11
的系统版本上,如果想扫描应用的所有文件,那么可以声明MANAGE_EXTERNAL_STORAGE
权限
MANAGE_EXTERNAL_STORAGE
权限需要使用Intent进行权限申请,会跳转到一个系统页面确认权限
需要说明的是:这种方式可以访问共享存储中的文件,但是不可以访问专属存储目录中的文件(Android/data)
如下方法可以判断是否拥有MANAGE_EXTERNAL_STORAGE权限
Environment.isExternalStorageManager() 复制代码
声明权限方式如下:
- manifest中声明权限
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/> 复制代码
- 申请权限代码
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { startActivity(Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)) } ```kt 3. 实现效果 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f9aa67860d724340b6ed82ab7091a364~tplv-k3u1fbpfcp-zoom-1.image) ### 其它 #### 权限方面 - 使用使用共享存储空间访问其它应用创建的应用需要申请存储权限,自己应用本次安装创建的文件不需要申请权限。 - 如果应用`target`版本大于 9,那么我们仅应该在<=9 的版本中进行权限申请,可以通过如下配置设置: maxSdkVersion ```kt <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> 复制代码
- 如果应用使用了存储兼容功能,那么仍然需要访问存储权限
存储兼容指的就是我们准备升级android10
但是暂时不想使用分区存储
开启使用存储兼容只需要在 manifest 中 application 标签声明如下配置即可:
android:requestLegacyExternalStorage="true" 复制代码
切换媒体文件待处理状态
如果应用操作可能非常耗时(例如写入文件),那么在我们操作文件期间应该避免让其他应用有处理文件的机会。我们可以通过将 ContentValue.put(MediaStore.Audio.Media.IS_PENDING, 1)
标记的值设为 1 来获取此独占访问权限。这样就只有我们的的应用可以操作该文件,直到我们的应用将 IS_PENDING 的值改回 0。
照片中的位置信息
相册中的照片可能会包含敏感信息,例如位置信息,这个信息默认是不允许用户进行查看的,如果想查看需要申请 ACCESS_MEDIA_LOCATION
权限