DocumentFile非常慢

时间:2017-02-12 11:06:36

标签: java android

我想创建像" 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 这么慢?

4 个答案:

答案 0 :(得分:3)

如果您阅读 TreeDocumentFileREADME,您会发现对 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 上测试时,sortOrderContentResolver#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获得的树的内容。