【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…...
虹科方案 | LIN/CAN总线汽车零部件测试方案
文章目录 摘要一、汽车零部件测试的重要性?二、虹科的测试仿真工具如何在汽车零部件测试展露头角?三、应用场景**应用场景1:方向盘开关的功能测试****应用场景2:各类型电机的控制测试****应用场景3:RGB氛围灯的功能测试…...
[solidity]合约调用合约
先写一个简单的合约将其部署,部署后的合约地址为:0xd9145CCE52D386f254917e481eB44e9943F39138 // SPDX-License-Identifier: MIT pragma solidity ^0.8.0;contract A{string myname;function setName(string memory _name) public{myname_name;}functi…...
Vulnhub系列靶机---JANGOW 1.0.1
文章目录 网卡配置信息收集主机发现端口扫描 漏洞利用反弹Shell提权 靶机文档:JANGOW 1.0.1 下载地址:Download (Mirror) 难易程度:. 网卡配置 水果味儿 信息收集 主机发现 端口扫描 访问80端口 点击site目录 点击页面上方的一个选项&…...
肖sir__项目环境之全流程__005
一、测试流程(h模型) 1、需求文档(产品) 需求文档(软件需求规格说明书srs) (1)如何分析需求 a、显示需求(主流程、功能,业务) b、隐性需求&#x…...
搜狗输入法下键翻页
搜狗输入法下键翻页 从官网下载 搜狗输入法智慧版关闭超级候选关闭候选...
C#多线程
一、多线程实现方式 1. 使⽤Thread类: System.Threading.Thread 类是C#中最基本的多线程编程⼯具。 2. 使⽤ThreadPool: 线程池是⼀个管理和重⽤线程的机制,它可以在应⽤程序中创建和使 ⽤多个线程,⽽⽆需显式地管理线程的…...
Unity 编辑器常用方法
unity编辑器开发 脚本注解1. RuntimeInitializeOnLoadMethod2. ColorUsage3. Header4. SerializeField5. HideInInspector6. Space7. Range8. Multiline9.[RequireComponent(typeof())]10.HelpURL 右键菜单注解1. CreateAssetMenu - 针对ScriptableObject 菜单栏注解1. MenuIt…...
21 mysql ref 查询
前言 这里主要是 探究一下 explain $sql 中各个 type 诸如 const, ref, range, index, all 的查询的影响, 以及一个初步的效率的判断 这里会调试源码来看一下 各个类型的查询 需要 lookUp 的记录 以及 相关的差异 此系列文章建议从 mysql const 查询 开始看 测试表结构…...
启山智软/一款包含主流商城类型的一款电商中台系统100%开源
文章目录 介绍一、Smart Shop JAVA 微服务电商中台优势二、电商中台包含那些主流商城模式1.S2B2C供应链商城2.B2B2C多商户商城3.B2C单商户商城4.O2O外卖配送商城5.社区团购商城 6.演示地址总结 介绍 想要了解代码规范,学习商城解决方案,点击下方官网链接…...
【C语言】指针的进阶(四)—— 企业笔试题解析
笔试题1: int main() {int a[5] { 1, 2, 3, 4, 5 };int* ptr (int*)(&a 1);printf("%d,%d", *(a 1), *(ptr - 1));return 0; } 【答案】在x86环境下运行 【解析】 &a是取出整个数组的地址,&a就表示整个数组,因此…...
做网站从哪方面入门/成都seo排名
开门见山。 PMP是应聘项目管理、项目经理这些岗位的敲门砖,真正引进国内是在1999年,那时候大家对这个证书了解不多,一年也就百八十人报考,随着PMP越来越被市场认可,到目前国内认证人数已经近百万,做过工程…...
重庆市建设工程信息网招标文件/网站seo基础优化
欢迎和号主【前端点线面】进群盘算法,此外本号干货满满:14个门类(100篇原创)内容(又干又硬)、《前端百题斩》pdf(助力薪资double)、20篇思维导图(知识系统化、记忆简单化…...
做美食网站需求分析报告/网站建设网络营销
1、 boot分区 大小:一般300Mb左右 作用:引导分区,包含了系统启动的必要内核文件,即使根分区损坏也能正常引导启动,一般这些文件所占空间在200M以内。 分区建议:分区的时候可选100M-500M之间,如果空间足够…...
网站平台怎么做的好处/营销策划公司靠谱吗
BAD_ACCESS 在什么情况下出现 BAD_ACCESS 报错属于内存访问错误,会导致程序崩溃,错误的原因是访问了野指针(悬挂指针)。野指针指的是本来指针指向的对象已经释放了,但指向该对象的指针没有置 nil,指针指向随机的未知的内存&#…...
网站里面内外链接如何做/2022知名品牌营销案例100例
实验十四 课程学习总结 项目内容这个作业属于哪个课程(https://www.cnblogs.com/nwnu-daizh/)这个作业的要求在哪里(https://www.cnblogs.com/nwnu-daizh/p/11093584.html)课程学习目标掌握软件项目评审会流程;反思总结课程学习内容。1.结合本学期课程学习内容&…...
做网站制作较好的公司/成都疫情最新消息
一.简介 0. 页面的生命周期。 1. WebForm后台页面类继承于Page类,Page类实现了IHttpHandler接口。 2. 前台页面类继承于后台页面类。 3. 先调用PageLoad方法,再调用Render方法生成html代码。 二. 加密安全 互联网没有绝对的安全,登…...