18

好吧,我已经搜索和搜索了,没有人有我的确切答案,或者我错过了。我让我的用户通过以下方式选择目录:

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

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

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/

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

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:也会返回。

我想这样做有两个原因。

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

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

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

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

4

6 回答 6

46

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

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

其中 FileUtil 是以下类

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;
    }
}

更新:

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

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

下载的树 uri 是

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

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

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

Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); 

如果您愿意,可以为所有提供商做类似的事情。我认为它会正常工作most of the time。但我想在某些极端情况下它不会。

感谢@DuhVir 支持 Android R 案例

于 2016-03-22T18:44:59.583 回答
3

这是对 Android 11 的 @Anonymous 答案的补充。

@TargetApi(Build.VERSION_CODES.R)
    private static String getVolumePath_SDK30(final String volumeId, Context context) {
        try {
            StorageManager mStorageManager =
                    (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
            if (mStorageManager == null) return null;
            List<StorageVolume> storageVolumes = mStorageManager.getStorageVolumes();
            for (StorageVolume storageVolume : storageVolumes) {
                String uuid = storageVolume.getUuid();
                Boolean primary = storageVolume.isPrimary();
                if (primary == null) return null;
                // primary volume?
                if (primary && PRIMARY_VOLUME_NAME.equals(volumeId))
                    return storageVolume.getDirectory().getPath();
                // other volumes?
                if (uuid != null && uuid.equals(volumeId))
                    return storageVolume.getDirectory().getPath();
            }
            // not found.
            return null;
        } catch (Exception ex) {
            return null;
        }
    }
于 2020-11-17T15:08:40.223 回答
3

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

那是因为可能没有实际路径,更不用说您可以访问的路径了。有许多可能的文档提供者,其中很少有将所有文档都保存在本地设备上,而且很少有提供者会将文件保存在外部存储中,您可以在其中使用它们。

我有相当多的数据将被下载和存储,我希望用户能够将其放置在他们想要的地方

然后使用存储访问框架 API,而不是认为您从存储访问框架获得的文档/树始终是本地的。或者,不要使用ACTION_OPEN_DOCUMENT_TREE.

在 5.0 中,我想让用户获得对该文件夹的读/写权限

这由存储提供者处理,作为用户与该存储提供者交互的一部分。你没有参与。

于 2016-01-21T15:56:55.417 回答
0
public static String findFullPath(String path) {
    String actualResult="";
    path=path.substring(5);
    int index=0;
    StringBuilder result = new StringBuilder("/storage");
    for (int i = 0; i < path.length(); i++) {
        if (path.charAt(i) != ':') {
            result.append(path.charAt(i));
        } else {
            index = ++i;
            result.append('/');
            break;
        }
    }
    for (int i = index; i < path.length(); i++) {
        result.append(path.charAt(i));
    }
    if (result.substring(9, 16).equalsIgnoreCase("primary")) {
        actualResult = result.substring(0, 8) + "/emulated/0/" + result.substring(17);
    } else {
        actualResult = result.toString();
    }
    return actualResult;
}

这个函数给了我树 uri 的绝对路径。该解决方案适用于我在 1000 多台设备中测试过的大多数设备。它还为我们提供了包含在存储卡或 OTG 中的文件夹的正确绝对路径。

How it Works?

基本上大多数设备都有以 /Storage/ 前缀开头的路径。路径的中间部分包含挂载点名称,即 /emulated/0/ 用于内部存储,或一些字符串,如 /C0V54440/ 等(仅作为示例)。最后一段是从存储根目录到 /movie/piratesofthecarribian 等文件夹的路径

所以,我们构建的路径:- /tree/primary:movie/piratesofthecarribian 是:- /storage/emulated/0/movie/piratesofthecarribian

您可以在我的 github 存储库中找到更多信息。访问那里获取有关如何将树 uri 转换为实际绝对路径的 android 代码。

gihub(https://github.com/chetanborase/TreeUritoAbsolutePath

于 2020-09-30T18:36:04.897 回答
0

我之前尝试添加默认保存目录,或者如果用户没有在我的应用程序的首选项屏幕中使用 SAF UI 选择自定义目录。用户可能会错过选择文件夹,应用程序可能会崩溃。要在设备内存中添加默认文件夹,您应该

    DocumentFile saveDir = null;
    saveDir = DocumentFile.fromFile(Environment.getExternalStorageDirectory());
    String uriString = saveDir.getUri().toString();

    List<UriPermission> perms = getContentResolver().getPersistedUriPermissions();
    for (UriPermission p : perms) {
        if (p.getUri().toString().equals(uriString) && p.isWritePermission()) {
            canWrite = true;
            break;
        }
    }
    // Permitted to create a direct child of parent directory
    DocumentFile newDir = null;
    if (canWrite) {
         newDir = saveDir.createDirectory("MyFolder");
    }

    if (newDir != null && newDir.exists()) {
        return newDir;
    }

此代码段将在设备的主内存中创建一个目录,并授予该文件夹和子文件夹的读/写权限。您不能直接创建 MyFolder/MySubFolder 层次结构,您应该再次创建另一个目录。

您可以检查该目录是否具有写入权限,据我在 3 台设备上看到的,如果它是使用DocumentFile而不是File类创建的,则返回 true。这是为 Android >= 5.0 创建和授予写入权限的简单方法,无需使用ACTION_OPEN_DOCUMENT_TREE

于 2017-09-18T15:06:59.030 回答
0
public static String findFullPath(String path) {
        String actualResult="";
        path=path.substring(5);
        int index=0;
        StringBuilder result = new StringBuilder("/storage");
        for (int i = 0; i < path.length(); i++) {
            if (path.charAt(i) != ':') {
                result.append(path.charAt(i));
            } else {
                index = ++i;
                result.append('/');
                break;
            }
        }
        for (int i = index; i < path.length(); i++) {
            result.append(path.charAt(i));
        }
        if (result.substring(9, 16).equalsIgnoreCase("primary")) {
            actualResult = result.substring(0, 8) + "/emulated/0/" + result.substring(17);
        } else {
            actualResult = result.toString();
        }
        return actualResult;
    }
于 2020-12-06T12:47:43.980 回答