怎么完成一个DocumentProvider

前言

假如你做了一个云盘类的app,或者能够保存用户导入的配置。用户在未来必定需求获取这些文件,一个办法是写一个Activity,向一个文件办理软件相同把他们列出来。可是这个有一个问题是用户有必要进入app 才干拜访。

现在有一个解决方案是完成一个DocumentProvider

步骤

DocumentProvider 承继自Content Provider。

  1. 首先在Manifest 中注册这个Provider。

    <provider
        android:name=".StorageProvider"
        android:authorities="com.storyteller_f.ping.documents"
        android:grantUriPermissions="true"
        android:exported="true"
        android:permission="android.permission.MANAGE_DOCUMENTS">
        <intent-filter>
            <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
        </intent-filter>
    </provider>
    
  2. 创立这个Provider

    
    class StorageProvider : DocumentsProvider() {
        companion object {
            private val DEFAULT_ROOT_PROJECTION: Array<String> = arrayOf(
                DocumentsContract.Root.COLUMN_ROOT_ID,
                DocumentsContract.Root.COLUMN_MIME_TYPES,
                DocumentsContract.Root.COLUMN_FLAGS,
                DocumentsContract.Root.COLUMN_ICON,
                DocumentsContract.Root.COLUMN_TITLE,
                DocumentsContract.Root.COLUMN_SUMMARY,
                DocumentsContract.Root.COLUMN_DOCUMENT_ID,
                DocumentsContract.Root.COLUMN_AVAILABLE_BYTES
            )
            private val DEFAULT_DOCUMENT_PROJECTION: Array<String> = arrayOf(
                DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                DocumentsContract.Document.COLUMN_MIME_TYPE,
                DocumentsContract.Document.COLUMN_FLAGS,
                DocumentsContract.Document.COLUMN_DISPLAY_NAME,
                DocumentsContract.Document.COLUMN_LAST_MODIFIED,
                DocumentsContract.Document.COLUMN_SIZE
            )
            private const val TAG = "StorageProvider"
        }
        override fun onCreate(): Boolean {
            return true
        }
    }
    
  3. 重写queryRoot

    在咱们的需求下只要一种状况,拜访的途径应该是/data/data/packagename/,不过假如一个app 支撑多用户,那就应该有多个目录。所以需求一个办法奉告文件办理使用咱们的app 有多少个

    override fun queryRoots(projection: Array<out String>?): Cursor {
        Log.d(TAG, "queryRoots() called with: projection = $projection")
        val flags = DocumentsContract.Root.FLAG_LOCAL_ONLY or DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD
        return MatrixCursor(projection ?: DEFAULT_ROOT_PROJECTION).apply {
            newRow().apply {
                add(DocumentsContract.Root.COLUMN_ROOT_ID, DEFAULT_ROOT_ID)
                add(DocumentsContract.Root.COLUMN_MIME_TYPES, DocumentsContract.Document.MIME_TYPE_DIR)
                add(DocumentsContract.Root.COLUMN_FLAGS, flags)
                add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_launcher_foreground)
                add(DocumentsContract.Root.COLUMN_TITLE, context?.getString(R.string.app_name))
                add(DocumentsContract.Root.COLUMN_SUMMARY, "your data")
                add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, "/")
            }
        }
    }
    

    回来值是Cursor 就像一个数据库连接查询数据时相同,不过咱们没有操作数据库,回来的数据是文件系统。所以咱们回来一个MatrixCursor。

    回来的数据没有什么真实数据,仅仅作为一个进口。

    此时翻开Android 系统的文件办理,就能看到了

    [Android]如何实现一个DocumentProvider

    此时点击的话就会溃散,因为还没有重写queryDocument

  4. 重写queryDocument

    /**
     * Return metadata for the single requested document. You should avoid
     * making network requests to keep this request fast.
     *
     * @param documentId the document to return.
     * @param projection list of {@link Document} columns to put into the
     *            cursor. If {@code null} all supported columns should be
     *            included.
     * @throws AuthenticationRequiredException If authentication is required from
     *            the user (such as login credentials), but it is not guaranteed
     *            that the client will handle this properly.
     */
    public abstract Cursor queryDocument(String documentId, String[] projection)
            throws FileNotFoundException;        
    

    这个办法才是真正回来数据的当地,上面回来root 更像是盘符,这里是回来root 盘符对应的根文件夹。数据结构是一个树形结构,queryDocument 回来根文件夹,根只要一个,所以回来的数据也是只要一个。假如你回来了多个数据应该也是无效的,系统会忽略掉。

    一般咱们需求依据documentId (盘符)回来,不过这里只要一个,就不做区分了。

    override fun queryDocument(documentId: String?, projection: Array<out String>?): Cursor {
        Log.d(TAG, "queryDocument() called with: documentId = $documentId, projection = $projection")
        return MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION).apply {
            val root = context?.filesDir?.parentFile ?: return@apply
            newRow().apply {
                add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, "/")
                add(DocumentsContract.Document.COLUMN_MIME_TYPE, DocumentsContract.Document.MIME_TYPE_DIR)
                val flags = 0
                add(DocumentsContract.Document.COLUMN_FLAGS, flags)
                add(DocumentsContract.Document.COLUMN_DISPLAY_NAME, root.name)
                add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, root.lastModified())
                add(DocumentsContract.Document.COLUMN_SIZE, 0)
            }
        }
    }
    
  5. 重写getChildDocument

    override fun queryChildDocuments(parentDocumentId: String?, projection: Array<out String>?, sortOrder: String?): Cursor {
        Log.d(TAG, "queryChildDocuments() called with: parentDocumentId = $parentDocumentId, projection = $projection, sortOrder = $sortOrder")
        return MatrixCursor(projection ?: DEFAULT_DOCUMENT_PROJECTION).apply {
            handleChild(parentDocumentId)
        }
    }
    

    咱们只需求依据parentDocumentId 来搜索就能够。就像一个遍历出一个文件夹的子文件夹和子文件相同。

  6. 除了上面介绍的,还能够承继翻开document,创立document,显示document thumbnail 等功能。

代码

代码能够在这里看 github.com/storyteller…