1 Overview
Android 4.4(API 级别 19)
introduced 存储访问框架 (Storage Access Framework)
. SAF
Allows users to easily browse and open in all their preferred document storage providers 文档
, 图像
and 其他文件
. UI
Users can browse files and access recently used files in a consistent way across all apps and providers through easy-to-use standards .
The storage access framework SAF
includes the following:
- Document Provider:
ConentProvider
A subclass that allows a storage service to display the files it manages. Document providersDocumentsProvider
are implemented as subclasses of the class. The document provider's architecture is based on a traditional file hierarchy.Android
The platform includes several built-in document providers,sd卡
corresponding to the operationsExternalStorageProvider
. - Client application: It is our usual app. It calls
ACTION_OPEN_DOCUMENT
these three methods to open, create documents, and open the document treeACTION_CREATE_DOCUMENT
.ACTION_OPEN_DOCUMENT_TREE
Intent
Action
- Picker: A system UI, as we call it
DocumentUi
, that allows users to access all documents within a document provider that meet the client application's search criteria. ThisDocumentUI
has no desktop icons and entrances and can only be accessed through the aboveIntent
.
In the SAF framework, there is no direct interaction between our app application and DocumentProvider, but through DocumentUi.
2 Use of SAF framework
As mentioned above, SAF
the use of the framework is DocumentUI
indirect through the selector, and file operations cannot be performed directly.
How to use it:
2.1 Open file
private static final int READ_REQUEST_CODE = 42;
...
public void performFileSearch() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
//过滤器只显示可以打开的结果
intent.addCategory(Intent.CATEGORY_OPENABLE);
//要搜索通过已安装的存储提供商提供的所有文档
//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);
}
}
}
Return Uri:
content://com.android.externalstorage.documents/document/primary%3ADCIM%2FCamera%2FIMG20190607162534.jpg
2.2 Open the file tree
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, OPEN_TREE_CODE);
private void handleTreeAction(Intent data){
Uri treeUri = data.getData();
//授予打开的文档树永久性的读写权限
final int takeFlags = intent.getFlags()
& (Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(uri, takeFlags);
//使用DocumentFile构建一个根文档,之后的操作可以在该文档上进行
mRoot = DocumentFile.fromTreeUri(this, treeUri);
//显示结果toast
showToast(" open tree uri "+treeUri);
}
Returned Uri:
content://com.android.externalstorage.documents/tree/primary%3AColorOS
- For the document tree we open, the system will grant us read and write permissions for all documents under the document tree, so we can freely use the input and output streams or files we introduced above to read and write, and this authorization will always be retained. to the user to reboot the device.
- But sometimes, we need to be able to permanently access these files without re-authorization after restarting, so we use the takePersistableUriPermission method to retain the system's authorization for our uri, even if the device is restarted.
- We may have saved URIs that an app recently accessed, but they may no longer be valid — another app may have deleted or modified the document. Therefore, getContentResolver().takePersistableUriPermission() should be called to check for the latest data.
- After getting the uri of the root directory, we can use the DocumentFile auxiliary class to easily create, delete files and other operations.
2.3 Create file ACTION_CREATE_DOCUMENT
private void createDocument(){
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
//设置创建的文件是可打开的
intent.addCategory(Intent.CATEGORY_OPENABLE);
//设置创建的文件的minitype为文本类型
intent.setType("text/*");
//设置创建文件的名称,注意SAF中使用minitype而不是文件的后缀名来判断文件类型。
intent.putExtra(Intent.EXTRA_TITLE, "123.txt");
startActivityForResult(intent,CREATE_DOCUMENT_CODE);
}
private void handleCreateDocumentAction(Intent data){
if (data == null) {
return;
}
BufferedWriter bw = null;
try {
OutputStream os = getContentResolver().openOutputStream(uri);
bw = new BufferedWriter(new OutputStreamWriter(os));
bw.write(" i am a text ");
showToast(" create document succeed uri "+uri);
} catch (IOException e) {
e.printStackTrace();
}finally {
closeSafe(bw);
}
}
2.4 Edit documents
After onActivityResult()
obtaining the Uri, you can operate on the Uri:
private void alterDocument(Uri uri) {
try {
ParcelFileDescriptor pfd = getContext().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()
fileOutputStream.close();
pfd.close();
} catch (IOException e) {
e.printStackTrace();
}
}
2.5 Delete documents
If you obtain the document URI
and the document's Document.COLUMN_FLAGS
package SUPPORTS_DELETE
, you can delete the document. For example:
DocumentsContract.deleteDocument(getContentResolver(), uri);
2.6 Use of DocumentFile class
DocumentFile
It is a help class introduced google
to facilitate everyone to use and perform file operations. SAF
Its class is relatively close to the class api
and is more in line with the habits of ordinary users, and its internal essence uses class methods to operate files. In other words, we can also use it to complete the file operations provided by the framework without using it at all. Three static factory methods are provided to create itself.java
File
DocumentsContact
DocumentFile
DocumentsContact
SAF
DocumentFile
fromSingleUri
,This method needs to pass in a SAF
returned uri that points to a , single file uri
. Our uri returned is of this type, and its corresponding ,implementation class is, representing a single file. , this method passes in the uri pointing to the folder, what we return is this type, and its corresponding implementation class is , which represents a folder. , this method passes in an ordinary class, which is a simulation of the class.ACTION_OPEN_DOCUMENT
ACTION_CREATE_DOCUMENT
ingleDocumentFile
fromTreeUri
ACTION_OPEN_TREE
TreeDocumentFile
fromFile
File
file
DocumentFile
The method is summarized as follows:
3 SAF framework principles
SAF
The class diagram of the 3.1 framework is as follows:
It can be seen from the class relationship diagram that DocumentFile
the tool class ultimately DocumentsContract
implements the operation, and DocumentsContract
the final operation Provider
is DocumentsProvider
. DocumentsProvider
There are three categories:
ExternalStorageProvider
It is for external SD卡
use Provider
and DownloadStorageProvider
for downloading Provider
.
ExternalStorageProvider
:com.android.externalstorage.documents
DownloadStorageProvider
:com.android.providers.downloads.documents
MediaDocumentProvider
:com.android.providers.media.documents
The following is a detailed analysis of the process of creating, modifying, and deleting files.
It can be seen that DocumentFile
the auxiliary class is ultimately DocumentsContract
operated throughDocumentsProvider
Let’s take a look at the process of jumping to selection PickerUI
:
PickerUI
Finally, it was also adjusted DocumentsContract
to the middle.
3.2 Document organization form in DocumentProvider
Within the document provider, the data structure follows a traditional file hierarchy, as shown in the following figure:
- Each DocumentProvider may have one or more Root directories as document structure trees. Each root directory has a unique COLUMN_ROOT_ID and points to the document representing the content in the root directory.
- There is a document under each root directory, which points to 1 to n documents, and each of the documents can point to 1 to N documents, thus forming a tree-shaped document structure.
- Each Document will have a unique COLUMN_DOCUMENT_ID to reference them. The document ID is unique and cannot be changed once issued because they are used for permanent URI authorization across all device restarts.
- The document can be an openable file (with a specific MIME type) or a directory containing attached documents (with the MIME_TYPE_DIR MIME type).
- Each document can have different functions, as described by COLUMN_FLAGS. For example, FLAG_SUPPORTS_WRITE, FLAG_SUPPORTS_DELETE, and FLAG_SUPPORTS_THUMBNAIL. Multiple directories can contain the same COLUMN_DOCUMENT_ID.
Document:
3.3 Custom DocumentProvider
If you want your application's data to be documentsui
opened in , you need to write your own document provider
. (If it is just a normal file operation, there is no need to define it this way)
1) First, you need to declare a custom one in the Manifest provider
:
2) DocumentProvider
Basic interface implemented
4 SAF Framework Summary
1. SAF
The framework does not deal directly with DocumentProvider
, but DocumentUI
operates indirectly through.
2. Whether it is through Intent
the method or through the auxiliary class DocumentFile
to perform file operations, it needs to be obtained uri
. This can only be returned uri
through the method, so it is not very convenient. DocumentUI
If you can accept DocumentUI
interaction through it, using SAF
a framework can basically replace the original file operation method.
This chapter gives you a general understanding SAF框架
, and we will introduce Android Q
the sandbox mode in the next chapter.(Scoped Storage)