概念

存储访问框架---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的更多相关文章

  1. Android_存储之scoped storage&媒体文件

    Scoped storage 文件存储介绍了内部存储和外部存储相关的内容.因为外部存储容易读写,所以在手机中经常看到很多“乱七八糟”的文件或文件夹,这些就是应用肆意创建的. Android Q(10) ...

  2. “Zhuang.Data”轻型数据库访问框架(一)开篇介绍

    目录: “Zhuang.Data”轻型数据库访问框架(一)开篇介绍 “Zhuang.Data”轻型数据库访问框架(二)框架的入口DbAccessor对象 框架介绍 该框架主要用于数据库访问,封装了包括 ...

  3. H5本地存储详细使用教程(localStorage + JSON数据存储应用框架)

    一.Web Storage教程 1.概述: 对于Web Storage来说,实际上是Cookies存储的进化版.如果了解Cookie的人几乎一看Web Storage就会用,如果你从来没用过没了解过C ...

  4. Android_存储之文件存储

    前面几篇随笔 讲到的关于存储的,SharedPreferences.Room.数据库等 最终都是以文件形式 存储到手机上的(除特殊的存储于手机内存的:如Room可以创建内存数据库). 这些存储方式,A ...

  5. 架构从最简单的数据访问框架(ORM)到资源调度和治理中心(SOA)说起

    随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进. 单一应用架构当网站流量很小时,只需一个应用,将 ...

  6. [开源].NET数据库访问框架Chloe.ORM

    扯淡 13年毕业之际,进入第一家公司实习,接触了 EntityFramework,当时就觉得这东西太牛了,访问数据库都可以做得这么轻松.优雅!毕竟那时还年轻,没见过世面.工作之前为了拿个实习机会混个工 ...

  7. 分享自己的超轻量级高性能ORM数据访问框架Deft

    Deft 简介 Deft是一个超轻量级高性能O/R mapping数据访问框架,简单易用,几分钟即可上手. Deft包含如下但不限于此的特点: 1.按照Transact-SQL的语法语义风格来设计,只 ...

  8. Android存储访问及目录

    Android存储访问及目录 Android的外部存储 Android支持外部存储(case-insensitive filesystem with immutable POSIX permissio ...

  9. “Zhuang.Data”轻型数据库访问框架(二)框架的入口DbAccessor对象

    目录: “Zhuang.Data”轻型数据库访问框架(一)开篇介绍 “Zhuang.Data”轻型数据库访问框架(二)框架的入口DbAccessor对象 先来看一段代码 DbAccessor dba ...

随机推荐

  1. andorid jar/库源码解析之Butterknife

    目录:andorid jar/库源码解析 Butterknife: 作用: 用于初始化界面控件,控件方法,通过注释进行绑定控件和控件方法 栗子: public class MainActivity e ...

  2. 精通awk系列文章

    精通awk系列文章 我录制了两个awk相关的视频教程: Awk经典实战案例精讲 精通awk精品课程:awk从入门到精通 1.安装新版本的gawk 2.本教程测试所用示例文件 3.铺垫知识:读取文件的几 ...

  3. Codeforces Round #632 (Div. 2)

    Codeforces Round #632 (Div. 2) 这一场打的好差呀,这几次艰难上的分全部掉回去了,感觉就像一夜回到了解放前. 说实话,就是被B卡到了,没看到只能从小的放到大的... Lit ...

  4. Android中限制输入框最大输入长度

    通常情况下只需要在布局文件中加入maxlength这一属性即可 <EditText android:inputType="text" android:singleLine=& ...

  5. u-boot: Not enough room for program headers, try linking with -N

    在编译u-boot的时候出现了以下错误: arm-linux-gnueabi-ld.bfd: u-boot: Not enough room for program headers, try link ...

  6. Luogu P5603 小C与桌游【贪心+拓扑排序】

    [Description]https://www.luogu.com.cn/problem/P5603 \(\;\) 题意可以简化为:一个不保证联通,n个点,m条边的DAG(有向无环图),构造一个拓扑 ...

  7. 【数论基础】素数判定和Miller Rabin算法

    判断正整数p是否是素数 方法一 朴素的判定   

  8. 6、保持会话(save)

    前言 为什么要保存会话呢?举个很简单的场景,你在上海测试某个功能接口的时候,发现了一个BUG,而开发这个接口的开发人员是北京的一家合作公司.你这时候给对方开发提bug, 如何显得专业一点,能让对方心服 ...

  9. Dynamics 365 联系人Contact的快速创建窗体,如何知道父窗体是哪个实体,通过window.top.parent.Xrm.Page.getUrl()可以知道父窗体的URL

    Dynamics 365 联系人Contact的快速创建窗体,如何知道父窗体是哪个实体?相信有人会遇到过这种头疼的问题,我这里分享一种方式: 在contact快速创建窗体的onload时间执行如下代码 ...

  10. git :error: bad signature fatal: index file corrupt

    删除.git/index文件再执行: git reset 不行的话就执行: git read-tree  --empty https://stackoverflow.com/questions/213 ...