使用 MTP 使用 Android 存储访问框架/DocumentProvider 遍历目录层次结构时出现问题
Issues traversing through directory hierarchy with Android Storage Access Framework / DocumentProvider using MTP
更新:
我最初的问题可能会产生误导,所以我想改一下:
我想通过 Android 的存储访问框架从 MTP 连接设备遍历层次结构树。我似乎无法实现这一点,因为我得到一个 SecurityException
说明子节点不是其父节点的后代。有没有办法解决这个问题?或者这是一个已知问题?谢谢。
我正在编写一个 Android 应用程序,它试图通过 MtpDocumentsProvider
使用 Android 的存储访问框架 (SAF) 通过层次结构树遍历和访问文档。我或多或少地遵循 https://github.com/googlesamples/android-DirectorySelection 中描述的关于如何从我的应用程序启动 SAF Picker 的代码示例,select 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 数据源。我 select 说数据源,我从我的 onActivityResult
:
得到 Uri
@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 不是 rootUri
的后代yy.
D/MyApp(19241): node 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): Writing exception to parcel
E/DatabaseUtils(20944): java.lang.SecurityException: Document 4 is not a descendant of 2
任何人都可以提供一些关于我做错了什么的见解吗?如果我使用不同的数据源提供程序,例如来自外部存储的数据源提供程序(即通过标准 USB OTG reader 连接的 SD 卡),一切正常。
附加信息:
我 运行 Nexus 6P Android 7.1.1,我的应用 minSdkVersion
是 19.
在开始前为结果添加的标志 activity 什么都不做。
我不明白你使用的是 DocumentsContract。用户选择一个目录。从你得到的uri你可以为那个目录构造一个DocumentFile
。
之后在该实例上使用 DocumentFile::listFiles()
获取子目录和文件列表。
经过多次尝试,我不确定是否有解决此问题的方法 SecurityException
MTP 数据源(除非有人可以反驳我)。查看 DocumentProvider.java
源代码和堆栈跟踪,似乎 DocumentProvider#enforceTree
中对 DocumentProvider#isChildDocument
的调用可能未在 MtpDocumentProvider
实现中被正确覆盖 Android 框架。默认实现总是 returns false
。 #悲伤的脸
我正在使用您的代码通过添加行进入子文件夹:
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));
}
更新:
我最初的问题可能会产生误导,所以我想改一下:
我想通过 Android 的存储访问框架从 MTP 连接设备遍历层次结构树。我似乎无法实现这一点,因为我得到一个 SecurityException
说明子节点不是其父节点的后代。有没有办法解决这个问题?或者这是一个已知问题?谢谢。
我正在编写一个 Android 应用程序,它试图通过 MtpDocumentsProvider
使用 Android 的存储访问框架 (SAF) 通过层次结构树遍历和访问文档。我或多或少地遵循 https://github.com/googlesamples/android-DirectorySelection 中描述的关于如何从我的应用程序启动 SAF Picker 的代码示例,select 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 数据源。我 select 说数据源,我从我的 onActivityResult
:
Uri
@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 不是 rootUri
的后代yy.
D/MyApp(19241): node 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): Writing exception to parcel E/DatabaseUtils(20944): java.lang.SecurityException: Document 4 is not a descendant of 2
任何人都可以提供一些关于我做错了什么的见解吗?如果我使用不同的数据源提供程序,例如来自外部存储的数据源提供程序(即通过标准 USB OTG reader 连接的 SD 卡),一切正常。
附加信息:
我 运行 Nexus 6P Android 7.1.1,我的应用 minSdkVersion
是 19.
在开始前为结果添加的标志 activity 什么都不做。
我不明白你使用的是 DocumentsContract。用户选择一个目录。从你得到的uri你可以为那个目录构造一个DocumentFile
。
之后在该实例上使用 DocumentFile::listFiles()
获取子目录和文件列表。
经过多次尝试,我不确定是否有解决此问题的方法 SecurityException
MTP 数据源(除非有人可以反驳我)。查看 DocumentProvider.java
源代码和堆栈跟踪,似乎 DocumentProvider#enforceTree
中对 DocumentProvider#isChildDocument
的调用可能未在 MtpDocumentProvider
实现中被正确覆盖 Android 框架。默认实现总是 returns false
。 #悲伤的脸
我正在使用您的代码通过添加行进入子文件夹:
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));
}