【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 Mainline基础简介

《AndroidMainline基础简介》AndroidMainline是通过模块化更新Android核心组件的框架,可能提高安全性,本文给大家介绍AndroidMainline基础简介,感兴趣的朋... 目录关键要点什么是 android Mainline?Android Mainline 的工作原理关键

如何解决idea的Module:‘:app‘platform‘android-32‘not found.问题

《如何解决idea的Module:‘:app‘platform‘android-32‘notfound.问题》:本文主要介绍如何解决idea的Module:‘:app‘platform‘andr... 目录idea的Module:‘:app‘pwww.chinasem.cnlatform‘android-32

Android实现打开本地pdf文件的两种方式

《Android实现打开本地pdf文件的两种方式》在现代应用中,PDF格式因其跨平台、稳定性好、展示内容一致等特点,在Android平台上,如何高效地打开本地PDF文件,不仅关系到用户体验,也直接影响... 目录一、项目概述二、相关知识2.1 PDF文件基本概述2.2 android 文件访问与存储权限2.

Android Studio 配置国内镜像源的实现步骤

《AndroidStudio配置国内镜像源的实现步骤》本文主要介绍了AndroidStudio配置国内镜像源的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、修改 hosts,解决 SDK 下载失败的问题二、修改 gradle 地址,解决 gradle

在Android平台上实现消息推送功能

《在Android平台上实现消息推送功能》随着移动互联网应用的飞速发展,消息推送已成为移动应用中不可或缺的功能,在Android平台上,实现消息推送涉及到服务端的消息发送、客户端的消息接收、通知渠道(... 目录一、项目概述二、相关知识介绍2.1 消息推送的基本原理2.2 Firebase Cloud Me

Android中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(

C# WinForms存储过程操作数据库的实例讲解

《C#WinForms存储过程操作数据库的实例讲解》:本文主要介绍C#WinForms存储过程操作数据库的实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、存储过程基础二、C# 调用流程1. 数据库连接配置2. 执行存储过程(增删改)3. 查询数据三、事务处

Python Dash框架在数据可视化仪表板中的应用与实践记录

《PythonDash框架在数据可视化仪表板中的应用与实践记录》Python的PlotlyDash库提供了一种简便且强大的方式来构建和展示互动式数据仪表板,本篇文章将深入探讨如何使用Dash设计一... 目录python Dash框架在数据可视化仪表板中的应用与实践1. 什么是Plotly Dash?1.1

基于Flask框架添加多个AI模型的API并进行交互

《基于Flask框架添加多个AI模型的API并进行交互》:本文主要介绍如何基于Flask框架开发AI模型API管理系统,允许用户添加、删除不同AI模型的API密钥,感兴趣的可以了解下... 目录1. 概述2. 后端代码说明2.1 依赖库导入2.2 应用初始化2.3 API 存储字典2.4 路由函数2.5 应

Python GUI框架中的PyQt详解

《PythonGUI框架中的PyQt详解》PyQt是Python语言中最强大且广泛应用的GUI框架之一,基于Qt库的Python绑定实现,本文将深入解析PyQt的核心模块,并通过代码示例展示其应用场... 目录一、PyQt核心模块概览二、核心模块详解与示例1. QtCore - 核心基础模块2. QtWid