转自 http://blog.youkuaiyun.com/caryee89/article/details/44155375
Android API Guide
Android 4.4 引入了存储访问框架Storage Access Framework(SAF),云或本地存储服务可以实现DocumentProvider来分装他们的服务以便加入到该生态系统中。
SAF包含了
Document Provider, Android 包含几个内置的DocumentsProvider,注入下载Downloads, Images, Videos.
Client App: 一个自定义的App,可发送ACTION_OPEN_DOCUMENT或者ACTION_CREATE_DOCUMENT,接收返回的文件。
Picker: 一个系统UI, 允许用户访问所有document providers 满足client app的搜索条件的。
Overview
架构图

- 以Root开始,指向单一节点,然后改单一节点再扇形展开。每个Root都有一个独一无二的COLUMN_ROOT_ID, 并且指向一个document或者directory, 代表了该Root下的内容。
- 在每个root下是一个single document, 该document指向1 to N个documents, 这下面的每个再指向1 to N个documents。
- 每个文件或者目录 都有COLUMN_DOCUMENT_ID, 该ID必须是独一无二的,且在issue后不可修改的。
- documents 可以是可打开的文件,也可是目录。
- 每个document 可以具有不同的能力,由COLUMN_FLAGS指定。例如:FLAG_SUPPORTS_WRITE, FLAG_SUPPORTS_DELETE, and FLAG_SUPPORTS_THUMBNAIL. 此外,COLUMN_DOCUMENT_ID可以出现在多个目录下。
Control Flow
document provider 的数据模型是基于传统的文件架构。但是,物理上,你可以随意存储,只要可以被DocumentProvider API访问。
下图显示了一个Photo App如何使用SAF访问图片

注意到:
1. Client and Provider 不直接交互。
2. 当客户端发起一个Intent(ACTION_OPEN_DOCUMENT or ACTION_CREATE_DOCUMENT), 可能包括一些filters
3. 一旦Intent发出, the system picker 就会到每个注册的provider,并显示匹配结果。
4. Picker提供一个标准的接口访问文件。
Writing a Client App
在Android 4.3之前,使用ACTION_PICK或者ACTION_GET_CONTENT 获得文件,对于后者是仅copy一份文件。
在Android 4.4及更高版本,将可使用ACTION_OPEN_DOCUMENT来获取文件,是直接使用源文件。
Search for documents
发起搜索图片请求
private static final int READ_REQUEST_CODE = 42;
...
/**
* Fires an intent to spin up the "file chooser" UI and select an image.
*/
public void performFileSearch() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
startActivityForResult(intent, READ_REQUEST_CODE);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
处理搜索结果
@Override
public void onActivityResult(int requestCode, int resultCode,
Intent resultData) {
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);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
Note: 不会直接返回文件,而是返回文件的URI地址。
既然已经获得文件的URI,那么就可以查询该URI指向源文件的metadata。
Examine document metadata
public void dumpImageMetaData(Uri uri) {
Cursor cursor = getActivity().getContentResolver()
.query(uri, null, null, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
String displayName = cursor.getString(
cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
Log.i(TAG, "Display Name: " + displayName);
int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
String size = null;
if (!cursor.isNull(sizeIndex)) {
size = cursor.getString(sizeIndex);
} else {
size = "Unknown";
}
Log.i(TAG, "Size: " + size);
}
} finally {
cursor.close();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
打开文档
比如打开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;
}
do it in background using AsyncTask.
再比如获得一个InputStream
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();
}
创建一个文档
// Here are some examples of how you might call this method.
// The first parameter is the MIME type, and the second parameter is the name
// of the file you are creating:
//
// createFile("text/plain", "foobar.txt");
// createFile("image/png", "mypicture.png");
// Unique request code.
private static final int WRITE_REQUEST_CODE = 43;
...
private void createFile(String mimeType, String fileName) {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
// Filter to only show results that can be "opened", such as
// a file (as opposed to a list of contacts or timezones).
intent.addCategory(Intent.CATEGORY_OPENABLE);
// Create a file with the requested MIME type.
intent.setType(mimeType);
intent.putExtra(Intent.EXTRA_TITLE, fileName);
startActivityForResult(intent, WRITE_REQUEST_CODE);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
删除一个文档
DocumentsContract.deleteDocument(getContentResolver(), uri)
注意,除了要有需要删除的文件uri, 该uri指向的文件的Document.COLUMN_FLAGS包含SUPPORTS_DELETE。
编辑一个文件:
private static final int EDIT_REQUEST_CODE = 44;
/**
* Open a file for writing and append some text to it.
*/
private void editDocument() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("text/plain");
startActivityForResult(intent, EDIT_REQUEST_CODE);
}
private void alterDocument(Uri uri) {
try {
ParcelFileDescriptor pfd = getActivity().getContentResolver().
openFileDescriptor(uri, "w");
FileOutputStream fileOutputStream =
new FileOutputStream(pfd.getFileDescriptor());
fileOutputStream.write(("Overwritten by MyCloud at " +
System.currentTimeMillis() + "\n").getBytes());
fileOutputStream.close();
pfd.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
在原文中,接下来是写一个自定义的Document Provider
Writing a Custom Document Provider,不再赘述,以后有时间再看。