Android SAF

1,507 阅读4分钟

Android SAF

这里就不介绍SAF的相关背景,内部实现啥的了,相关知识网上一搜一大堆。

这里主要介绍SAF的相关操作。

别的程序和SAF之间的通信主要是通过Intent进行通信的。别的程序通过调用startActivityForResult(Intent intent, int requestCode),启动SAF。然后在SAF中进行相关操作之后,SAF返回URI。

Intent.ACTION_GET_CONTENT

该Intent主要是打开SAF,让用户选择一个文件。程序获取到文件的Uri,然后可以进行相关的自定义的操作。

代码:

Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
intent.putExtra(Intent.EXTRA_MIME_TYPES,
        new String[]{"application/octet-stream", "message/rfc822"});
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);

上面的代码中setType()方法是设置SAF中哪些文件是处于可以选择状态的。"*/*"表示全部的文件都处于可以选择状态。常见的格式网上有。

intent.putExtra(Intent.EXTRA_MIME_TYPES,new String[]{"application/octet-stream", "message/rfc822"})
这句代码是设置多个文件Type的,像这边就是设置了"application/octet-stream", "message/rfc822"这两种Type的文件是处于可以选择状态的。

这边看起来好像就跟上面的setType重复了一样。但是其实是这样的,如果你只想让某一种类型格式格式的文件处于可选择状态,只需要在setType中设置相关的类型格式就完全OK了,比如setType("message/rfc822")这样。

但是想让多种类型格式的文件处于可选择状态的,这个时候就需要使用intent.putExtra(Intent.EXTRA_MIME_TYPES,new String[]{"application/octet-stream", "message/rfc822"})这句代码了。如果这个时候只设置这句代码,不设置setType("/")的话,程序会报FileNotFoundException这个错误。所以此时setType("/")也是需要的。

intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)设置在SAF中允许用户多选。

上面的代码运行之后,可以在onActivityResult(int requestCode, int resultCode, Intent data)方法中获取到用户操作的URI。

如果用户是单选的话,通过如下代码获取URI

                    if (data.getData() != null) {
                        Uri uri = data.getData();
                        if (uri != null) {
                            XXXXXX
                        }
                    }

如果用户是多选的话,可以通过如下代码获取URI

                    } else {
                        ClipData clipData = data.getClipData();
                        if (clipData != null) {
                            for (int i = 0; i < clipData.getItemCount(); i++) {
                                ClipData.Item item = clipData.getItemAt(i);
                                Uri uri = item.getUri();
                                if (uri != null) {
                                    XXXXXX
                                }
                            }
                        }

多选的情况下,通过data.getData()方法就获取不到内容了,所以判断是多选还是单选,可以if (data.getData != null) {} else {} 进行区分。

Intent.ACTION_OPEN_DOCUMENT

该Intent主要是打开SAF,让用户选择一个文件。程序获取到文件的Uri,然后可以进行相关的自定义的操作。

代码:

        intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("image/*");
        intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);

从上面的代码看跟Intent.ACTION_GET_CONTENT的使用方式是一样的,并且在onActivityResult中返回的结果也是一样的。单选的时候用data.getData(),多选用data.getClipData()。

该Intent跟Intent.ACTION_GET_CONTENT的区别:

①Intent.ACTION_GET_CONTENT是App Level 1的时候就添加的;而Intent.ACTION_OPEN_DOCUMENT是App Level 19的时候就添加的。
②Intent.ACTION_GET_CONTENT返回的是ContentProvider;而Intent.ACTION_OPEN_DOCUMENT返回的是DocumentsProvider。
③Intent.ACTION_GET_CONTENT操作的是一个文件副本;而Intent.ACTION_OPEN_DOCUMENT由于返回的是DocumentsProvider,所以可以通过openFileDescriptor()打开该文件,并使用DocumentsContract.Document中的列查询其详细信息。
④Intent.ACTION_GET_CONTENT的文件选择器的左边是包含第三方程序的图片的;而Intent.ACTION_OPEN_DOCUMENT只包含Google的。如下图红色方框标注的地方。

无标题.png

Intent.ACTION_OPEN_DOCUMENT_TREE

该Intent主要是打开SAF,让用户选择一个文件夹。程序获取到文件的Uri,然后可以进行相关的自定义的操作。

代码:

    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
    startActivityForResult(intent, SDCARD_CHOOSE_REQUEST_CODE);

上面的代码运行之后,可以在onActivityResult(int requestCode, int resultCode, Intent data)方法中获取到用户操作的URI。

通过Uri uri = data.getData()获取URI。

获取到Uri之后,需要给该URI赋予读写权限。

getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
这样就可以很方便的通过该URI进行子文件夹或者子文件的创建,子文件列表的读取等操作了。

获取到Uri之后,我们就可以调用DocumentFile.fromTreeUri(@NonNull Context context, @NonNull Uri treeUri)创建一个DocumentFile对象,然后进行后续操作了。

    if (data.getData() != null) {
        Uri uri = data.getData();
        getContentResolver().takePersistableUriPermission(uri,
                Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        DocumentFile pickedDir = DocumentFile.fromTreeUri(context, uri);
        DocumentFile pickedFile = pickedDir.findFile(XXX);
        if (pickedFile == null) {
            pickedFile = pickedDir.createFile("", XXX);
            if (pickedFile != null && pickedFile.exists()) {
                return pickedFile.getUri();
            }
        }

:DocumentFile类的相关包是在android support包或者androidx包中的,这边建议使用androidx包,毕竟Google已经不再对support包进行后续升级支持,而改为推荐使用androidx包了。

在AndroidStudio中导入DocumentFile包的代码如下:

dependencies { implementation "androidx.documentfile:documentfile:1.0.1" }
而如果是源码通过android.mk进行编译apk的话,代码如下:

LOCAL_STATIC_JAVA_LIBRARIES += androidx.documentfile_documentfile

参考文献:stackoverflow.com/questions/3…