Storage Access Framework(存储访问框架)
Android 4.4(API 19 级)引入了存储访问框架 (SAF)。SAF 让用户能够在其所有首选 DocumentsProvider中方便地浏览并打开文档、图像以及其他文件。 用户可以通过易用的标准 UI,以统一方式在所有应用和提供程序中浏览文件和访问最近使用的文件。
云存储服务或本地存储服务可以通过实现封装其服务的 DocumentsProvider 参与此生态系统。只需几行代码,便可将需要访问提供程序文档的客户端应用与 SAF 集成。
SAF 包括以下内容:
DocumentsProvider— 一种ContentProivder,允许存储服务(如 Google 云端硬盘)显示其管理的文件。文档提供程序作为 DocumentsProvider 类的子类实现。文档提供程序的架构基于传统文件层次结构,但其实际数据存储方式由您决定。Android 平台包括若干内置文档提供程序,如 Downloads、Images 和 Videos;
客户端应用— 一种自定义应用,它调用 ACTION_OPEN_DOCUMENT 和/或 ACTION_CREATE_DOCUMENT Intent 并接收文档提供程序返回的文件;
选取器— 一种系统 UI,允许用户访问所有满足客户端应用搜索条件的文档提供程序内的文档。
SAF 提供的部分功能如下:
允许用户浏览所有文档提供程序而不仅仅是单个应用中的内容;
让您的应用获得对文档提供程序所拥有文档的长期、持久性访问权限。 用户可以通过此访问权限添加、编辑、保存和删除提供程序上的文件;
支持多个用户帐户和临时根目录,如只有在插入驱动器后才会出现的 USB 存储提供程序。
概览
SAF 围绕的ContentProvider的DocumentsProvider 类是一个子类。在文档提供程序内,数据结构采用传统的文件层次结构:
图 1. 文档提供程序数据模型。根目录指向单个文档,后者随即启动整个结构树的扇出。
请注意以下事项:
每个文档提供程序都会报告一个或多个作为探索文档结构树起点的“根目录”。每个根目录都有一个唯一的 COLUMN_ROOT_ID,并且指向表示该根目录下内容的文档(目录)。根目录采用动态设计,以支持多个帐户、临时 USB 存储设备或用户登录/注销等用例;
每个根目录下都有一个文档。该文档指向 1 至 N 个文档,而其中每个文档又可指向 1 至 N 个文档;
每个存储后端都会通过使用唯一的 COLUMN_DOCUMENT_ID 引用各个文件和目录来显示它们。文档 ID 必须具有唯一性,一旦发放便不得更改,因为它们用于所有设备重启过程中的永久性 URI 授权;
文档可以是可打开的文件(具有特定 MIME 类型)或包含附加文档的目录(具有 MIME_TYPE_DIR MIME 类型);
每个文档都可以具有不同的功能,如 COLUMN_FLAGS 所述。例如,FLAG_SUPPORTS_WRITE、FLAG_SUPPORTS_DELETE 和 FLAG_SUPPORTS_THUMBNAIL。多个目录中可以包含相同的 COLUMN_DOCUMENT_ID。
控制流
如前文所述,文档提供程序数据模型基于传统文件层次结构。 不过,只要可以通过 DocumentsProvider API 访问数据,您实际上可以按照自己喜好的方式存储数据。例如,您可以使用基于标记的云存储来存储数据。
图 2 中的示例展示的是照片应用如何利用 SAF 访问存储的数据:
图 2. 存储访问框架流
请注意以下事项:
在 SAF 中,提供程序和客户端并不直接交互。客户端请求与文件交互(即读取、编辑、创建或删除文件)的权限;
交互在应用(在本示例中为照片应用)触发 Intent ACTION_OPEN_DOCUMENT 或 ACTION_CREATE_DOCUMENT 后开始。
Intent 可能包括进一步细化条件的过滤器—例如,“为我提供所有 MIME 类型为‘图像’的可打开文件”;Intent 触发后,系统选取器将检索每个已注册的提供程序,并向用户显示匹配的内容根目录;
选取器会为用户提供一个标准的文档访问界面,但底层文档提供程序可能与其差异很大。 例如,图 2 显示了一个 Google 云端硬盘提供程序、一个 USB 提供程序和一个云提供程序。
图 3 显示了一个选取器,一位搜索图像的用户在其中选择了一个 Google 云端硬盘帐户:
图 3. 选取器
当用户选择 Google 云端硬盘时,系统会显示图像,如图 4 所示。从这时起,用户就可以通过提供程序和客户端应用支持的任何方式与它们进行交互。
图 4. 图像
编写客户端应用
对于 Android 4.3 及更低版本,如果您想让应用从其他应用中检索文件,它必须调用 ACTION_PICK 或 ACTION_GET_CONTENT 等 Intent。然后,用户必须选择一个要从中选取文件的应用,并且所选应用必须提供一个用户界面,以便用户浏览和选取可用文件。
对于 Android 4.4 及更高版本,您还可以选择使用 ACTION_OPEN_DOCUMENT Intent,后者会显示一个由系统控制的选取器 UI,用户可以通过它浏览其他应用提供的所有文件。用户只需通过这一个 UI 便可从任何受支持的应用中选取文件。
ACTION_OPEN_DOCUMENT 并非设计用于替代 ACTION_GET_CONTENT。应使用的 Intent 取决于应用的需要:
如果您只想让应用读取/导入数据,请使用 ACTION_GET_CONTENT。使用此方法时,应用会导入数据(如图像文件)的副本;
如果您想让应用获得对文档提供程序所拥有文档的长期、持久性访问权限,请使用 ACTION_OPEN_DOCUMENT。 例如,允许用户编辑存储在文档提供程序中的图像的照片编辑应用。
本节描述如何编写基于 ACTION_OPEN_DOCUMENT 和 ACTION_CREATE_DOCUMENT Intent 的客户端应用。
搜索文档
以下代码段使用 ACTION_OPEN_DOCUMENT 来搜索包含图像文件的文档提供程序:
private static final int READ_REQUEST_CODE = 42;
...
/**
* Fires an intent to spin up the "file chooser" UI and select an image.
*/
public void performFileSearch() {
// ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
// browser.
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
// Filter to only show results that can be "opened", such as a
// file (as opposed to a list of contacts or timezones)
intent.addCategory(Intent.CATEGORY_OPENABLE);
// Filter to show only images, using the image MIME data type.
// If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
// To search for all documents available via installed storage providers,
// it would be "*/*".
intent.setType("image/*");
startActivityForResult(intent, READ_REQUEST_CODE);
}
请注意以下事项:
当应用触发 ACTION_OPEN_DOCUMENT Intent 时,后者会启动一个选取器来显示所有匹配的文档提供程序
在 Intent 中添加类别 CATEGORY_OPENABLE 可对结果进行过滤,以仅显示可以打开的文档(如图像文件)
语句 intent.setType(“image/*”) 可做进一步过滤,以仅显示 MIME 数据类型为图像的文档
处理结果
用户在选取器中选择文档后,系统就会调用 onActivityResult()。指向所选文档的 URI 包含在 resultData 参数中。使用 getData() 提取 URI。获得 URI 后,即可使用它来检索用户想要的文档。例如:
@Override
public void onActivityResult(int requestCode, int resultCode,
Intent resultData) {
// The ACTION_OPEN_DOCUMENT intent was sent with the request code
// READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
// response to some other intent, and the code below shouldn't run at all.
if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
// The document selected by the user won't be returned in the intent.
// Instead, a URI to that document will be contained in the return intent
// provided to this method as a parameter.