我想创建像" PDF Viewer app"。应用程序将搜索用户选择的位置中的所有* .pdf文件。用户可以通过此功能选择此文件夹:
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, REQUEST_CODE);
然后我得到DocumentFile(文件夹):
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == getActivity().RESULT_OK && requestCode == REQUEST_CODE) {
Uri uriTree = data.getData();
DocumentFile documentFile = DocumentFile.fromTreeUri(getActivity(), uriTree);
//rest of code here
}
}
为什么我选择这种选择文件夹的方法?因为我希望能够选择辅助存储(您知道,在Android> = 5.0中,您无法使用Java.io.file访问辅助存储)。
好的,所以我得到所有* .pdf文件夹作为DocumentFile。然后我打电话给:
for(DocumentFile file: documentFile.listFiles()){
String fileNameToDisplay = file.getName();
}
这是非常慢。当所选文件夹中有~600个文件时,大约需要30秒。为了证明这一点,我从外部存储(而不是二级存储)中选择了目录,然后我尝试了两个解决方案:DocumentFile和File。 文件版本看起来像:
File f = new File(Environment.getExternalStorageDirectory()+"/pdffiles");
for(File file: f.listFiles()){
String fileNameToDisplay = file.getName();
}
}
第二版的速度提高了约500倍。几乎没有时间在列表视图上显示所有文件。
为什么 DocumentFile 这么慢?
答案 0 :(得分:3)
如果您阅读 TreeDocumentFile
的 README,您会发现对 listFiles()
和 getName()
的每次调用都在幕后调用 ContentResolver#query()
。就像 CommonsWare 说的那样,这会执行数百个查询,效率非常低。
这是listFiles()
的源代码:
@Override
public DocumentFile[] listFiles() {
final ContentResolver resolver = mContext.getContentResolver();
final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(mUri,
DocumentsContract.getDocumentId(mUri));
final ArrayList<Uri> results = new ArrayList<>();
Cursor c = null;
try {
c = resolver.query(childrenUri, new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID }, null, null, null);
while (c.moveToNext()) {
final String documentId = c.getString(0);
final Uri documentUri = DocumentsContract.buildDocumentUriUsingTree(mUri,
documentId);
results.add(documentUri);
}
} catch (Exception e) {
Log.w(TAG, "Failed query: " + e);
} finally {
closeQuietly(c);
}
final Uri[] result = results.toArray(new Uri[results.size()]);
final DocumentFile[] resultFiles = new DocumentFile[result.length];
for (int i = 0; i < result.length; i++) {
resultFiles[i] = new TreeDocumentFile(this, mContext, result[i]);
}
return resultFiles;
}
在此函数调用中,listFiles()
进行了仅选择文档 ID 列的查询。但是,在您的情况下,您还需要每个文件的文件名。因此,您可以将列 COLUMN_DISPLAY_NAME
添加到查询中。这将在单个查询中检索文件名和文档 ID(稍后您将其转换为 Uri)并且效率更高。还有许多其他可用的列,例如文件类型、文件大小和上次修改时间,您可能也想检索它们。
c = resolver.query(mUri, new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
DocumentsContract.Document.COLUMN_DISPLAY_NAME
}, null, null, null);
在while循环中,通过
检索文件名final String filename = c.getString(1);
上述修改后的代码能够立即检索具有 1000 多个文件的目录的 Uri 和文件名。
总而言之,我的建议是,如果您处理的不仅仅是几个文件,请避免使用 DocumentFile
。而是使用 ContentResolver#query()
通过在查询中选择多个列来检索 Uri 和其他信息。对于文件操作,通过传递适当的 Uri 来使用 DocumentsContract
类中的静态方法。
顺便说一下,在 Android 11 和 Android 9 上测试时,sortOrder
的 ContentResolver#query()
参数似乎在上面的代码片段中被完全忽略了。我会手动对结果进行排序而不是依赖查询顺序。
答案 1 :(得分:1)
为什么DocumentFile这么慢?
对于~600个文件,您正在执行〜ContentProvider
的约600个请求以获取显示名称,这意味着~600个IPC事务。
而是使用MediaStore
查询具有application/pdf
MIME类型的所有索引媒体。
答案 2 :(得分:1)
要使用DocumentsContract获取子文档,请参阅https://developer.android.com/reference/android/provider/DocumentsContract.html#buildChildDocumentsUriUsingTree(android.net.Uri,java.lang.String)。
从ACTION_OPEN_DOCUMENT_TREE返回的Uri是树文档URI。使用上面的方法构建Uri以查询所有子文档。
根据ACTION_OPEN_TREE_DOCUMENT返回的Uri,可以使用https://developer.android.com/reference/android/provider/DocumentsContract.html#getTreeDocumentId(android.net.Uri)获取根文档ID。
答案 3 :(得分:0)
要获得的速度略低于File
使用DocumentsContract
而不是DocumentFile
的速度,以列出使用Intent.ACTION_OPEN_DOCUMENT_TREE
获得的树的内容。