8

更新:我最初的问题可能具有误导性,所以我想改写一下:我想通过 Android 的存储访问框架从 MTP 连接的设备遍历层次结构树。我似乎无法做到这一点,因为我得到一个SecurityException声明,即子节点不是其父节点的后代。有没有办法解决这个问题?或者这是一个已知问题?谢谢。

我正在编写一个 Android 应用程序,它尝试使用 Android 的存储访问框架 (SAF) 通过MtpDocumentsProvider. 我或多或少遵循https://github.com/googlesamples/android-DirectorySelection中描述的代码示例,了解如何从我的应用程序启动 SAF Picker,选择 MTP 数据源,然后在onActivityResult中使用返回Uri的遍历层次结构。不幸的是,这似乎不起作用,因为一旦我访问一个子文件夹并尝试遍历它,我总是会得到一个SecurityException说明document xx is not a descendant of yy

所以我的问题是,使用MtpDocumentProvider,我怎样才能成功地从我的应用程序遍历层次结构树并避免这个异常?

具体来说,在我的应用程序中,首先,我调用以下方法来启动 SAF Picker:

private void launchStoragePicker() {
    Intent browseIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
    browseIntent.addFlags(
        Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
            | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
            | Intent.FLAG_GRANT_READ_URI_PERMISSION
            | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
    );
    startActivityForResult(browseIntent, REQUEST_CODE_OPEN_DIRECTORY);
}

Android SAF 选择器随后启动,我看到我连接的设备被识别为 MTP 数据源。我选择所述数据源并Uri从我的onActivityResult

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE_OPEN_DIRECTORY && resultCode == Activity.RESULT_OK) {
        traverseDirectoryEntries(data.getData()); // getData() returns the root uri node
    }
}

然后,使用返回的Uri,我调用DocumentsContract.buildChildDocumentsUriUsingTree以获取Uri然后我可以使用它来查询和访问树层次结构:

void traverseDirectoryEntries(Uri rootUri) {
    ContentResolver contentResolver = getActivity().getContentResolver();
    Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, DocumentsContract.getTreeDocumentId(rootUri));

    // Keep track of our directory hierarchy
    List<Uri> dirNodes = new LinkedList<>();
    dirNodes.add(childrenUri);

    while(!dirNodes.isEmpty()) {
        childrenUri = dirNodes.remove(0); // get the item from top
        Log.d(TAG, "node uri: ", childrenUri);
        Cursor c = contentResolver.query(childrenUri, new String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME, Document.COLUMN_MIME_TYPE}, null, null, null);
        try {
            while (c.moveToNext()) {
                final String docId = c.getString(0);
                final String name = c.getString(1);
                final String mime = c.getString(2);
                Log.d(TAG, "docId: " + id + ", name: " + name + ", mime: " + mime);
                if(isDirectory(mime)) {
                    final Uri newNode = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, docId);
                    dirNodes.add(newNode);
                }
            }
        } finally {
            closeQuietly(c);
        }
    }
}

// Util method to check if the mime type is a directory
private static boolean isDirectory(String mimeType) {
    return DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType);
}

// Util method to close a closeable
private static void closeQuietly(Closeable closeable) {
    if (closeable != null) {
        try {
            closeable.close();
        } catch (RuntimeException re) {
            throw re;
        } catch (Exception ignore) {
            // ignore exception
        }
    }
}

外部 while 循环的第一次迭代成功:调用query返回了一个有效Cursor的我可以遍历。问题是第二次迭代:当我尝试查询Uri恰好是 的子节点的 时rootUri,我得到一个SecurityException说明文档 xx 不是 yy 的后代的信息。

D/MyApp(19241): 节点 uri: content://com.android.mtp.documents/tree/2/document/2/children D/MyApp(19241): docId: 4, name: DCIM, mime: vnd. android.document/directory D/MyApp(19241): node uri: content://com.android.mtp.documents/tree/2/document/4/children E/DatabaseUtils(20944): 写入异常包裹E/DatabaseUtils (20944):java.lang.SecurityException:文档 4 不是 2 的后代

谁能提供一些关于我做错了什么的见解?如果我使用不同的数据源提供程序,例如来自外部存储的数据源提供程序(即通过标准 USB OTG 读卡器连接的 SD 卡),一切正常。

附加信息:我在 Nexus 6P、Android 7.1.1 上运行它,我的应用minSdkVersion是 19。

4

3 回答 3

4

我正在使用您的代码通过添加行进入子文件夹:

Uri childrenUri;
try {
    //for childs and sub child dirs
    childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, DocumentsContract.getDocumentId(uri));
} catch (Exception e) {
    // for parent dir
    childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, DocumentsContract.getTreeDocumentId(uri));
}
于 2018-03-15T13:12:57.117 回答
2

在开始活动之前为结果添加的标志什么都不做。

我不明白您使用 DocumentsContract。用户选择一个目录。从您获得的 uri 中,您可以DocumentFile为该目录构建一个。

之后DocumentFile::listFiles()在该实例上使用以获取子目录和文件的列表。

于 2016-12-12T10:57:28.003 回答
0

经过不同的尝试,我不确定SecurityExceptionMTP 数据源有没有办法解决这个问题(除非有人可以反驳我)。查看DocumentProvider.java源代码和堆栈跟踪,似乎对DocumentProvider#isChildDocumentinside的调用DocumentProvider#enforceTree可能没有MtpDocumentProvider在 Android 框架的实现中被正确覆盖。默认实现总是返回false。#悲伤的脸

于 2016-12-13T00:45:07.700 回答