Android_存储访问框架SAF
概念
存储访问框架---Storage Access Framework (SAF),这是在Android4.4(API level 19)之后引入的。
借助 SAF,用户可轻松在其所有首选文档存储提供程序中浏览并打开文档、图像及其他文件。用户可通过易用的标准界面,以统一方式在所有应用和提供程序中浏览文件,以及访问最近使用的文件。
云存储服务或本地存储服务可实现封装其服务的 DocumentsProvider,进而参与此生态系统。只需几行代码,便可将需要访问提供程序文档的客户端应用与 SAF 进行集成。
SAF 包含以下3部分内容:
文档提供程序(Document provider):一个Content Provider, 以 DocumentsProvider 类的子类形式实现。文档提供程序的架构基于传统的文件层次结构,但其实际的数据存储方式由您决定。Android 平台包含若干内置文档提供程序,如 Downloads、Images 和 Videos。文档提供程序 可让存储服务(如 Google Drive)显示其管理的文件。
客户端应用(Client app) :一种自定义应用,它会调用 ACTION_OPEN_DOCUMENT 和/或 ACTION_CREATE_DOCUMENT Intent 并接收文档提供程序返回的文件。
选择器(Picker) :一种系统界面,可让用户访问所有满足客户端应用搜索条件的文档提供程序内的文档。
注:在控制流部分和最后的编写客户端应用的例子中 有更清晰明确的介绍。控制流中的图就包含了这3个内容。
控制流

如图,SAF包含3个部分,文档提供程序、客户端应用和选择器。上图左侧是照片应用(客户端应用),中间是选择器,右侧是注册的提供程序。
当应用(photo app)启动Intent ACTION_OPEN_DOCUMENT 或 ACTION_CREATE_DOCUMENT后,选择器会前往每个已注册的提供程序 并显示匹配的Root目录给用户。选择器为用户提供了标准的文档访问界面(即使底层文档提供程序与其差异比较大)。
如下图就是一个选择器,该图还显示可供客户端应用使用的所有根目录。

文档提供程序
SAF 所围绕的Content Provider是 DocumentsProvider 类的一个子类。在文档提供程序内,数据结构采用传统的文件层次结构:

文档提供程序数据模型。Root节点指向单个文档,然后引出整个结构树。
注意:
1.每个文档提供程序有一个或多个Root节点(引出整个文档结构树的起点),且每个Root节点有唯一的COLUMN_ROOT_ID(DocumentsContract.Root)。Root设计是动态的,用以支持多账号、Usb存储或用户注销登录等。
2.Root下只有一个文档,这个文档后指向1~N个文档,之后的同样指向1~N个文档。
3.每个存储 后端后会有一个唯一的COLUMN_DOCUMENT_ID(DocumentsContract.Document),用来引用这个文档或目录。
4.文档可以是可打开的文件(具有特定的 MIME类型)或包含附加文档的目录(具有 MIME_TYPE_DIR MIME 类型)。
5.如 COLUMN_FLAGS 所描述,每个文档可拥有不同功能。例如,FLAG_SUPPORTS_WRITE、FLAG_SUPPORTS_DELETE 和 FLAG_SUPPORTS_THUMBNAIL。多个目录中可包含相同的 COLUMN_DOCUMENT_ID。
编写客户端应用
在 Android 4.3 及更低版本中,如果您想让应用从其他应用中检索文件,则该应用必须调用 ACTION_PICK 或 ACTION_GET_CONTENT 等 Intent。然后,用户必须选择一个要从中选取文件的应用,并且所选应用必须提供用户界面,以便用户浏览和选取可用文件。
在 Android 4.4 及更高版本中,您还可选择使用 ACTION_OPEN_DOCUMENT Intent,此 Intent 会显示由系统控制的选择器界面,以便用户浏览其他应用提供的所有文件。借助此界面,用户便可从任何受支持的应用中选取文件。
ACTION_OPEN_DOCUMENT 并非用于代替 ACTION_GET_CONTENT。您应根据应用需求选择所使用的 Intent:
如果您只想让应用读取/导入数据,请使用 ACTION_GET_CONTENT。使用此方法时,应用会导入数据(如图片文件)的副本。
如果您想让应用获得对文档提供程序所拥有文档的长期、持续性访问权限,请使用 ACTION_OPEN_DOCUMENT。例如,照片编辑应用可让用户编辑存储在文档提供程序中的图像。
先看下下面代码:
public class MainActivity extends Activity {
private static final String TAG = "flx_saf";
private static final int READ_REQUEST_CODE = ;
private static final int WRITE_REQUEST_CODE = ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate( savedInstanceState );
// createFile();
// fileSearch();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (data == null || resultCode != Activity.RESULT_OK) return;
if (requestCode == READ_REQUEST_CODE) {
Log.d( TAG, "READ_REQUEST_CODE uri : " + data.getData() );
getPathForSearch( data.getData() );
} else if(requestCode == WRITE_REQUEST_CODE) {
Log.d( TAG, "WRITE_REQUEST_CODE uri : " + data.getData() );
}
}
//only for Image Uri
private void getPathForSearch(Uri uri) {
String[] selectionArgs = new String[] {DocumentsContract.getDocumentId(uri).split(":")[]};
Cursor cursor = getContentResolver().query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
null, MediaStore.Images.Media._ID + "=?",
selectionArgs, null );
if ( null != cursor ) {
if ( cursor.moveToFirst() ) {
int index = cursor.getColumnIndex( MediaStore.Images.Media.DATA );
if ( index > - ) {
String path = cursor.getString( index );
Log.d( TAG, "onActivityResult path="+path+";id="+selectionArgs[] );
}
}
cursor.close();
}
}
protected void fileSearch() {
Intent intent = new Intent( Intent.ACTION_OPEN_DOCUMENT );
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
startActivityForResult(intent, READ_REQUEST_CODE);
}
protected void createFile() {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
intent.putExtra(Intent.EXTRA_TITLE, "test_create.png");
startActivityForResult(intent, WRITE_REQUEST_CODE);
}
}
- 当执行fileSearch() 时,使用了ACTION_OPEN_DOCUMENT,intent启动了选择器,调试手机中调用的选择器是com.android.documentsui,如图:

选择其中一个图片,在onActivityResult()可以对结果进行处理。这里提取了选择的文档的Uri,有了Uri就可以对文档进行更多操作,这里获取了下文档的路径。
2019-10-09 10:35:02.112 2099-2099/com.flx.testsaf D/flx_saf: READ_REQUEST_CODE uri : content://com.android.providers.media.documents/document/image%3A333
2019-10-09 10:35:02.145 2099-2099/com.flx.testsaf D/flx_saf: onActivityResult path=/storage/emulated/0/screenshot2.png;id=333
注:这里只是验证说明,getPathForSearch()这里的方法并未做兼容处理,这里的Uri是从Image文档提供程序中传入的Uri才有效,其他无法处理甚至报错。
- 当执行createFile(),使用ACTION_CREATE_DOCUMENT,启动选择器创建文档test_create.png。也可以通过onActivityResult()对结果进行处理,获取Uri进行更多操作。
不同文档提供程序 保存,得到的Uri是不同的,如下是分别保存在Download和SD卡根目录的Uri:
7266-7266/com.flx.testsaf D/flx_saf: WRITE_REQUEST_CODE uri : content://com.android.providers.downloads.documents/document/114
7266-7266/com.flx.testsaf D/flx_saf: WRITE_REQUEST_CODE uri : content://com.android.externalstorage.documents/document/primary%3Atest_create.png
注:
1.这里只介绍了ACTION_CREATE_DOCUMENT和ACTION_OPEN_DOCUMENT使用,因为可以获取操作文档的Uri,更多操作可以查看文档或者自己尝试。
2.注意运行中的权限哦
官方文档:https://developer.android.google.cn/guide/topics/providers/document-provider
Android_存储访问框架SAF的更多相关文章
- Android_存储之scoped storage&媒体文件
Scoped storage 文件存储介绍了内部存储和外部存储相关的内容.因为外部存储容易读写,所以在手机中经常看到很多“乱七八糟”的文件或文件夹,这些就是应用肆意创建的. Android Q(10) ...
- “Zhuang.Data”轻型数据库访问框架(一)开篇介绍
目录: “Zhuang.Data”轻型数据库访问框架(一)开篇介绍 “Zhuang.Data”轻型数据库访问框架(二)框架的入口DbAccessor对象 框架介绍 该框架主要用于数据库访问,封装了包括 ...
- H5本地存储详细使用教程(localStorage + JSON数据存储应用框架)
一.Web Storage教程 1.概述: 对于Web Storage来说,实际上是Cookies存储的进化版.如果了解Cookie的人几乎一看Web Storage就会用,如果你从来没用过没了解过C ...
- Android_存储之文件存储
前面几篇随笔 讲到的关于存储的,SharedPreferences.Room.数据库等 最终都是以文件形式 存储到手机上的(除特殊的存储于手机内存的:如Room可以创建内存数据库). 这些存储方式,A ...
- 架构从最简单的数据访问框架(ORM)到资源调度和治理中心(SOA)说起
随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进. 单一应用架构当网站流量很小时,只需一个应用,将 ...
- [开源].NET数据库访问框架Chloe.ORM
扯淡 13年毕业之际,进入第一家公司实习,接触了 EntityFramework,当时就觉得这东西太牛了,访问数据库都可以做得这么轻松.优雅!毕竟那时还年轻,没见过世面.工作之前为了拿个实习机会混个工 ...
- 分享自己的超轻量级高性能ORM数据访问框架Deft
Deft 简介 Deft是一个超轻量级高性能O/R mapping数据访问框架,简单易用,几分钟即可上手. Deft包含如下但不限于此的特点: 1.按照Transact-SQL的语法语义风格来设计,只 ...
- Android存储访问及目录
Android存储访问及目录 Android的外部存储 Android支持外部存储(case-insensitive filesystem with immutable POSIX permissio ...
- “Zhuang.Data”轻型数据库访问框架(二)框架的入口DbAccessor对象
目录: “Zhuang.Data”轻型数据库访问框架(一)开篇介绍 “Zhuang.Data”轻型数据库访问框架(二)框架的入口DbAccessor对象 先来看一段代码 DbAccessor dba ...
随机推荐
- 地表最强的MySQL安装一键式安装,信不信你下完我就给你装好!附各种Mysql安装失败的解决办法(什么你安装失败了?快来看这个)
这里还有数据库相关的优质文章:快戳我,快戳我
- CodeForces - 1058A. In Search of an Easy Problem
这题,全零是esay有1是hard,真难呀. #include<bits/stdc++.h> using namespace std; int main(){ int n,i,x,flag ...
- 题目分享M
题意: 其实就是找到AB+min(AC,BC)的最大值 分析:刚看到这道题,很容易往AB为树的直径方向上去想,C就可以遍历每个点,最后求出对于每个点为C时的最大值 那AB到底是不是树的直径或者为什么A ...
- 进程间通信之socketpair
socketpair是进程间通信的一种方式. API: ]); DEMO: #include <stdio.h> #include <stdlib.h> #include &l ...
- JavaScript 循环判断练习题
JavaScript 循环判断练习题 小明有一组水果("苹果","梨子","香蕉","葡萄","西瓜" ...
- LeetCode 572. 另一个树的子树 | Python
572. 另一个树的子树 题目来源:https://leetcode-cn.com/problems/subtree-of-another-tree 题目 给定两个非空二叉树 s 和 t,检验 s 中 ...
- LTE基站开局流程
1.全局参数配置 MOD ENODEB :修改基站 ADD CNOPERATOR: 添加运营商 ADD CNOPERATORTA:添加跟踪区(TA) 2.设备参数配置(机柜.机框.RRU.光纤链 ...
- BOM基础
BOM基础 打开窗口 window.open('about:blank','_blank') 第一个参数是打开哪一个口,第二个参数是在哪里打开窗口. 关闭窗口 window.close() windo ...
- Spring方法注入的使用与实现原理
一.前言 这几天为了更详细地了解Spring,我开始阅读Spring的官方文档.说实话,之前很少阅读官方文档,就算是读,也是读别人翻译好的.但是最近由于准备春招,需要了解很多知识点的细节,网上几乎 ...
- 0804_serial port
其实这个程序总的来说是有问题的 仿真图: MacroAndConst.h #ifndef _MACRO_AND_CONST_H_ #define _MACRO_AND_CONST_H_ typedef ...