如何實現一個DocumentProvider 如何實現一個DocumentProvider

如何實現一個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 系統的文件管理,就能看到了

    此時點擊的話就會崩潰,因爲還沒有重寫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;        
    

    這個方法纔是真正返回數據的地方。數據結構是一個樹形結構,queryDocument 返回是真正文件的根。返回的數據也是隻有一個。如果你返回了多個數據應該也是無效的,系統會忽略掉。

    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 等功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章