如何實現一個DocumentProvider
前言
假如你做了一個雲盤類的app,或者可以保存用戶導入的配置。用戶在未來肯定需要獲取這些文件,一個辦法是寫一個Activity,向一個文件管理軟件一樣把他們列出來。但是這個有一個問題是用戶必須進入app 才能訪問。
現在有一個解決方案時實現一個DocumentProvider
步驟
DocumentProvider 繼承自Content Provider。
-
首先在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>
-
創建這個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 } }
-
重寫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
-
重寫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) } } }
-
重寫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 來搜索就可以。就像一個遍歷出一個文件夾的子文件夾和子文件一樣。
除了上面介紹的,還可以繼承打開document,創建document,顯示document thumbnail 等功能。