概念
存储访问框架---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 = 42; private static final int WRITE_REQUEST_CODE = 43; @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(":")[1]}; 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 > -1 ) { String path = cursor.getString( index ); Log.d( TAG, "onActivityResult path=" path ";id=" selectionArgs[0] ); } } 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:333 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:test_create.png
注:
1.这里只介绍了ACTION_CREATE_DOCUMENT和ACTION_OPEN_DOCUMENT使用,因为可以获取操作文档的Uri,更多操作可以查看文档或者自己尝试。
2.注意运行中的权限哦
官方文档:https://developer.android.google.cn/guide/topics/providers/document-provider