Android如何实现一个DocumentProvider示例详解
作者:storytellerF 发布时间:2022-12-30 20:10:13
前言
假如你做了一个云盘类的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;
这个方法才是真正返回数据的地方,上面返回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…
来源:https://juejin.cn/post/7180568940174082106


猜你喜欢
- 微信公众号提供了微信支付、微信优惠券、微信H5红包、微信红包封面等等促销工具来帮助我们的应用拉新保活。但是这些福利要想正确地发放到用户的手里
- 本文实例为大家分享了Android 自定义弹窗提示的具体代码,供大家参考,具体内容如下Java文件:private void showSet
- 比如在窗体中显示时间:错误思路一:我在窗体结构函数中写入一个死循环,每隔一秒显示一次当前时间public Form6() &n
- 本文实例讲述了Android编程中EditText限制文字输入的方法。分享给大家供大家参考,具体如下:Android的编辑框控件EditTe
- 题意Description相信大家都做过"A+B Problem"了吧,这道题是它的加强版。输入两个整数 A , B ,
- 1.更新同步方式:/** * 三个参数 * the path of the node
- 前言在介绍Dubbo之前先了解一下基本概念:Dubbo是一个RPC框架,RPC,即Remote Procedure Call(远程过程调用)
- 本文实例为大家分享了Android实现截长图功能的具体代码,供大家参考,具体内容如下先看看手机自带的长截屏功能: 机型: viv
- 当采用HttpClient httpClient = HttpClients.createDefault() 实例化的时候。会导致Addre
- 今天上班中午吃饱之后、逛博客溜达看到一道题:数组反转 晚上回家洗完澡没事情做,就自己练习一把。public static cla
- 本文实例所述为Android天气预报之解析天气数据的代码,可实现获取HttpGet对象读取天气网站天气数据,并从数据中解析出天气数据,比如温
- 本文实例分析了C#中float的取值范围和精度。分享给大家供大家参考。具体分析如下:float类型的表现形式:默认情况下,赋值运算符右侧的实
- 1、检测权限因为dump系统lsass内存和sam注册表需要管理员权限,所以首先需要对当前进程上下文权限做判断。public static
- 一些初学Android的朋友可能会遇到JAVA的数据类型之间转换的苦恼,例如,整数和float,double型之间的转换,整数和String
- 我们使用Spring整合Quartz开发,本实例采用数据库模式的demo。xml文件配置如下:<?xml version="
- 大家好,今天我们继续来学习Android 8.0系统的适配。之前我们已经讲到了,Android 8.0系统最主要需要进行适配的地方有两处:应
- 本文实例讲述了Android编程之canvas绘制各种图形的方法。分享给大家供大家参考,具体如下:1、首先说一下canvas类:Class
- Cloneable这个接口设计得十分奇葩,不符合正常人的使用习惯,然而用这个接口的人很多也很有必要,所以还是有必要了解一下这套扭曲的机制。以
- 本文实例讲述了Spring和Hibernate的整合操作。分享给大家供大家参考,具体如下:一 web配置<?xml version=&
- C#连接本地.mdf文件:项目中右键点击,新增——数据——基于服务的数据库,项目下直接生成.mdf数据库文件,后台(数据库的写入用参数传递)