代码之家  ›  专栏  ›  技术社区  ›  Anthony

queryRoots首先调用queryDocument,而不是queryChildDocuments

  •  0
  • Anthony  · 技术社区  · 7 年前

    我正在为Dropbox编写SAF包装,因为每个人(包括谷歌)都懒得实现这个“非常丰富”(即:糟糕)的API。我的根扎根于采摘者,但我想 queryChildren 应该先打电话。然而 queryChildren is never called and it goes straight to 查询文档`。

    override fun queryRoots(projection: Array<out String>?): Cursor {
        // TODO: Likely need to be more strict about projection (ie: map to supported)
        val result = MatrixCursor(projection ?: DEFAULT_ROOT_PROJECTION)
    
        val row = result.newRow()
        row.add(DocumentsContract.Root.COLUMN_ROOT_ID, "com.anthonymandra.cloudprovider.dropbox")
        row.add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_dropbox_gray)
        row.add(DocumentsContract.Root.COLUMN_TITLE, "Dropbox")
        row.add(DocumentsContract.Root.COLUMN_FLAGS, DocumentsContract.Root.FLAG_SUPPORTS_CREATE)   // TODO:
        row.add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, ROOT_DOCUMENT_ID)
        return result
    }
    
    override fun queryChildDocuments(
        parentDocumentId: String?,
        projection: Array<out String>?,
        sortOrder: String?
    ): Cursor {
        // TODO: Likely need to be more strict about projection (ie: map to supported)
        val result = MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION)
        val dropboxPath = if (parentDocumentId == ROOT_DOCUMENT_ID) "" else parentDocumentId
    
        try {
            val client = DropboxClientFactory.client
    
            var childFolders = client.files().listFolder(dropboxPath)
            while (true) {
                for (metadata in childFolders.entries) {
                    addDocumentRow(result, metadata)
                }
    
                if (!childFolders.hasMore) {
                    break
                }
    
                childFolders = client.files().listFolderContinue(childFolders.cursor)
            }
        } catch(e: IllegalStateException) { // Test if we can attempt auth thru the provider
            context?.let {
                Auth.startOAuth2Authentication(it, appKey)   // TODO: appKey
            }
        }
        return result
    }
    
    override fun queryDocument(documentId: String?, projection: Array<out String>?): Cursor {
        // TODO: Likely need to be more strict about projection (ie: map to supported)
        val result = MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION)
    
        try {
            val client = DropboxClientFactory.client
            val metadata = client.files().getMetadata(documentId)
            addDocumentRow(result, metadata)
        } catch(e: IllegalStateException) { // Test if we can attempt auth thru the provider
            context?.let {
                Auth.startOAuth2Authentication(it, appKey)   // TODO: appKey
            }
        }
        return result
    }
    

    错误:

    java.lang.IllegalArgumentException: String 'path' does not match pattern
        at com.dropbox.core.v2.files.GetMetadataArg.<init>(GetMetadataArg.java:58)
        at com.dropbox.core.v2.files.GetMetadataArg.<init>(GetMetadataArg.java:80)
        at com.dropbox.core.v2.files.DbxUserFilesRequests.getMetadata(DbxUserFilesRequests.java:1285)
        at com.anthonymandra.cloudprovider.dropbox.DropboxProvider.queryDocument(DropboxProvider.kt:98)
        at android.provider.DocumentsProvider.query(DocumentsProvider.java:797)
        at android.content.ContentProvider$Transport.query(ContentProvider.java:240)
        at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:102)
        at android.os.Binder.execTransact(Binder.java:731)
    

    path ROOT_DOCUMENT_ID queryChildDocuments 第一

    我错过了什么?

    0 回复  |  直到 7 年前
        1
  •  3
  •   tfrysinger    6 年前

    我还编写了一个SAF DropBox实现,起初对此我也有点困惑。

    documentation :

    请注意以下几点:

    • 每个文档提供者报告一个或多个正在启动的“根” 指向探索文档树。每个根都有一个唯一的 列_ROOT_ID,它指向一个文档(一个目录) 表示该根下的内容。根是动态的 设计为支持多账户、瞬时USB等用例 存储设备,或用户登录/注销。
    • 每个根目录下都有一个文档。该文件指向1到N 文档,每个文档依次可以指向1到N个文档。
    • 每个存储后端都会按顺序显示各个文件和目录 使用唯一的列\文档\ ID引用它们。文档ID必须 必须是唯一的,一经发布不得更改,因为它们用于 跨设备重新启动授予持久URI。
    • 文档可以是可打开的文件(具有特定的MIME类型), 或包含其他文档的目录(带有 MIME_TYPE_DIR MIME TYPE)。
    • 每个文档可以具有不同的功能,如 列_标志。例如,FLAG_支持_WRITE, FLAG_支持_删除,FLAG_支持_缩略图。相同的 列_DOCUMENT _ID可以包含在多个目录中。

    第二颗子弹是关键子弹 .从queryRoots()返回后,对于返回的每个根,SAF都会调用queryDocument()。这实质上是创建列表中显示的“根文件夹”文档。我做的是在queryDocument()中检查传入的documentId是否与我给DocumentsContract的唯一值匹配。根queryRoots()调用中的列\u ROOT\u ID。如果是,那么您知道这个queryDocument()调用需要返回一个表示该根的文件夹。否则,我会在其他地方使用DropBox的路径作为我的documentId,所以我会在通过DbxClientV2的调用中使用该documentId值。

    下面是一些示例代码——请注意,在我的例子中,我创建了一个AbstractStorageProvider类,我的所有提供者(Dropbox、Instagram等)都是从该类扩展而来的。基类负责接收来自SAF的调用,并执行一些内务处理(如创建游标),然后调用实现类中的方法,以按照特定服务的要求填充游标:

    基层

    public Cursor queryRoots(final String[] projection) {
        Timber.d( "Lifecycle: queryRoots called");
    
        // If they are not paid up, they do not get to use any of these implementations
        if (!InTouchUtils.isLoginPaidSubscription()) {
            return null;
        }
    
        // Create a cursor with either the requested fields, or the default projection if "projection" is null.
        final MatrixCursor cursor = new MatrixCursor(projection != null ? projection : getDefaultRootProjection());
    
        // Classes that extend this one must implement this method
        addRowsToQueryRootsCursor(cursor);
    
        return cursor;
    }
    

    从DropboxProvider AddRowsToQueryRotScursors:

    protected void addRowsToQueryRootsCursor(MatrixCursor cursor) {
        // See if we need to init
        long l = System.currentTimeMillis();
        if ( !InTouchUtils.initDropboxClient()) {
            return;
        }
        Timber.d( "Time to test initialization of DropboxClient: %dms.", (System.currentTimeMillis() - l));
        l = System.currentTimeMillis();
        try {
            SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(Objects.requireNonNull(getContext()).getApplicationContext());
            String displayname = sharedPrefs.getString(getContext().getString(R.string.pref_dropbox_displayname_token_key),
                    getContext().getResources().getString(R.string.pref_dropbox_displayname_token_default));
    
            batchSize = Long.valueOf(Objects.requireNonNull(sharedPrefs.getString(getContext().getString(R.string.pref_dropbox_query_limit_key),
                    getContext().getResources().getString(R.string.pref_dropbox_query_limit_key_default))));
    
            final MatrixCursor.RowBuilder row = cursor.newRow();
    
            row.add(DocumentsContract.Root.COLUMN_ROOT_ID, <YOUR_UNIQUE_ROOTS_KEY_HERE>);
            row.add(DocumentsContract.Root.COLUMN_TITLE,
                    String.format(getContext().getString(R.string.dropbox_root_title),getContext().getString(R.string.app_name)));
            row.add(DocumentsContract.Root.COLUMN_SUMMARY,displayname+
                    getContext().getResources().getString(R.string.dropbox_root_summary));
            row.add(DocumentsContract.Root.COLUMN_FLAGS, DocumentsContract.Root.FLAG_SUPPORTS_RECENTS | DocumentsContract.Root.FLAG_SUPPORTS_SEARCH);
            row.add(DocumentsContract.Root.COLUMN_DOCUMENT_ID,<YOUR_UNIQUE_ROOT_FOLDER_ID_HERE>);
            row.add(DocumentsContract.Root.COLUMN_ICON, R.drawable.intouch_for_dropbox);
        } catch (Exception e) {
            Timber.d( "Called addRowsToQueryRootsCursor got exception, message was: %s", e.getMessage());
        }
        Timber.d( "Time to queryRoots(): %dms.", (System.currentTimeMillis() - l));
    }
    

    然后基类中的queryDocument()方法:

    @Override
    public Cursor queryDocument(final String documentId, final String[] projection) {
        Timber.d( "Lifecycle: queryDocument called for: %s", documentId);
    
        // Create a cursor with either the requested fields, or the default projection if "projection" is null.
        // Return a cursor with a getExtras() method, to avoid the immutable ArrayMap problem.
        final MatrixCursor cursor = new MatrixCursor(projection != null ? projection : getDefaultDocumentProjection()){
            Bundle cursorExtras = new Bundle();
            @Override
            public Bundle getExtras() {
                return cursorExtras;
    
            }
        };
        addRowToQueryDocumentCursor(cursor, documentId);
        return cursor;
    }
    

    以及DropboxProvider中的addRowToQueryDocumentCursor():

    protected void addRowToQueryDocumentCursor(MatrixCursor cursor,
                                               String documentId) {
    
        try {
            SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(Objects.requireNonNull(getContext()).getApplicationContext());
            String displayname = sharedPrefs.getString(getContext().getString(R.string.pref_dropbox_displayname_token_key),
                    getContext().getString(R.string.pref_dropbox_displayname_token_default));
            if ( !InTouchUtils.initDropboxClient()) {
                return;
            }
    
            if ( documentId.equals(<YOUR_UNIQUE_ROOTS_ID_HERE>)) {
                // root Dir
                Timber.d( "addRowToQueryDocumentCursor called for the root");
                final MatrixCursor.RowBuilder row = cursor.newRow();
                row.add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, <YOUR_UNIQUE_FOLDER_ID_HERE>);
                row.add(DocumentsContract.Document.COLUMN_DISPLAY_NAME,
                        String.format(getContext().getString(R.string.dropbox_root_title),
                                      getContext().getString(R.string.app_name)));
                row.add(DocumentsContract.Document.COLUMN_SUMMARY,displayname+
                        getContext().getString(R.string.dropbox_root_summary));
                row.add(DocumentsContract.Document.COLUMN_ICON, R.drawable.folder_icon_dropbox);
                row.add(DocumentsContract.Document.COLUMN_MIME_TYPE, DocumentsContract.Document.MIME_TYPE_DIR);
                row.add(DocumentsContract.Document.COLUMN_FLAGS, 0);
                row.add(DocumentsContract.Document.COLUMN_SIZE, null);
                row.add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, null);
                return;
            }
            Timber.d( "addRowToQueryDocumentCursor called for documentId: %s", documentId);
            DbxClientV2 mDbxClient = DropboxClientFactory.getClient();
            Metadata metadata = mDbxClient.files().getMetadata(documentId);
    
            if ( metadata instanceof FolderMetadata) {
                Timber.d( "Document was a folder");
                includeFolder(cursor, (FolderMetadata)metadata);
            } else {
                Timber.d( "Document was a file");
                includeFile(cursor, (FileMetadata) metadata);
            }
        } catch (Exception e ) {
            Timber.d( "Called addRowToQueryDocumentCursor got exception, message was: %s documentId was: %s.", e.getMessage(), documentId);
        }
    }
    
        2
  •  1
  •   CommonsWare    7 年前

    用于实现 DocumentsProvider 是有限的没有特别记录的保证。因此 文档提供者 真正应该实施的是尽可能少地假设这些调用的顺序。

    例如,我不会假设 queryRoots() 第一个被称为。如果第一次使用 文档提供者 因为这个过程恰好是存储访问框架UI。然而,考虑到客户端可以(小心地)持久化文档或文档树 Uri ,如果第一件事碰巧是一个使用持久化 乌里 .

    而且,在你的具体情况下,我不会假设 queryChildDocuments() 发生在之前或之后 queryDocument() .

    推荐文章