1. 引言

在 Android 中,数据共享是应用开发中非常重要的一部分。为了在不同应用之间共享数据,Android 提供了 ContentProvider 机制,通过该机制,应用能够访问其他应用的数据或向其他应用提供自己的数据。结合 SQLite 数据库、ContentProviderContentResolverUriMatcher,我们可以方便地实现数据的持久化存储、查询、插入、更新和删除操作,同时还能够保证数据访问的安全性。

本文将介绍如何结合 SQLite 数据库、ContentProviderContentResolverUriMatcher 实现一个完整的应用数据共享系统,并通过一个具体的示例进行详细说明。

2. 基本概念

2.1 SQLite 数据库

SQLite 是 Android 中推荐的本地数据库,它是一个轻量级的关系型数据库管理系统。SQLite 可以嵌入到应用中,支持 SQL 查询、数据持久化存储。

  • 优点:开销小、无需服务器,适合单机应用。
  • 缺点:功能相对较简单,适用于小型应用和中小规模的数据存储。

2.2 ContentProvider

ContentProvider 是一个提供数据存取接口的组件。它允许不同的应用共享数据,支持跨进程调用和跨应用访问。ContentProvider 通过 URI(统一资源标识符)来标识数据资源,并通过 SQL 查询操作来处理数据。

  • 方法query()insert()update()delete()getType() 等。
  • 作用:提供统一的接口,允许不同应用之间共享数据,避免直接暴露内部数据库结构。

2.3 ContentResolver

ContentResolver 是一个访问 ContentProvider 的接口。通过 ContentResolver,我们可以向 ContentProvider 发起查询、插入、更新和删除操作。

2.4 UriMatcher

UriMatcher 是一个帮助 ContentProvider 匹配 URI 的工具,它根据 URI 的路径来判断进行操作的数据表或者数据项。通过 UriMatcher,我们可以根据传入的 URI 判断用户请求的是整个数据表的数据,还是某一条特定的数据记录。

  • 作用:根据 URI 匹配不同的数据处理操作,帮助 ContentProvider 做出正确的响应。

2.5 概念及它们之间的关系

  • ContentProvider

    • 作用:用于对外暴露应用的数据(例如 SQLite 数据库、文件等),支持跨进程或跨应用数据共享。
    • 关键方法onCreate()(初始化)、query()insert()update()delete()getType()
  • UriMatcher

    • 作用:帮助 ContentProvider 根据传入的 URI 判断需要操作的数据类型或数据集合。
    • 工作原理:预先注册一系列 URI 模式,每个模式对应一个整数代码;在请求时根据 URI 返回相应代码,以便在代码中进行判断和分支处理。
  • ContentResolver

    • 作用:作为数据访问者,由外部(或本应用内其他组件)调用,用于向 ContentProvider 发送数据操作请求(如查询、插入等)。

    • 使用方式

      :直接在 Activity、Service 等继承了 Context 的组件中调用,例如:

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

这三者一起工作,实现了应用间(或应用内)的数据共享:

  1. ContentResolver 发起请求,传入一个 URI。
  2. ContentProvider 通过内部的 UriMatcher 匹配 URI,判断请求的数据类型。
  3. 根据匹配结果,ContentProvider 调用相应的数据库操作,并返回数据(Cursor、Uri、影响行数等)。

3. 使用 SQLite + ContentProvider + ContentResolver + UriMatcher 实现数据共享

3.1 数据库实现

首先,我们需要创建一个 SQLite 数据库并定义数据表。下面是一个简单的 SQLiteOpenHelper 类,它管理一个名为 diary 的表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class DiaryDatabaseHelper(context: Context) : SQLiteOpenHelper(context, "DiaryDB", null, 1) {

override fun onCreate(db: SQLiteDatabase) {
val createTableSQL = """
CREATE TABLE diary (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT,
date INTEGER,
content TEXT
)
""".trimIndent()
db.execSQL(createTableSQL)
}

override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("DROP TABLE IF EXISTS diary")
onCreate(db)
}
}

在这个数据库中,diary 表包含 id(主键)、title(标题)、date(日期)和 content(内容)等字段。

3.2 ContentProvider 实现

接下来,我们要实现一个 ContentProvider,它将使用我们刚刚定义的数据库。通过 ContentProvider,外部应用可以通过 URI 来访问我们提供的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
class DiaryProvider : ContentProvider() {

companion object {
const val AUTHORITY = "com.example.providerdemo.diaryprovider"
const val TABLE_DIARY = "diary"
val CONTENT_URI: Uri = Uri.parse("content://$AUTHORITY/$TABLE_DIARY")
const val DIARY_DIR = 1
const val DIARY_ITEM = 2
}

private lateinit var dbHelper: DiaryDatabaseHelper
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
addURI(AUTHORITY, TABLE_DIARY, DIARY_DIR)

/*DIARY_Dir是一个整数值,用来标识这个 URI 模式对应的操作。在后面的 ContentProvider 中,我们会使用这个整数值来确定执行哪些操作(例如查询、插入、更新、删除等)。DIARY_1Dir 是你在代码中定义的常量。比如后面的return when (uriMatcher.match(uri)) {
DIARY_DIR ->...}*/

addURI(AUTHORITY, "$TABLE_DIARY/#", DIARY_ITEM)

}

override fun onCreate(): Boolean {
dbHelper = DiaryDatabaseHelper(context!!)
return true
}

override fun query(uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?, sortOrder: String?): Cursor? {
val db = dbHelper.readableDatabase
return when (uriMatcher.match(uri)) {
DIARY_DIR -> db.query(TABLE_DIARY, projection, selection, selectionArgs, null, null, sortOrder)
DIARY_ITEM -> {
val id = uri.lastPathSegment
db.query(TABLE_DIARY, projection, "id=?", arrayOf(id), null, null, sortOrder)
}
else -> null
}
}

override fun insert(uri: Uri, values: ContentValues?): Uri? {
val db = dbHelper.writableDatabase
return when (uriMatcher.match(uri)) {
DIARY_DIR -> {
val id = db.insert(TABLE_DIARY, null, values)
Uri.withAppendedPath(CONTENT_URI, id.toString())
}
else -> null
}
}

override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?): Int {
val db = dbHelper.writableDatabase
return when (uriMatcher.match(uri)) {
DIARY_DIR -> db.update(TABLE_DIARY, values, selection, selectionArgs)
DIARY_ITEM -> {
val id = uri.lastPathSegment
db.update(TABLE_DIARY, values, "id=?", arrayOf(id))
}
else -> 0
}
}

override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
val db = dbHelper.writableDatabase
return when (uriMatcher.match(uri)) {
DIARY_DIR -> db.delete(TABLE_DIARY, selection, selectionArgs)
DIARY_ITEM -> {
val id = uri.lastPathSegment
db.delete(TABLE_DIARY, "id=?", arrayOf(id))
}
else -> 0
}
}

override fun getType(uri: Uri): String? {
return when (uriMatcher.match(uri)) {
DIARY_DIR -> "vnd.android.cursor.dir/vnd.$AUTHORITY.$TABLE_DIARY"
DIARY_ITEM -> "vnd.android.cursor.item/vnd.$AUTHORITY.$TABLE_DIARY"
else -> null
}
}
}

3.3 UriMatcher 配置

在 ContentProvider 中,我们使用 UriMatcher 来判断传入的 URI 属于哪个类型。UriMatcher 会匹配表名(diary)或者某个具体的记录(通过 ID 来识别)。

1
2
3
4
val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
addURI(AUTHORITY, TABLE_DIARY, DIARY_DIR) // 匹配整个 diary 表
addURI(AUTHORITY, "$TABLE_DIARY/#", DIARY_ITEM) // 匹配单条 diary 记录
}
  • DIARY_DIR:匹配整个表的 URI。
  • DIARY_ITEM:匹配单条数据记录的 URI,带有 # 通配符,表示记录的 ID。

3.4 ContentResolver 的使用

ContentResolver 是外部应用用来访问 ContentProvider 的接口。在外部应用中,我们可以通过 ContentResolver 来执行查询、插入、更新和删除操作。例如,查询所有标题包含“心情”的日记:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//构造 content:// 开头的 Uri,它用于标识数据库中的某条数据。
val uri = Uri.parse("content://com.example.providerdemo.diaryprovider/diary")
val projection = arrayOf("id", "title", "date", "content")
val selection = "title LIKE ?"
val selectionArgs = arrayOf("%心情%")
val sortOrder = "date DESC"

// 查询操作
val cursor = contentResolver.query(uri, projection, selection, selectionArgs, sortOrder)
cursor?.use {
while (it.moveToNext()) {
val id = it.getInt(it.getColumnIndexOrThrow("id"))
val title = it.getString(it.getColumnIndexOrThrow("title"))
val date = it.getLong(it.getColumnIndexOrThrow("date"))
val content = it.getString(it.getColumnIndexOrThrow("content"))
println("ID: $id, Title: $title, Date: $date, Content: $content")
}
}

4. 总结

通过结合 SQLite 数据库、ContentProviderContentResolverUriMatcher,我们可以实现一个完整的跨应用数据共享机制。ContentProvider 作为数据提供者,向外部应用提供访问接口,ContentResolver 则充当请求者的角色,发起查询、插入、更新和删除等操作,UriMatcher 则帮助我们根据 URI 匹配具体的数据操作。

这种架构不仅保证了数据共享的高效性和安全性,同时还使得应用内部数据管理变得更加清晰和规范。