概念

存储访问框架---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。例如,照片编辑应用可让用户编辑存储在文档提供程序中的图像。

先看下下面代码:

  1. public class MainActivity extends Activity {
  2.  
  3. private static final String TAG = "flx_saf";
  4. private static final int READ_REQUEST_CODE = ;
  5. private static final int WRITE_REQUEST_CODE = ;
  6.  
  7. @Override
  8. protected void onCreate(Bundle savedInstanceState) {
  9. super.onCreate( savedInstanceState );
  10. // createFile();
  11. // fileSearch();
  12. }
  13.  
  14. @Override
  15. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  16. if (data == null || resultCode != Activity.RESULT_OK) return;
  17. if (requestCode == READ_REQUEST_CODE) {
  18. Log.d( TAG, "READ_REQUEST_CODE uri : " + data.getData() );
  19. getPathForSearch( data.getData() );
  20. } else if(requestCode == WRITE_REQUEST_CODE) {
  21. Log.d( TAG, "WRITE_REQUEST_CODE uri : " + data.getData() );
  22. }
  23. }

  24.    //only for Image Uri
  25. private void getPathForSearch(Uri uri) {
  26. String[] selectionArgs = new String[] {DocumentsContract.getDocumentId(uri).split(":")[]};
  27. Cursor cursor = getContentResolver().query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
  28. null, MediaStore.Images.Media._ID + "=?",
  29. selectionArgs, null );
  30. if ( null != cursor ) {
  31. if ( cursor.moveToFirst() ) {
  32. int index = cursor.getColumnIndex( MediaStore.Images.Media.DATA );
  33. if ( index > - ) {
  34. String path = cursor.getString( index );
  35. Log.d( TAG, "onActivityResult path="+path+";id="+selectionArgs[] );
  36. }
  37. }
  38. cursor.close();
  39. }
  40. }
  41.  
  42. protected void fileSearch() {
  43. Intent intent = new Intent( Intent.ACTION_OPEN_DOCUMENT );
  44. intent.addCategory(Intent.CATEGORY_OPENABLE);
  45. intent.setType("image/*");
  46. startActivityForResult(intent, READ_REQUEST_CODE);
  47. }
  48.  
  49. protected void createFile() {
  50. Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
  51. intent.addCategory(Intent.CATEGORY_OPENABLE);
  52. intent.setType("image/*");
  53. intent.putExtra(Intent.EXTRA_TITLE, "test_create.png");
  54. startActivityForResult(intent, WRITE_REQUEST_CODE);
  55. }
  56. }
  • 当执行fileSearch() 时,使用了ACTION_OPEN_DOCUMENT,intent启动了选择器,调试手机中调用的选择器是com.android.documentsui,如图:

选择其中一个图片,在onActivityResult()可以对结果进行处理。这里提取了选择的文档的Uri,有了Uri就可以对文档进行更多操作,这里获取了下文档的路径。

  1. 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
  2. 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:

  1. 7266-7266/com.flx.testsaf D/flx_saf: WRITE_REQUEST_CODE uri : content://com.android.providers.downloads.documents/document/114
  2. 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. CodeForces - 1245 B - Restricted RPS(贪心)

    Codeforces Round #597 (Div. 2) Let nn be a positive integer. Let a,b,ca,b,c be nonnegative integers ...

  2. linux下编译boost的多线程程序

    linux下面用boost库进行多线程编程,一开始总是编译不成功,花了好多的时间. 下面是一段小示例代码: //start from the very beginning,and to create ...

  3. weak_ptr

    #include <iostream> #include <memory> using namespace std; int main(int argc, char **arg ...

  4. 怎么成为一名WEB前端开发工程师

       对于刚开始学的人来说,web 就是HTML+CSS+JavaScript其实我们是可以这样理解的.web工程师负责或参与Web产品的页面开发,包含PC端.移动APP内嵌移动端.微信小程序.web ...

  5. T - zxa and leaf HDU - 5682 二分+dfs

    T - zxa and leaf HDU - 5682 题目大意是:给你一颗树,这棵树有些节点已经设置了它的美丽值,然后剩下一些节点需要我们设置美丽值. 一条边的丑陋程度等于被定义为由这个边缘连接的两 ...

  6. python gdal 写GeoTiff文件

    1.gdal数据类型 (1)GDT_Byte(int8)  (2)GDT_UInt16 (3)GDT_Int16  (4)GDT_UInt32  (5)GDT_Int32  (6)GDT_Float3 ...

  7. STM32 时钟树配置快速入门

    layout: post tags: [STM32] comments: true 文章目录 layout: post tags: [STM32] comments: true 为什么要了解时钟树? ...

  8. 如何将项目发布到npm仓库

    有时候,我们希望将项目里的模块提升为公共模块,以便其他项目也能使用.在前端可以将模块发布到npm仓库,这样所有项目都可以通过 npm install youProject 使用模块了. 这个过程很简单 ...

  9. Algorithms - Quicksort - 快速排序算法

    相关概念 快速排序法 Quicksort 也是一个分治思想的算法. 对一个子数组 A[p: r] 进行快速排序的三步分治过程: 1, 分解. 将数组 A[p : r] 被划分为两个子数组(可能为空) ...

  10. CF#633 D. Edge Weight Assignment

    D. Edge Weight Assignment 题意 给出一个n个节点的树,现在要为边赋权值,使得任意两个叶子节点之间的路径权值异或和为0,问最多,最少有多少个不同的权值. 题解 最大值: 两个叶 ...