Storage Access Framework(SAF)
Storage Access Framework(SAF) 存储访问框架,Android 4.4+ 以后引入的为用户浏览手机中的存储内容提供了便利,可供访问的内容包括:文档,图片,视频,音频,下载
SAF 框架的组成
-
Document provider
DocumentsProvider 的子类
一个特殊的
ContentProvider
,让一个存储服务(比如Google Drive
)可以对外展示自己所管理的文件Android 系统内置了几个
Document provider
,比如关于下载,图片以及视频的Document provider
-
Client app
一个普通的客户端软件
触发 ACTION_OPEN_DOCUMENT 或 ACTION_CREATE_DOCUMENT 就可以接收到来自于
Document provider
返回的内容,比如选择一个图片,然后返回一个Uri -
Picker
类似于文件管理器的界面,而且是系统级的界面,提供额访问客户端过滤条件的 Document provider 内容的通道
SAF
SAF 的核心是实现了 DocumentsProvider
的子类,还是一个 ContentProvider
在一个 document provider 中是以传统的文件目录树组织起来的
SAF 流程图
document provider data 是基于传统的文件层次结构的,不过那只是对外的表现形式
如何存储你的数据,取决于你自己,只要对外的接口能够通过 DocumentsProvider 的 api 访问就可以
下面的流程图展示了一个 photo 应用使用 SAF 可能的结构
从图中可以看出 Picker
是链接调用者和内容提供者的一个桥梁,它提供并告诉调用者,可以选择
哪些内容提供者,比如这里的 DriveDocProvider,UsbDocProvider,CloundDocProvider
当客户端触发了 ACTION_OPEN_DOCUMENT 或 ACTION_CREATE_DOCUMENT 的 Intent,就会发生上述交互。当然我们还可以在 Intent 中增加过滤条件,比如限制 MIME type
的类型为 "image"
客户端调用,并获取返回的 Uri
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private static final int READ_REQUEST_CODE = 42; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button btn_show = (Button) findViewById(R.id.btn_show); btn_show.setOnClickListener(this); } @Override public void onClick(View v) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("image/*"); startActivityForResult(intent, READ_REQUEST_CODE); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) { Uri uri; if (data != null) { uri = data.getData(); Log.e("HeHe", "Uri: " + uri.toString()); } } } }
根据 uri 获取文件参数
public void dumpImageMetaData(Uri uri) { Cursor cursor = getContentResolver() .query(uri, null, null, null, null, null); try { if (cursor != null && cursor.moveToFirst()) { String displayName = cursor.getString( cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); Log.e("HeHe", "Display Name: " + displayName); int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE); String size = null; if (!cursor.isNull(sizeIndex)) { size = cursor.getString(sizeIndex); }else { size = "Unknown"; } Log.e("HeHe", "Size: " + size); } }finally { cursor.close(); } }
根据 Uri 获得 Bitmap
private Bitmap getBitmapFromUri(Uri uri) throws IOException { ParcelFileDescriptor parcelFileDescriptor = getContentResolver().openFileDescriptor(uri, "r"); FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor); parcelFileDescriptor.close(); return image; }
根据 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(); }
创建新文件以及删除文件
创建文件
private void createFile(String mimeType, String fileName) { Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType(mimeType); intent.putExtra(Intent.EXTRA_TITLE, fileName); startActivityForResult(intent, WRITE_REQUEST_CODE); }
可在 onActivityResult() 中获取被创建文件的 uri
删除文件
前提是 Document.COLUMN_FLAGS 包含 SUPPORTS_DELETE
DocumentsContract.deleteDocument(getContentResolver(), uri);