Android 跨程序共享数据

概述

ContentProvider 主要用于在不同的应用程序之间实现数据共享的功能,提供了一套完整的机制,允许一个程序访问另一个程序的数据,同时还能保证数据的安全

ContentProvider 的用法一般有两种:一种是使用现在的 ContentProvider 读取和操作相应程序中的数据;另一种是创建自己的 ContentProvider,给程序的数据提供外部访问接口

ContentResolver

对于一个应用程序来说,要想访问 ContentProvider 中共享的数据,就要借助 ContentResolver 类,可以通过 Context 中的 getContentResolver() 方法获取该类的实例

ContentResolver 提供了一系列方法用于对数据进行增删改查,与 SQLiteDatabase 相似。但不同的是,ContentResolver 中的增删改查方法不接收表名参数,而是使用一个 Uri 参数代替,这个参数称为内容 URI

内容 URI 给 ContentProvider 中的数据建立了唯一标识符,它主要由两部分组成:authority 和 path,authority 用于区分不同的应用程序,一般采用应用包名的方式进行命名,比如某个应用的包名是 com.example.app,那么 authority 就可以命名为 com.example.app.provider。path 则是用于对同一程序中不同的表做区分,通常添加到 authority 的后面,比如某个应用程序的数据库存在两个 table1 和 table2,这时可以将 path 分别命名为 /table1 和 /table2。然后将 authority 和 path 进行组合,内容 URI 就变成了 com.example.app.provider/table1com.example.app.provider/table2。我们还需在字符串的头部加上协议声明,因此内容 URI 最标准格式如下:content://com.example.app.provider/table1content://com.example.app.provider/table1

在得到内容 URI 字符串之后,我们还需要将它解析成 Uri 对象才可以作为参数传入,代码如下:

val uri = Uri.parse("content://com.example.app.provider/table1")

再使用这个 Uri 对象查询 table1 表中的数据,代码如下:

val cursor = contentResolver.query(
	uri,
    projection,
    selection,
    selectionArgs,
    sortOrder)

下表是 query() 方法的参数说明

query() 方法参数 对应 SQL 部分 描述
uri from table_name 指定查询某个应用程序下的某一张表
projection select column1, column2 指定查询的列名
selection where column = value 指定 where 的约束条件
selectionArgs 为 where 中的占位符提供具体的值
sortOrder order by column1, column2 指定查询结果的排序方式

根据返回的 Cursor 对象,我们就可以将数据从 Cursor 对象中遍历读取出来了

while(cursor.moveToNext()) {
    val column1 = cursor.getString(cursor.getColumnIndex("column1"))
    val column2 = cursor.getInt(cursor.getColumnIndex("column2"))
}
cursor.close()

掌握了查询操作,剩下的增加、修改、删除操作就更不在话下了

查询操作是将待添加的数据组装到 ContentValues 中,然后调用 ContentResolver 的 insert() 方法

val values = contentValuesOf("column1" to "next", "column2" to 1)
contentResolver.insert(uri, values)

如果我们想要更新这条新添加的数据,然后将 column1 的值清空,可以借助 ContentResolver 的 update() 方法实现

val values = contentValuesOf("column1" to "")
contentResolver.update(uri, values, "column1 = ? and column2 = ?", arrayOf("text", "1"))

最后,可以调用 ContentResolver 的 delete() 方法将这条数据删除

contentResolver.delete(uri, "column2 = ?", arrayOf("1"))

ContentProvider

如果希望自己的程序的数据能被共享,可以新建一个类去继承 ContentProvider 的方式去实现。ContentProvider 类中有六个抽象方法,我们在使用子类继承它的时候,需要将这六个方法全部重写

class MyProvider : ContentProvider() {

    /*
     * 向 ContentProvider 中添加一条数据
     */
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        return null
    }

    /*
     * 从 ContentProvider 中查询数据
     */
    override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? {
        return null
    }
	
    /*
     * 初始化 ContentProvider 时调用,通常在这里完成对数据库的创建和升级
     * 返回 true 表示 ContentProvider 初始化成功,返回 false 表示失败
     */
    override fun onCreate(): Boolean {
        return false
    }

    /*
     * 更新 ContentProvider 中已有的数据
     */
    override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int {
        return 0
    }

    /*
     * 从 ContentProvider 中删除数据
     */
    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
        return 0
    }

    /*
     * 根据传入的内容 URI 返回相应的 MIME 类型
     */
    override fun getType(uri: Uri): String? {
        return null
    }
}

可以看到,很多方法里带有 uri 这个参数,这个参数也正是调用 ContentResolver 的增删改查方法时传递过来的。我们需要对传入的 uri 参数进行解析,从中分析出调用方期望访问的表和数据

我们可以在这个内容 URI 的后面加上一个 id

content://com.example.app.provider/table1/1

表示调用方期望访问的是 com.example.app 这个应用的 table1 表中 id 为 1 的数据

我们还可以使用通配符分别匹配这两种格式的内容 URI,规则如下

  • * 表示匹配任意长度的任意字符
  • # 表示匹配任意长度的数字

一个能匹配任意表的内容 URI 格式就可以写成:

content://com.example.app.provider/*

一个能够匹配 table1 表中任意一行数据的内容 URI 格式就可以写成:

content://com.example.app.provider/#

接着,我们再借助 UriMatcher 这个类就可以实现匹配内容 Uri 的功能,该类提供了一个 addURI() 方法,这个方法接收三个参数,可以分别把 authority、path 和一个自定义代码传进去,当调用 UriMatcher 的 match() 方法时,就可以将一个 Uri 对象传进去,返回值是某个能匹配这个 Uri 对象所对应的自定义代码

class MyProvider : ContentProvider() {

    private val table1Dir = 0
    private val table1Item = 1
    private val table2Dir = 2
    private val table2Item = 3

    private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)

    init {
        uriMatcher.addURI("com.example.app.provider", "table1", table1Dir)
        uriMatcher.addURI("com.example.app.provider", "table1/#", table1Item)
        uriMatcher.addURI("com.example.app.provider", "table2", table2Dir)
        uriMatcher.addURI("com.example.app.provider", "table2/#", table2Item)

    }
    
    ...

    override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? {
        when (uriMatcher.match(uri)) {
            table1Dir -> {
                // 查询 table1 表中的所有数据
            }
            table1Item -> {
                // 查询 table1 表中的单条数据
            }
            table2Dir -> {
                // 查询 table2 表中的所有数据
            }
            table2Item -> {
                // 查询 table2 表中的单条数据
            }
        }
    }
}

上述代码只是以 query() 方法为例做了个示范,其实 insert()、update()、delete() 这几个方法的实现是差不多的

除此以外,还有 getType() 方法,用于获取 Uri 对象所对应的 MIME 类型。一个内容 URI 所对应的 MIME 字符串主要由三个部分组成,Android 对这三个部分做了如下格式规定:

  • 必须以 vnd 开头

  • 如果内容 URI 以路径结尾,则后接 android.cursor.dir/

    如果内容 URI 以 id 结尾,则后接 android.cursor.item/

  • 最后接上 vnd.<authority>.<path>

继续完善 MyProvider 的内容,实现 getType() 方法中的逻辑

class MyProvider : ContentProvider() {

	...

    override fun getType(uri: Uri) = when (uriMatcher.match(uri)) {
        table1Dir -> "vnd.android.cursor.dir/vnd.com.example.app.provider.table1"
        table1Item -> "vnd.android.cursor.item/vnd.com.example.app.provider.table1"
        table2Dir -> "vnd.android.cursor.dir/vnd.com.example.app.provider.table2"
        table2Item -> "vnd.android.cursor.item/vnd.com.example.app.provider.table2"
        else -> null
    }
}

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>