软件编程
位置:首页>> 软件编程>> Android编程>> Android如何实现一个DocumentProvider示例详解

Android如何实现一个DocumentProvider示例详解

作者:storytellerF  发布时间:2022-12-30 20:10:13 

标签:Android,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 系统的文件管理,就能看到了

Android如何实现一个DocumentProvider示例详解

此时点击的话就会崩溃,因为还没有重写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;        

这个方法才是真正返回数据的地方,上面返回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)
       }
   }
}

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

代码可以在这里看 github.com/storyteller&hellip;

来源:https://juejin.cn/post/7180568940174082106

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com