如何在 Android 11 上获取给定树 URI 的文件路径?

Gan*_*kar 5 android android-11

仅当用户使用存储访问框架选择文件夹时,我们才会获得树 URI。文档提到,从 Android 11 开始,我们可以使用原始文件路径来访问文件。我需要这个,以便我可以使用现有的本机库。但我找不到任何关于获取给定树 URI 的原始路径的文档。如何在 Android 11 上获取给定树 URI 的文件路径?

请注意,stackoverflow 上的所有现有答案都是 hack,它们现在甚至不起作用。我需要一个官方 API。

Per*_*abs 5

对于 >= 29 的 API,存储访问选项已发生巨大变化,由于限制、所需权限等,即使有文件路径也没有多大用处。

API 30 (Andorid 11) 的文档指出可以使用旧的文件 API,指的是此类 API 现在可用于访问/处理位于“共享存储”文件夹中的文件,因此 DCIM、音乐、等等。在这些位置之外,文件 API 将无法工作,在 Android 10 中也是如此。

请注意,Android 11 及所有未来版本中的文件 API 速度要慢得多,并受到惩罚,因为已被重写为包装器,因此不再是文件系统 API。Underneath 现在将所有操作委托给 MediaStore。如果性能很重要,那么最好直接使用 MediaStore API。

我建议通过ContentResolver使用Uris /文件描述符,并远离真实的文件路径。任何解决方法都将是暂时的,并且对于每个新的 Android 版本来说都更难以维护。

下一个示例(有点 hacky)可以帮助解析 API 29 和 30 中的文件路径;未在 29 以下进行测试。不是官方的,因此不能保证它适用于所有用例,我建议不要将其用于生产目的。在我的测试中,解析的路径与 MediaScannerConnection 配合良好但可能需要调整才能处理其他要求。

@Nullable
public static File getFile(@NonNull final Context context, @NonNull final DocumentFile document)
{
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
    {
        return null;
    }

    try
    {
        final List<StorageVolume> volumeList = context
                .getSystemService(StorageManager.class)
                .getStorageVolumes();

        if ((volumeList == null) || volumeList.isEmpty())
        {
            return null;
        }

        // There must be a better way to get the document segment
        final String documentId      = DocumentsContract.getDocumentId(document.getUri());
        final String documentSegment = documentId.substring(documentId.lastIndexOf(':') + 1);

        for (final StorageVolume volume : volumeList)
        {
            final String volumePath;

            if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q)
            {
                final Class<?> class_StorageVolume = Class.forName("android.os.storage.StorageVolume");

                @SuppressWarnings("JavaReflectionMemberAccess")
                @SuppressLint("DiscouragedPrivateApi")
                final Method method_getPath = class_StorageVolume.getDeclaredMethod("getPath");

                volumePath = (String)method_getPath.invoke(volume);
            }
            else
            {
                // API 30
                volumePath = volume.getDirectory().getPath();
            }

            final File storageFile = new File(volumePath + File.separator + documentSegment);

            // Should improve with other checks, because there is the
            // remote possibility that a copy could exist in a different
            // volume (SD-card) under a matching path structure and even
            // same file name, (maybe a user's backup in the SD-card).
            // Checking for the volume Uuid could be an option but
            // as per the documentation the Uuid can be empty.

            final boolean isTarget = (storageFile.exists())
                    && (storageFile.lastModified() == document.lastModified())
                    && (storageFile.length() == document.length());

            if (isTarget)
            {
                return storageFile;
            }
        }
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }

    return null;
}
Run Code Online (Sandbox Code Playgroud)

  • 什么是DocumentFile文件以及如何传递: (2认同)

Com*_*are 1

如何在 Android 11 上获取给定树 URI 的文件路径?

抱歉,但这不是一个选择。

我需要这个,以便我可以使用现有的本机库

让本机库使用您的应用程序可以直接在 Android 10+ 和旧设备上使用的文件系统位置(例如,getFilesDir()getExternalFilesDir())。

文档提到,从 Android 11 开始,我们可以使用原始文件路径来访问文件。

对,但是:

  • 这不适用于 Android 10,并且
  • ACTION_OPEN_DOCUMENT_TREE不涉及存储访问框架(例如)

您可以使用像getDirectory()on这样的 APIStorageVolume通过 Android 11 的“所有文件访问”来处理存储卷(外部和可移动)。有关更多信息,请参阅此博客文章