SAF 存储访问框架

  • 使用 SAF 创建和读取文件

  • 资料来源:

    https://developer.android.com/guide/topics/providers/document-provider#virtual

  • 更新

    1
    20.06.25 初始化

导语

android Q 在 beta 中引入了文件沙盒,android 应用不再拥有内部存储的无限读写权限,结果 beta3 版本不兼容应用太多,暂缓实行,不过 Android R 里面还是来了.

客观上文件沙盒对用户隐私保护很有用,但是 google 给开发者留的坑有点大,如果适配的成本能降低很多,也不会有这么大的反对声音.

SAF 框架在 android 4.4 就已经引入了,可能是 R 以后直接访问用户文件的唯一方式.这里仅仅以 NowakeLock 为例演示如何读取和创建文件.

概览

SAF 实际上是 ContentProvider 的应用.系统或用户应用有提供 DocumentsProvider 的实现,应用可以通过 SAF 得到 uri 进而访问文件.不论这个文件是在本地还是云端.

所以使用 SAF 的过程也类似使用 ContentProvider

  • 发送 Intent,系统内的 DocumentsProvider 的实现会响应
  • 选择一个文件,拿到 uri
  • 在 onActivityResult 内对 uri 操作.读/写/创建等.

读文件

读文件对应的 Intent 有几种

  • ACTION_GET_CONTENT: 可以运行在 4.4 以下版本,如果只是导入文件,不需要长期的性的权限.
  • ACTION_OPEN_DOCUMENT: 仅在 4.4 以上可用,但是可以提供长期的持久性的访问权限.

示例是 Nowakelock 导入 json 备份文件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private val readJson: Int = 42

private fun getJson() {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
}
startActivityForResult(intent, readJson)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
if (requestCode == readJson && resultCode == Activity.RESULT_OK) {
resultData?.data?.also { uri ->
//do something
}
}
}

写文件

创建文件过程

  • 发出 ACTION_CREATE_DOCUMENT 类型 Intent
  • 之后可以在 DocumentsProvider 的实现中选择保存位置,定义文件名,创建文件.
  • 在 onActivityResult 拿到 uri 再写入内容.

这里是 Nowakelock 创建备份文件的过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private val saveJson: Int = 43

private fun createFile(mimeType: String, fileName: String) {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = mimeType
putExtra(Intent.EXTRA_TITLE, fileName)
}

startActivityForResult(intent, saveJson)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
if (requestCode == saveJson && resultCode == Activity.RESULT_OK) {
resultData?.data?.also { uri ->
//do something
}
}
}