来自树 URI 的 Android 5.0 DocumentFile [英] Android 5.0 DocumentFile from tree URI

查看:32
本文介绍了来自树 URI 的 Android 5.0 DocumentFile的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

好吧,我找了又找,没有人有我的确切答案,或者我错过了.我让我的用户通过以下方式选择目录:

Alright, I've searched and searched and no one has my exact answer, or I missed it. I'm having my users select a directory by:

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

在我的活动中,我想捕捉实际路径,这似乎是不可能的.

In my activity I want to capture the actual path, which seems to be impossible.

protected void onActivityResult(int requestCode, int resultCode, Intent intent){
    super.onActivityResult(requestCode, resultCode, intent);
    if (resultCode == RESULT_OK) {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M){
            //Marshmallow 

        } else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP){
            //Set directory as default in preferences
            Uri treeUri = intent.getData();
            //grant write permissions
            getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            //File myFile = new File(uri.getPath()); 
            DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);

我选择的文件夹位于:

Device storage/test/

我已经尝试了以下所有方法来获取确切的路径名,但都无济于事.

I've tried all of the following ways to get an exact path name, but to no avail.

File myFile = new File (uri.getPath());
//returns: /tree/1AF6-3708:test

treeUri.getPath();
//returns: /tree/1AF6-3708:test/

pickedDir.getName()
//returns: test

pickedDir.getParentFile()
//returns: null

基本上我需要将 /tree/1AF6-3708: 转换为 /storage/emulated/0/ 或每个设备调用它的存储位置.所有其他可用选项也返回 /tree/1AF6-37u08:.

Basically I need to turn /tree/1AF6-3708: into /storage/emulated/0/ or whatever each device calls it's storage location. All other available options return /tree/1AF6-37u08: also.

我想这样做有两个原因.

There are 2 reasons I want to do it this way.

1) 在我的应用程序中,我将文件位置存储为共享首选项,因为它是特定于用户的.我有相当多的数据将被下载和存储,我希望用户能够把它放在他们想要的地方,特别是如果他们有一个额外的存储位置.我确实设置了默认值,但我想要多功能性,而不是以下专用位置:

1) In my app I store the file location as a shared preference because it is user specific. I have quite a bit of data that will be downloaded and stored and I want the user to be able to place it where they want, especially if they have an additional storage location. I do set a default, but I want versatility, rather than the dedicated location of:

Device storage/Android/data/com.app.name/

2) 在 5.0 中,我想让用户获得对该文件夹的读/写权限,这似乎是唯一的方法.如果我可以从可以解决此问题的字符串中获得读/写权限.

2) In 5.0 I want to enable the user to get read/write permissions to that folder and this seems the only way to do that. If I can get read/write permissions from a string that would fix this issue.

我能找到的所有解决方案都与 Mediastore 相关,这对我没有帮助.我一定是在某处遗漏了一些东西,否则我一定对它上瘾了.任何帮助,将不胜感激.谢谢.

All solutions I've been able to find relate to Mediastore, which doesn't help me exactly. I have to be missing something somewhere or I must have glazed over it. Any help would be appreciated. Thanks.

推荐答案

这将为您提供所选文件夹的实际路径 它适用于属于本地存储的文件/文件夹.

This will give you the actual path of the selected folder It will work ONLY for files/folders that belong in local storage.

Uri treeUri = data.getData();
String path = FileUtil.getFullPathFromTreeUri(treeUri,this); 

其中 FileUtil 是以下类

where FileUtil is the following class

public final class FileUtil {

    private static final String PRIMARY_VOLUME_NAME = "primary";

    @Nullable
    public static String getFullPathFromTreeUri(@Nullable final Uri treeUri, Context con) {
        if (treeUri == null) return null;
        String volumePath = getVolumePath(getVolumeIdFromTreeUri(treeUri),con);
        if (volumePath == null) return File.separator;
        if (volumePath.endsWith(File.separator))
            volumePath = volumePath.substring(0, volumePath.length() - 1);

        String documentPath = getDocumentPathFromTreeUri(treeUri);
        if (documentPath.endsWith(File.separator))
            documentPath = documentPath.substring(0, documentPath.length() - 1);

        if (documentPath.length() > 0) {
            if (documentPath.startsWith(File.separator))
                return volumePath + documentPath;
            else
                return volumePath + File.separator + documentPath;
        }
        else return volumePath;
    }


    private static String getVolumePath(final String volumeId, Context context) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
            return null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
            return getVolumePathForAndroid11AndAbove(volumeId, context);
        else
            return getVolumePathBeforeAndroid11(volumeId, context);
    }


    private static String getVolumePathBeforeAndroid11(final String volumeId, Context context){
        try {
            StorageManager mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
            Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
            Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
            Method getUuid = storageVolumeClazz.getMethod("getUuid");
            Method getPath = storageVolumeClazz.getMethod("getPath");
            Method isPrimary = storageVolumeClazz.getMethod("isPrimary");
            Object result = getVolumeList.invoke(mStorageManager);

            final int length = Array.getLength(result);
            for (int i = 0; i < length; i++) {
                Object storageVolumeElement = Array.get(result, i);
                String uuid = (String) getUuid.invoke(storageVolumeElement);
                Boolean primary = (Boolean) isPrimary.invoke(storageVolumeElement);

                if (primary && PRIMARY_VOLUME_NAME.equals(volumeId))    // primary volume?
                    return (String) getPath.invoke(storageVolumeElement);

                if (uuid != null && uuid.equals(volumeId))    // other volumes?
                    return (String) getPath.invoke(storageVolumeElement);
            }
            // not found.
            return null;
        } catch (Exception ex) {
            return null;
        }
    }

    @TargetApi(Build.VERSION_CODES.R)
    private static String getVolumePathForAndroid11AndAbove(final String volumeId, Context context) {
        try {
            StorageManager mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
            List<StorageVolume> storageVolumes = mStorageManager.getStorageVolumes();
            for (StorageVolume storageVolume : storageVolumes) {
                // primary volume?
                if (storageVolume.isPrimary() && PRIMARY_VOLUME_NAME.equals(volumeId))
                    return storageVolume.getDirectory().getPath();

                // other volumes?
                String uuid = storageVolume.getUuid();
                if (uuid != null && uuid.equals(volumeId))
                    return storageVolume.getDirectory().getPath();

            }
            // not found.
            return null;
        } catch (Exception ex) {
            return null;
        }
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private static String getVolumeIdFromTreeUri(final Uri treeUri) {
        final String docId = DocumentsContract.getTreeDocumentId(treeUri);
        final String[] split = docId.split(":");
        if (split.length > 0) return split[0];
        else return null;
    }


    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private static String getDocumentPathFromTreeUri(final Uri treeUri) {
        final String docId = DocumentsContract.getTreeDocumentId(treeUri);
        final String[] split = docId.split(":");
        if ((split.length >= 2) && (split[1] != null)) return split[1];
        else return File.separator;
    }
}

更新:

解决评论中提到的下载问题:如果您从默认 Android 文件选择器的左侧抽屉中选择 Downloads,您实际上并未选择目录.下载是一个提供者.一个普通的文件夹树 uri 看起来像这样:

To address the Downloads issue mentioned in the comments: If you select Downloads from the left drawer in the default Android file picker you are not actually selecting a directory. Downloads is a provider. A normal folder tree uri looks something like this:

content://com.android.externalstorage.documents/tree/primary%3ADCIM

下载的树uri是

content://com.android.providers.downloads.documents/tree/downloads 

你可以看到一个写着externalstorage,而另一个写着providers.这就是为什么它不能与文件系统中的目录匹配的原因.因为它不是目录.

You can see that the one says externalstorage while the other one says providers. That is why it cannot be matched to a directory in the file system. Because it is not a directory.

解决方案:您可以添加相等性检查,如果树 uri 等于该值,则返回可以像这样检索的默认下载文件夹路径:

SOLUTION: You can add an equality check and if the tree uri is equal to that then return the default download folder path which can be retrieved like this:

Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); 

如果您愿意,还可以为所有提供者执行类似的操作.我假设它会在大部分时间正常工作.但我想在某些边缘情况下它不会.

And do something similar for all the providers if you wish to. And it would work correctly most of the time I assume. But I imagine that there are edge cases where it wouldn't.

感谢@DuhVir 支持 Android R 案例

thanx to @DuhVir for supporting the Android R case

这篇关于来自树 URI 的 Android 5.0 DocumentFile的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆