【Android Framework系列】第16章 存储访问框架 (SAF)

2023-11-06 18:51

本文主要是介绍【Android Framework系列】第16章 存储访问框架 (SAF),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1 概述

Android 4.4(API 级别 19)引入了存储访问框架 (Storage Access Framework)SAF让用户能够在其所有首选文档存储提供程序中方便地浏览并打开文档图像以及其他文件。 用户可以通过易用的标准 UI,以统一方式在所有应用和提供程序中浏览文件和访问最近使用的文件。
在这里插入图片描述
存储访问框架SAF包括以下内容:

  1. 文档提供程序 :ConentProvider的子类,允许存储服务显示其管理的文件。 文档提供程序作为 DocumentsProvider 类的子类实现。文档提供程序的架构基于传统文件层次结构。Android 平台包括若干内置文档提供程序,操作sd卡对应的为ExternalStorageProvider
  2. 客户端应用 :就是我们平时的app,它调用 ACTION_OPEN_DOCUMENT,ACTION_CREATE_DOCUMENT ,ACTION_OPEN_DOCUMENT_TREE这三种IntentAction,来实现打开,创建文档,以及打开文档树。
  3. 选取器 : 一种系统 UI,我们称为DocumentUi,允许用户访问所有满足客户端应用搜索条件的文档提供程序内的文档。这个DocumentUI无桌面图标和入口,只能通过上面的Intent访问。
    在SAF框架中,我们的app应用和DocumentProvider之间并不产生直接的交互,而是通过DocumentUi进行。

2 SAF框架的使用

上文已经讲过,SAF框架的使用是通过DocumentUI的选择器来间接进行的,没法直接进行文件的操作。
使用方法如下:

2.1 打开文件

private static final int READ_REQUEST_CODE = 42;
...public void performFileSearch() {Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);//过滤器只显示可以打开的结果intent.addCategory(Intent.CATEGORY_OPENABLE);//要搜索通过已安装的存储提供商提供的所有文档//intent.setType("*/*");startActivityForResult(intent, READ_REQUEST_CODE);}@Overridepublic void onActivityResult(int requestCode, int resultCode,Intent resultData) {//使用resultdata.getdata ( )提取该URIif (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {Uri uri = null;if (resultData != null) {uri = resultData.getData();Log.i(TAG, "Uri: " + uri.toString());showImage(uri);}}
}

返回Uri:

content://com.android.externalstorage.documents/document/primary%3ADCIM%2FCamera%2FIMG20190607162534.jpg

在这里插入图片描述

2.2 打开文件树

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, OPEN_TREE_CODE);private void handleTreeAction(Intent data){Uri treeUri = data.getData();//授予打开的文档树永久性的读写权限final int takeFlags = intent.getFlags()& (Intent.FLAG_GRANT_READ_URI_PERMISSION| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);getContentResolver().takePersistableUriPermission(uri, takeFlags);//使用DocumentFile构建一个根文档,之后的操作可以在该文档上进行mRoot = DocumentFile.fromTreeUri(this, treeUri);//显示结果toastshowToast(" open tree uri "+treeUri);
}

返回的Uri:

content://com.android.externalstorage.documents/tree/primary%3AColorOS

在这里插入图片描述

  1. 对于我们打开的文档树,系统会赋予我们对该文档树下所有文档的读写权限,因此我们可以自由的使用我们上面介绍的输入输出流或者文件的方式来进行读写,该授权会一直保留到用户重启设备。
  2. 但是有时候,我们需要能够永久性的访问这些文件的权限,而不是重启就需要重新授权,因此我们使用了takePersistableUriPermission方法来保留系统对我们的uri的授权,即使设备重启也不影响。
  3. 我们可能保存了应用最近访问的 URI,但它们可能不再有效 — 另一个应用可能已删除或修改了文档。 因此,应该调用 getContentResolver().takePersistableUriPermission() 以检查有无最新数据。
  4. 拿到了根目录的uri,我们就可用使用DocumentFile辅助类来方便的进行创建,删除文件等操作了。

2.3 创建文件 ACTION_CREATE_DOCUMENT

private void createDocument(){Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);//设置创建的文件是可打开的intent.addCategory(Intent.CATEGORY_OPENABLE);//设置创建的文件的minitype为文本类型intent.setType("text/*");//设置创建文件的名称,注意SAF中使用minitype而不是文件的后缀名来判断文件类型。intent.putExtra(Intent.EXTRA_TITLE, "123.txt");startActivityForResult(intent,CREATE_DOCUMENT_CODE);
}private void handleCreateDocumentAction(Intent data){if (data == null) {return;}BufferedWriter bw = null;try {OutputStream os = getContentResolver().openOutputStream(uri);bw = new BufferedWriter(new OutputStreamWriter(os));bw.write(" i am a text ");showToast(" create document succeed uri "+uri);} catch (IOException e) {e.printStackTrace();}finally {closeSafe(bw);}}

2.4 编辑文档

onActivityResult()中获取到Uri之后,就可以对这个uri进行操作:

    private void alterDocument(Uri uri) {try {ParcelFileDescriptor pfd = getContext().getContentResolver().openFileDescriptor(uri, "w");FileOutputStream fileOutputStream =new FileOutputStream(pfd.getFileDescriptor());fileOutputStream.write(("Overwritten by MyCloud at " + System.currentTimeMillis() + "\n").getBytes());// Let the document provider know you' re done by closing the stream.fileOutputStream.close()fileOutputStream.close();pfd.close();} catch (IOException e) {e.printStackTrace();}}

2.5 删除文档

如果您获得了文档的 URI,并且文档的 Document.COLUMN_FLAGSSUPPORTS_DELETE,便可以删除该文档。例如:

DocumentsContract.deleteDocument(getContentResolver(), uri);

2.6 DocumentFile类的使用

DocumentFilegoogle为了方便大家使用SAF进行文件操作,而推出的帮助类。它的apijavaFile类比较接近,更符合一般用户的习惯,且内部实质都是使用了DocumentsContact类的方法来对文件进行操作。也就是说,我们也可以完全不使用DocumentFile而是使用DocumentsContact来完成SAF框架提供的文件操作,DocumentFile提供了三个静态工厂方法来创建自身。

fromSingleUri,该方法需要传入一个SAF返回的指向单个文件的uri,我们的ACTION_OPEN_DOCUMENT,ACTION_CREATE_DOCUMENT返回的uri就是该类型,其对应的实现类为ingleDocumentFile,代表的是单个的文件。
fromTreeUri,该方法传入指向文件夹的uri,我们的ACTION_OPEN_TREE返回的就是该类型,其对应的实现类为TreeDocumentFile,代表的是一个文件夹。
fromFile,该方法传入普通的File类,是对file类的一个模拟。

DocumentFile的方法总结如下:
在这里插入图片描述

3 SAF框架原理

3.1 SAF框架的类关系图如下所示:

在这里插入图片描述

由类关系图可以看出,DocumentFile工具类最终是通过DocumentsContract来实现操作的,而DocumentsContract最终操作的ProviderDocumentsProviderDocumentsProvider有三类:

ExternalStorageProvider是外置SD卡对应的ProviderDownloadStorageProvider是下载对应的Provider

ExternalStorageProvider:com.android.externalstorage.documents
DownloadStorageProvider:com.android.providers.downloads.documents
MediaDocumentProvider:com.android.providers.media.documents

下面具体分析下创建,修改,删除文件的流程
可以看出DocumentFile辅助类最终也是通过DocumentsContract来操作DocumentsProvider
在这里插入图片描述

下面看下跳到选择PickerUI的流程:
PickerUI最终也调到了DocumentsContract中。
在这里插入图片描述

3.2 DocumentProvider中的文档组织形式

在文档提供程序内,数据结构采用传统的文件层次结构,如下图所示:
在这里插入图片描述

  1. 每个DocumentProvider都可能有1个或多个做为文档结构树的Root根目录,每个根目录都有唯一的COLUMN_ROOT_ID,并且指向该根目录下表示内容的文档。
  2. 每个根目录下都有一个文档,该文档指向1到n个文档,而其中的每个文档又可以指向1到N个文档,从而形成树形的文档结构。
  3. 每个Document都会有唯一的COLUMN_DOCUMENT_ID用以引用它们,文档id具有唯一性,并且一旦发放就不得更改,因为它们用于所有设备重启过程中的永久性 URI 授权。
  4. 文档可以是可打开的文件(具有特定 MIME 类型)或包含附加文档的目录(具有 MIME_TYPE_DIR MIME 类型)。
  5. 每个文档都可以具有不同的功能,如 COLUMN_FLAGS 所述。例如,FLAG_SUPPORTS_WRITE、FLAG_SUPPORTS_DELETE 和 FLAG_SUPPORTS_THUMBNAIL。多个目录中可以包含相同的 COLUMN_DOCUMENT_ID。
    Document:
    在这里插入图片描述

3.3 自定义DocumentProvider

如果你希望自己应用的数据也能在documentsui中打开,你就需要写一个自己的document provider。(如果只是普通的文件操作,则不需要这么定义)
1)首先需要在Manifest中声明自定义的provider
在这里插入图片描述

2)实现DocumentProvider的基本接口
在这里插入图片描述
在这里插入图片描述

4 SAF框架总结

1. SAF框架,并不是直接与与DocumentProvider直接打交道,而是通过DocumentUI来间接操作。
2. 无论是通过Intent的方式,还是通过辅助类DocumentFile来进行文件操作,都需要获取uri,这个uri只能通过DocumentUI来返回,所以不是很方便。如果能接受通过DocumentUI来交互的,用SAF框架基本可以替代原有的文件操作方法

本章节大概了解SAF框架,我们下一章将对Android Q的沙箱模式(Scoped Storage)进行介绍

这篇关于【Android Framework系列】第16章 存储访问框架 (SAF)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/358424

相关文章

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

Android里面的Service种类以及启动方式

《Android里面的Service种类以及启动方式》Android中的Service分为前台服务和后台服务,前台服务需要亮身份牌并显示通知,后台服务则有启动方式选择,包括startService和b... 目录一句话总结:一、Service 的两种类型:1. 前台服务(必须亮身份牌)2. 后台服务(偷偷干

修改若依框架Token的过期时间问题

《修改若依框架Token的过期时间问题》本文介绍了如何修改若依框架中Token的过期时间,通过修改`application.yml`文件中的配置来实现,默认单位为分钟,希望此经验对大家有所帮助,也欢迎... 目录修改若依框架Token的过期时间修改Token的过期时间关闭Token的过期时js间总结修改若依

本地搭建DeepSeek-R1、WebUI的完整过程及访问

《本地搭建DeepSeek-R1、WebUI的完整过程及访问》:本文主要介绍本地搭建DeepSeek-R1、WebUI的完整过程及访问的相关资料,DeepSeek-R1是一个开源的人工智能平台,主... 目录背景       搭建准备基础概念搭建过程访问对话测试总结背景       最近几年,人工智能技术

Ollama整合open-webui的步骤及访问

《Ollama整合open-webui的步骤及访问》:本文主要介绍如何通过源码方式安装OpenWebUI,并详细说明了安装步骤、环境要求以及第一次使用时的账号注册和模型选择过程,需要的朋友可以参考... 目录安装环境要求步骤访问选择PjrIUE模型开始对话总结 安装官方安装地址:https://docs.

Redis存储的列表分页和检索的实现方法

《Redis存储的列表分页和检索的实现方法》在Redis中,列表(List)是一种有序的数据结构,通常用于存储一系列元素,由于列表是有序的,可以通过索引来访问元素,因此可以很方便地实现分页和检索功能,... 目录一、Redis 列表的基本操作二、分页实现三、检索实现3.1 方法 1:客户端过滤3.2 方法

Android kotlin语言实现删除文件的解决方案

《Androidkotlin语言实现删除文件的解决方案》:本文主要介绍Androidkotlin语言实现删除文件的解决方案,在项目开发过程中,尤其是需要跨平台协作的项目,那么删除用户指定的文件的... 目录一、前言二、适用环境三、模板内容1.权限申请2.Activity中的模板一、前言在项目开发过程中,尤

解读静态资源访问static-locations和static-path-pattern

《解读静态资源访问static-locations和static-path-pattern》本文主要介绍了SpringBoot中静态资源的配置和访问方式,包括静态资源的默认前缀、默认地址、目录结构、访... 目录静态资源访问static-locations和static-path-pattern静态资源配置

C++中使用vector存储并遍历数据的基本步骤

《C++中使用vector存储并遍历数据的基本步骤》C++标准模板库(STL)提供了多种容器类型,包括顺序容器、关联容器、无序关联容器和容器适配器,每种容器都有其特定的用途和特性,:本文主要介绍C... 目录(1)容器及简要描述‌php顺序容器‌‌关联容器‌‌无序关联容器‌(基于哈希表):‌容器适配器‌:(

Java访问修饰符public、private、protected及默认访问权限详解

《Java访问修饰符public、private、protected及默认访问权限详解》:本文主要介绍Java访问修饰符public、private、protected及默认访问权限的相关资料,每... 目录前言1. public 访问修饰符特点:示例:适用场景:2. private 访问修饰符特点:示例: