概念

存储访问框架---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. 一句话教你分清楚UML组合聚合和联系!

    组合:组合后的实体消失,则所有构成实体的部件都无意义,可以理解为不能独立存在 定义: 与聚合相比,组合描述的是这样的关联关系,部分离开整体后就没有实际意义了.所以我们说组合是一种很强的关联关系. 例子 ...

  2. P4168 蒲公英

    神仙分块,把减写成加调了半小时.. 不过这题也启示我们其实有的分块题要把多个块的信息拿到一起维护 以前做的都是每个块的信息单独维护 写的分块题还不太多,同时维护一个块的左右边界好像有点冗余,不过这样代 ...

  3. ASP.NET Core3.x 基础—注册服务(2)

    这篇文章介绍在ASP.NET Core中注册一下自己的服务. 首先创建一个Services文件夹.在文件夹里面创建一个接口 IClock,以及两个类ChinaClock.UtcClock.这两个类分别 ...

  4. Navicat12.1系列安装,破解以及破解navicat报错的解决方案

    由于上课的需要,我们必须自己下载并安装 Navicat Premium 12,虽然安装过程很简单,但是安装后的navicat只能试用,并没有永久激活,然而我还想永久使用,所以就各种百度,因为不断地遇到 ...

  5. P2762 太空飞行计划问题 网络流

    题目描述 W 教授正在为国家航天中心计划一系列的太空飞行.每次太空飞行可进行一系列商业性实验而获取利润.现已确定了一个可供选择的实验集合E={E1,E2,…,Em},和进行这些实验需要使用的全部仪器的 ...

  6. java线程池原理解析

    五一假期大雄看了一本<java并发编程艺术>,了解了线程池的基本工作流程,竟然发现线程池工作原理和互联网公司运作模式十分相似. 线程池处理流程 原理解析 互联网公司与线程池的关系 这里用一 ...

  7. Elasticsearch系列---Term Vector工具探查数据

    概要 本篇主要介绍一个Term Vector的概念和基本使用方法. term vector是什么? 每次有document数据插入时,elasticsearch除了对document进行正排.倒排索引 ...

  8. 武装你的WEBAPI-OData入门

    本文属于OData系列 目录(可能会有后续修改) 武装你的WEBAPI-OData入门 武装你的WEBAPI-OData便捷查询 武装你的WEBAPI-OData分页查询 武装你的WEBAPI-ODa ...

  9. FPGA自计数六位共阳极数码管动态显示2(调用task的方法)

    `timescale 1ns/1ps module adc_dis( clk , rst_n , sm_seg , sm_bit ); input clk;//50HZ input rst_n; :] ...

  10. FastDFS安装(mac)|文件存储方案

    目录 FastDFS安装(mac)|文件存储方案 1 FastDFS介绍 1.1 FastDFS架构 1.2 工作原理实例介绍 1.3 FastDFS上传和下载流程 1.4 FastDFS文件索引 2 ...