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的搜索条件的。
document provider 的数据模型是基于传统的文件架构。但是,物理上,你可以随意存储,只要可以被DocumentProvider API访问。
注意到:
1. Client and Provider 不直接交互。
2. 当客户端发起一个Intent(ACTION_OPEN_DOCUMENT or ACTION_CREATE_DOCUMENT), 可能包括一些filters
3. 一旦Intent发出, the system picker 就会到每个注册的provider,并显示匹配结果。
4. Picker提供一个标准的接口访问文件。
在Android 4.3之前,使用ACTION_PICK或者ACTION_GET_CONTENT 获得文件,对于后者是仅copy一份文件。
在Android 4.4及更高版本,将可使用ACTION_OPEN_DOCUMENT来获取文件,是直接使用源文件。
发起搜索图片请求
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() {
// ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
// browser.
Intent intent = new Intent(Intent.ACTION_OPEN_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);
// Filter to show only images, using the image MIME data type.
// If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
// To search for all documents available via installed storage providers,
// it would be "*/*".
intent.setType("image/*");
startActivityForResult(intent, READ_REQUEST_CODE);
}
处理搜索结果
@Override
public void onActivityResult(int requestCode, int resultCode,
Intent resultData) {
// The ACTION_OPEN_DOCUMENT intent was sent with the request code
// READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
// response to some other intent, and the code below shouldn't run at all.
if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
// The document selected by the user won't be returned in the intent.
// Instead, a URI to that document will be contained in the return intent
// provided to this method as a parameter.
// Pull that URI using resultData.getData().
Uri uri = null;
if (resultData != null) {
uri = resultData.getData();
Log.i(TAG, "Uri: " + uri.toString());
showImage(uri);
}
}
}
Note: 不会直接返回文件,而是返回文件的URI地址。
既然已经获得文件的URI,那么就可以查询该URI指向源文件的metadata。
Examine document metadata
public void dumpImageMetaData(Uri uri) {
// The query, since it only applies to a single document, will only return
// one row. There's no need to filter, sort, or select fields, since we want
// all fields for one document.
Cursor cursor = getActivity().getContentResolver()
.query(uri, null, null, null, null, null);
try {
// moveToFirst() returns false if the cursor has 0 rows. Very handy for
// "if there's anything to look at, look at it" conditionals.
if (cursor != null && cursor.moveToFirst()) {
// Note it's called "Display Name". This is
// provider-specific, and might not necessarily be the file name.
String displayName = cursor.getString(
cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
Log.i(TAG, "Display Name: " + displayName);
int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
// If the size is unknown, the value stored is null. But since an
// int can't be null in Java, the behavior is implementation-specific,
// which is just a fancy term for "unpredictable". So as
// a rule, check if it's null before assigning to an int. This will
// happen often: The storage API allows for remote files, whose
// size might not be locally known.
String size = null;
if (!cursor.isNull(sizeIndex)) {
// Technically the column stores an int, but cursor.getString()
// will do the conversion automatically.
size = cursor.getString(sizeIndex);
} else {
size = "Unknown";
}
Log.i(TAG, "Size: " + size);
}
} finally {
cursor.close();
}
}
打开文档
比如打开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);
}
删除一个文档
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() {
// ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's
// file browser.
Intent intent = new Intent(Intent.ACTION_OPEN_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);
// Filter to show only text files.
intent.setType("text/plain");
startActivityForResult(intent, EDIT_REQUEST_CODE);
}
// onActivityResult() 的处理中,调用以下方法,然后并将其写出。
private void alterDocument(Uri uri) {
try {
// 获得uri指向文件的 序列化文件描述符
ParcelFileDescriptor pfd = getActivity().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();
pfd.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
在原文中,接下来是写一个自定义的Document Provider
Writing a Custom Document Provider,不再赘述,以后有时间再看。