内容简介:Android 4.4(API 级别 19)引入了Storage Access Framework存储访问框架 (SAF),SAF 让用户能够在其所有首选文档存储提供程序中方便地浏览并打开文档、图像以及其他文件。 用户可以通过易用的标准 UI,以统一方式在所有应用和提供程序中浏览文件和访问最近使用的文件。云存储服务或本地存储服务可以通过实现封装其服务的 DocumentsProvider 参与此生态系统。只需几行代码,便可将需要访问提供程序文档的客户端应用与 SAF 集成。SAF 包括以下内容:
Android 4.4(API 级别 19)引入了Storage Access Framework存储访问框架 (SAF),SAF 让用户能够在其所有首选文档存储提供程序中方便地浏览并打开文档、图像以及其他文件。 用户可以通过易用的标准 UI,以统一方式在所有应用和提供程序中浏览文件和访问最近使用的文件。
云存储服务或本地存储服务可以通过实现封装其服务的 DocumentsProvider 参与此生态系统。只需几行代码,便可将需要访问提供程序文档的客户端应用与 SAF 集成。
SAF 包括以下内容:
- 文档提供程序:一种内容提供程序,允许存储服务(如 Google Drive)显示其管理的文件。 文档提供程序作为 DocumentsProvider 类的子类实现。文档提供程序的架构基于传统文件层次结构,但其实际数据存储方式由您决定。Android 平台包括若干内置文档提供程序,如 Downloads、Images 和 Videos。
- 客户端应用:一种自定义应用,它调用 ACTION_OPEN_DOCUMENT 和/或 ACTION_CREATE_DOCUMENT Intent 并接收文档提供程序返回的文件;
- 选取器:一种系统 UI,允许用户访问所有满足客户端应用搜索条件的文档提供程序内的文档。下面是Google的N6上的选取器
具体的存储访问框架流如下图
SAF 提供的部分功能如下:
- 允许用户浏览所有文档提供程序而不仅仅是单个应用中的内容;
- 让您的应用获得对文档提供程序所拥有文档的长期、持久性访问权限。 用户可以通过此访问权限添加、编辑、保存和删除提供程序上的文件;
- 支持多个用户帐户和临时根目录,如只有在插入驱动器后才会出现的 USB 存储提供程序。
如何使用SAF
在Android 4.3 及更低版本,如果想让应用从其他应用中检索文件,它必须调用 ACTION_PICK 或 ACTION_GET_CONTENT 的 Intent;对于 Android 4.4 及更高版本,使用 ACTION_OPEN_DOCUMENT的Intent,这样会触发选取器(Picker),紧接着Picker会从所有注册的Provider中定位符合intent请求条件的数据源,并且通过统一的界面显示给用户,当用户选择完文件/目录后,Picker会将数据以Uri的形式返回给客户端,这样客户端就拿到这些文件/目录的读写权限并进行想要的处理。 需要注意的是ACTION_OPEN_DOCUMENT 并非设计用于替代 ACTION_GET_CONTENT。应使用的 Intent 取决于应用的需要:
- 如果您只想让应用读取/导入数据,请使用 ACTION_GET_CONTENT。使用此方法时,应用会导入数据(如图像文件)的副本;
- 如果您想让应用获得对文档提供程序所拥有文档的长期、持久性访问权限,请使用 ACTION_OPEN_DOCUMENT, 例如,允许用户编辑存储在文档提供程序中的图像的照片编辑应用。
获取文档
private static final int READ_REQUEST_CODE = 42; ... public void performFileSearch() { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); //过滤器只显示可以打开的结果 intent.addCategory(Intent.CATEGORY_OPENABLE); //使用图像MIME数据类型过滤以仅显示图像 intent.setType("image/*"); //要搜索通过已安装的存储提供商提供的所有文档 //intent.setType("*/*"); startActivityForResult(intent, READ_REQUEST_CODE); } 复制代码
处理结果
@Override public void onActivityResult(int requestCode, int resultCode,Intent resultData) { //使用resultdata.getdata ( )提取该URI if (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); } } } 复制代码
获取 InputStream,获取到InputStream我们就可以读取该文件了,当然还可通过URi获取文件的具体文件地址
private String readTextFromUri(Uri uri) throws IOException { InputStream inputStream = getContentResolver().openInputStream(uri); BufferedReader reader = new BufferedReader(new InputStreamReader( inputStream)); StringBuilder stringBuilder = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { stringBuilder.append(line); } fileInputStream.close(); parcelFileDescriptor.close(); return stringBuilder.toString(); } 复制代码
保留权限
当应用打开文件进行读取或写入时,系统会为我们的应用提供针对该文件的 URI 授权。 该授权将一直持续到用户设备重启时。但假定我们的应用是图像编辑应用,而且我们希望用户能够直接从应用中访问他们编辑的最后 5 张图像。 如果用户的设备已经重启,您就需要将用户转回系统选取器以查找这些文件,这显然不是理想的做法。
为防止出现这种情况,可以保留系统为您的应用授予的权限。 应用实际上是获取了系统提供的持久 URI 授权。 这使用户能够通过您的应用持续访问文件,即使设备已重启也不受影响:
final int takeFlags = intent.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); // 检查最新的数据权限 getContentResolver().takePersistableUriPermission(uri, takeFlags); 复制代码
获取文件的真实路径
SAF应用只能获取到用户所选择文件的Uri,有时候我们需要的却是文件的绝对路径,通过下面的方式可以获取文件的绝对路径,參考Stackoverflow
public static String getPath(Context context, Uri uri) { String path = null; //file: 开头的 if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { path = uri.getPath(); return path; } // 以 content:// 开头的,比如 content://media/extenral/images/media/17766 if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()) && Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { Cursor cursor = context.getContentResolver().query(uri, new String[]{MediaStore.Images.Media.DATA}, null, null, null); if (cursor != null) { if (cursor.moveToFirst()) { int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); if (columnIndex > -1) { path = cursor.getString(columnIndex); } } cursor.close(); } return path; } // 4.4 if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (DocumentsContract.isDocumentUri(context, uri)) { if (isExternalStorageDocument(uri)) { // ExternalStorageProvider final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; if ("primary".equalsIgnoreCase(type)) { path = Environment.getExternalStorageDirectory() + "/" + split[1]; return path; } } else if (isDownloadsDocument(uri)) { // DownloadsProvider final String id = DocumentsContract.getDocumentId(uri); final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); path = getDataColumn(context, contentUri, null, null); return path; } else if (isMediaDocument(uri)) { // MediaProvider final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; Uri contentUri = null; if ("image".equals(type)) { contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; } else if ("video".equals(type)) { contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; } else if ("audio".equals(type)) { contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; } final String selection = "_id=?"; final String[] selectionArgs = new String[]{split[1]}; path = getDataColumn(context, contentUri, selection, selectionArgs); return path; } } } return null; } private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { Cursor cursor = null; final String column = "_data"; final String[] projection = {column}; try { cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { final int column_index = cursor.getColumnIndexOrThrow(column); return cursor.getString(column_index); } } finally { if (cursor != null) cursor.close(); } return null; } private static boolean isExternalStorageDocument(Uri uri) { return "com.android.externalstorage.documents".equals(uri.getAuthority()); } private static boolean isDownloadsDocument(Uri uri) { return "com.android.providers.downloads.documents".equals(uri.getAuthority()); } private static boolean isMediaDocument(Uri uri) { return "com.android.providers.media.documents".equals(uri.getAuthority()); } 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Apache Sling 10 发布,Web 内容存储框架
- Golang实现简单爬虫框架(5)——项目重构与数据存储
- tsql – 实体框架4:所选存储过程不返回任何列
- 块存储、文件存储、对象存储三者之比较
- 云原生存储详解:容器存储与 K8s 存储卷
- Android 存储(本地存储 SD卡存储 SharedPreference SQLite ContentProvider)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。