【Android Framework系列】第16章 存储访问框架 (SAF)
1 概述
Android 4.4(API 级别 19)
引入了存储访问框架 (Storage Access Framework)
。SAF
让用户能够在其所有首选文档存储提供程序中方便地浏览并打开文档
、图像
以及其他文件
。 用户可以通过易用的标准 UI
,以统一方式在所有应用和提供程序中浏览文件和访问最近使用的文件。
存储访问框架SAF
包括以下内容:
- 文档提供程序 :
ConentProvider
的子类,允许存储服务显示其管理的文件。 文档提供程序作为DocumentsProvider
类的子类实现。文档提供程序的架构基于传统文件层次结构。Android
平台包括若干内置文档提供程序,操作sd卡
对应的为ExternalStorageProvider
。 - 客户端应用 :就是我们平时的app,它调用
ACTION_OPEN_DOCUMENT
,ACTION_CREATE_DOCUMENT
,ACTION_OPEN_DOCUMENT_TREE
这三种Intent
的Action
,来实现打开,创建文档,以及打开文档树。 - 选取器 : 一种系统 UI,我们称为
DocumentUi
,允许用户访问所有满足客户端应用搜索条件的文档提供程序内的文档。这个DocumentUI
无桌面图标和入口,只能通过上面的Intent
访问。
在SAF框架中,我们的app应用和DocumentProvider之间并不产生直接的交互,而是通过DocumentUi进行。
2 SAF框架的使用
上文已经讲过,SAF
框架的使用是通过DocumentUI
的选择器来间接进行的,没法直接进行文件的操作。
使用方法如下:
2.1 打开文件
private static final int READ_REQUEST_CODE = 42;
...public void performFileSearch() {Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);//过滤器只显示可以打开的结果intent.addCategory(Intent.CATEGORY_OPENABLE);//要搜索通过已安装的存储提供商提供的所有文档//intent.setType("*/*");startActivityForResult(intent, READ_REQUEST_CODE);}@Overridepublic void onActivityResult(int requestCode, int resultCode,Intent resultData) {//使用resultdata.getdata ( )提取该URIif (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {Uri uri = null;if (resultData != null) {uri = resultData.getData();Log.i(TAG, "Uri: " + uri.toString());showImage(uri);}}
}
返回Uri:
content://com.android.externalstorage.documents/document/primary%3ADCIM%2FCamera%2FIMG20190607162534.jpg
2.2 打开文件树
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, OPEN_TREE_CODE);private void handleTreeAction(Intent data){Uri treeUri = data.getData();//授予打开的文档树永久性的读写权限final int takeFlags = intent.getFlags()& (Intent.FLAG_GRANT_READ_URI_PERMISSION| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);getContentResolver().takePersistableUriPermission(uri, takeFlags);//使用DocumentFile构建一个根文档,之后的操作可以在该文档上进行mRoot = DocumentFile.fromTreeUri(this, treeUri);//显示结果toastshowToast(" open tree uri "+treeUri);
}
返回的Uri:
content://com.android.externalstorage.documents/tree/primary%3AColorOS
- 对于我们打开的文档树,系统会赋予我们对该文档树下所有文档的读写权限,因此我们可以自由的使用我们上面介绍的输入输出流或者文件的方式来进行读写,该授权会一直保留到用户重启设备。
- 但是有时候,我们需要能够永久性的访问这些文件的权限,而不是重启就需要重新授权,因此我们使用了takePersistableUriPermission方法来保留系统对我们的uri的授权,即使设备重启也不影响。
- 我们可能保存了应用最近访问的 URI,但它们可能不再有效 — 另一个应用可能已删除或修改了文档。 因此,应该调用 getContentResolver().takePersistableUriPermission() 以检查有无最新数据。
- 拿到了根目录的uri,我们就可用使用DocumentFile辅助类来方便的进行创建,删除文件等操作了。
2.3 创建文件 ACTION_CREATE_DOCUMENT
private void createDocument(){Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);//设置创建的文件是可打开的intent.addCategory(Intent.CATEGORY_OPENABLE);//设置创建的文件的minitype为文本类型intent.setType("text/*");//设置创建文件的名称,注意SAF中使用minitype而不是文件的后缀名来判断文件类型。intent.putExtra(Intent.EXTRA_TITLE, "123.txt");startActivityForResult(intent,CREATE_DOCUMENT_CODE);
}private void handleCreateDocumentAction(Intent data){if (data == null) {return;}BufferedWriter bw = null;try {OutputStream os = getContentResolver().openOutputStream(uri);bw = new BufferedWriter(new OutputStreamWriter(os));bw.write(" i am a text ");showToast(" create document succeed uri "+uri);} catch (IOException e) {e.printStackTrace();}finally {closeSafe(bw);}}
2.4 编辑文档
在onActivityResult()
中获取到Uri之后,就可以对这个uri进行操作:
private void alterDocument(Uri uri) {try {ParcelFileDescriptor pfd = getContext().getContentResolver().openFileDescriptor(uri, "w");FileOutputStream fileOutputStream =new FileOutputStream(pfd.getFileDescriptor());fileOutputStream.write(("Overwritten by MyCloud at " + System.currentTimeMillis() + "\n").getBytes());// Let the document provider know you' re done by closing the stream.fileOutputStream.close()fileOutputStream.close();pfd.close();} catch (IOException e) {e.printStackTrace();}}
2.5 删除文档
如果您获得了文档的 URI
,并且文档的 Document.COLUMN_FLAGS
包 SUPPORTS_DELETE
,便可以删除该文档。例如:
DocumentsContract.deleteDocument(getContentResolver(), uri);
2.6 DocumentFile类的使用
DocumentFile
是google
为了方便大家使用SAF
进行文件操作,而推出的帮助类。它的api
和java
的File
类比较接近,更符合一般用户的习惯,且内部实质都是使用了DocumentsContact
类的方法来对文件进行操作。也就是说,我们也可以完全不使用DocumentFile
而是使用DocumentsContact
来完成SAF
框架提供的文件操作,DocumentFile
提供了三个静态工厂方法来创建自身。
fromSingleUri
,该方法需要传入一个SAF
返回的指向单个文件的uri
,我们的ACTION_OPEN_DOCUMENT
,ACTION_CREATE_DOCUMENT
返回的uri就是该类型,其对应的实现类为ingleDocumentFile
,代表的是单个的文件。
fromTreeUri
,该方法传入指向文件夹的uri,我们的ACTION_OPEN_TREE
返回的就是该类型,其对应的实现类为TreeDocumentFile
,代表的是一个文件夹。
fromFile
,该方法传入普通的File
类,是对file
类的一个模拟。
DocumentFile
的方法总结如下:
3 SAF框架原理
3.1 SAF
框架的类关系图如下所示:
由类关系图可以看出,DocumentFile
工具类最终是通过DocumentsContract
来实现操作的,而DocumentsContract
最终操作的Provider
是DocumentsProvider
。DocumentsProvider
有三类:
ExternalStorageProvider
是外置SD卡
对应的Provider
,DownloadStorageProvider
是下载对应的Provider
。
ExternalStorageProvider
:com.android.externalstorage.documents
DownloadStorageProvider
:com.android.providers.downloads.documents
MediaDocumentProvider
:com.android.providers.media.documents
下面具体分析下创建,修改,删除文件的流程
可以看出DocumentFile
辅助类最终也是通过DocumentsContract
来操作DocumentsProvider
下面看下跳到选择PickerUI
的流程:
PickerUI
最终也调到了DocumentsContract
中。
3.2 DocumentProvider中的文档组织形式
在文档提供程序内,数据结构采用传统的文件层次结构,如下图所示:
- 每个DocumentProvider都可能有1个或多个做为文档结构树的Root根目录,每个根目录都有唯一的COLUMN_ROOT_ID,并且指向该根目录下表示内容的文档。
- 每个根目录下都有一个文档,该文档指向1到n个文档,而其中的每个文档又可以指向1到N个文档,从而形成树形的文档结构。
- 每个Document都会有唯一的COLUMN_DOCUMENT_ID用以引用它们,文档id具有唯一性,并且一旦发放就不得更改,因为它们用于所有设备重启过程中的永久性 URI 授权。
- 文档可以是可打开的文件(具有特定 MIME 类型)或包含附加文档的目录(具有 MIME_TYPE_DIR MIME 类型)。
- 每个文档都可以具有不同的功能,如 COLUMN_FLAGS 所述。例如,FLAG_SUPPORTS_WRITE、FLAG_SUPPORTS_DELETE 和 FLAG_SUPPORTS_THUMBNAIL。多个目录中可以包含相同的 COLUMN_DOCUMENT_ID。
Document:
3.3 自定义DocumentProvider
如果你希望自己应用的数据也能在documentsui
中打开,你就需要写一个自己的document provider
。(如果只是普通的文件操作,则不需要这么定义)
1)首先需要在Manifest中声明自定义的provider
:
2)实现DocumentProvider
的基本接口
4 SAF框架总结
1. SAF
框架,并不是直接与与DocumentProvider
直接打交道,而是通过DocumentUI
来间接操作。
2. 无论是通过Intent
的方式,还是通过辅助类DocumentFile
来进行文件操作,都需要获取uri
,这个uri
只能通过DocumentUI
来返回,所以不是很方便。如果能接受通过DocumentUI
来交互的,用SAF
框架基本可以替代原有的文件操作方法
本章节大概了解SAF框架
,我们下一章将对Android Q
的沙箱模式(Scoped Storage)
进行介绍
相关文章:

【Android Framework系列】第16章 存储访问框架 (SAF)
1 概述 Android 4.4(API 级别 19)引入了存储访问框架 (Storage Access Framework)。SAF让用户能够在其所有首选文档存储提供程序中方便地浏览并打开文档、图像以及其他文件。 用户可以通过易用的标准 UI,以统一方式在所有应用和提供程序中浏…...

Antdesign 4中让分页组件居中显示的方法
在Ant Design 4中分页组件默认是最右边显示的,而这个没有设置位置的属性的 解决办法: 在pagination的属性中增加: style: {textAlign: "center"} 在Ant Design 5中可以让pagination使用align: center来实现分页组件居中...

【笔记】ubuntu 20.04 + mongodb 4.4.14定时增量备份脚本
环境 ubuntu 20.04mongodb 4.4.14还没实际使用(20230922)后续到10月底如果有问题会修改 原理 只会在有新增数据时生成新的备份日期目录备份恢复时,如果恢复的数据库未删除,则会覆盖数据 准备 准备一个文件夹,用于…...
c++实现的一个定时器实例
/* * author: hjjdebug * date : 2023年 09月 23日 星期六 11:52:29 CST * description: 用std::thread 实现了一个定时器,深刻理解一下定时器是怎样工作的. * 参考Timer.h, Timer.cpp */ $ cat main.cpp #include "Timer.h" #include <unis…...

Python线程和进程
1、深度解析Python线程和进程 一篇文章带你深度解析Python线程和进程 - 知乎使用Python中的线程模块,能够同时运行程序的不同部分,并简化设计。如果你已经入门Python,并且想用线程来提升程序运行速度的话,希望这篇教程会对你有所帮…...
算法 寻找峰值-(二分查找+反向双指针)
牛客网: BM19 题目: 寻找数组峰值,可能多个返回任一个,每个值满足nums[i] ! nums[i 1] 思路: 双指针 left 0, right n-1, 相向而行,取中间位置mid, nums[mid]与nums[mid1]比较,如果nums[mid] < nums[mid1],说明…...

【数据结构】—交换排序之快速排序究极详解,手把手带你从简单的冒泡排序升级到排序的难点{快速排序}(含C语言实现)
食用指南:本文在有C基础的情况下食用更佳 🔥这就不得不推荐此专栏了:C语言 ♈️今日夜电波:靴の花火—ヨルシカ 0:28━━━━━━️💟──────── 5:03 …...

【c#-Nuget 包“在此源中不可用”】 Nuget package “Not available in this source“
标题c#-Nuget 包“在此源中不可用”…但 VS 仍然知道它吗? (c# - Nuget package “Not available in this source”… but VS still knows about it?) 背景: 今日从公司svn 上拉取很久很久以前的代码,拉取下来200报错,进一步发…...

【数据结构】二叉树之堆的实现
🔥博客主页:小王又困了 📚系列专栏:数据结构 🌟人之为学,不日近则日退 ❤️感谢大家点赞👍收藏⭐评论✍️ 目录 一、二叉树的顺序结构 📒1.1顺序存储 📒1.2堆的性质…...

电工-三极管输入输出特性曲线讲解
三极管特性曲线是反映三极管各电极电压和电流之间相互关系的曲线,是用来描述晶体三极管工作特性曲线,常用的特性曲线有输入特性曲线和输出特性曲线。这里以下图所示的共发射极电路来分析三极管的特性曲线。 输入特性曲线 该曲线表示当e极与c极之间的电…...

深入解析容器与虚拟化:技术、对比与生态
深入解析容器与虚拟化:技术、对比与生态 文章目录 深入解析容器与虚拟化:技术、对比与生态容器和虚拟化的基本概念和原理容器的定义和特点虚拟化的定义和特点 容器使用场景容器和虚拟机的对比虚拟化技术的四个特点容器实现虚拟化的原理常见容器引擎和容器…...
制作游戏demo的心得
制作这个游戏demo出来的心得 https://www.bilibili.com/video/BV1cF411m7Dh/ 制作游戏demo的心得 制作游戏demo,主要是为了表现自己的技术,那就一门心思想着如何提高表现力就行了,在整体的画面渲染风格方面或许没有什么可选择的,…...

Web Tour Server窗口闪现
1.打开该文件所在位置 2.右击选择编辑,在最后一行加上pause,保存后重新打开Server窗口 3.重新打开后,若出现以下情况: 以管理员身份打开cmd命令行,输入命令netstat -aon|findstr “1080”,查看1080端口占用…...

Linux下的基本指令
目录 01. ls 指令 02. pwd命令 03. cd 指令 04. touch指令 05.mkdir指令(重要): 06.rmdir指令 && rm 指令(重要): 07.man指令(重要): 08mv指令ÿ…...
随机数生成器代码HTML5
代码如下 <!DOCTYPE html> <html> <head> <title>随机数生成器</title> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <style> body { text-align: center; bac…...

正确理解redux Toolkits中createSlice的action.payload
使用redux Toolkits中的createSlice编写extraReducers经常看到使用action.payload来更新state状态值: 那么action.payload指的到底是什么? 让我们看看action的定义部分: 注意: action.payload不是上面ajax请求的返回内容&#x…...

YOLOv8快速复现 官网版本 ultralytics
YOLOV8环境安装教程.:https://www.bilibili.com/video/BV1dG4y1c7dH/ YOLOV8保姆级教学视频:https://www.bilibili.com/video/BV1qd4y1L7aX/ b站视频:https://www.bilibili.com/video/BV12p4y1c7UY/ 1 平台搭建YOLOv8 平台:https://www.a…...

Haproxy搭建 Web 群集实现负载均衡
目录 1 Haproxy 1.1 HAProxy的主要特性 1.2 HAProxy负载均衡策略 1.3 LVS、Nginx、HAproxy的区别 2 Haproxy搭建 Web 群集 2.1 haproxy 服务器部署 2.1.1 关闭防火墙 2.1.2 内核配置(实验环境可有可无) 2.1.3 安装 Haproxy 2.1.4 Haproxy服务…...

Tessy 5.0.4
Tessy 5.0.4 Linux 2692407267qq.com,更多内容请见http://user.qzone.qq.com/2692407267/...
mybatis-plus根据指定条件批量更新
1.service实现类中 比如我这里只针对UserEntity,在UserServiceImpl下(该实现类是继承了mybatis-plus的ServiceImpl的)新增如下代码: public boolean updateBatchByQueryWrapper(Collection<UserEntity> entityList, Funct…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...

【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...

React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...

linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...

九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...