01、ContentProvider基础与ContentResolver使用
01、ContentProvider 基础与 ContentResolver 使用
一、ContentProvider 是什么
ContentProvider,中文通常叫“内容提供者”或“内容提供器”。
它是 Android 四大组件之一,主要作用是:
以统一、安全、可控的方式,把一个应用的数据暴露给另一个应用访问。
比如:
- 通讯录 App 把联系人数据暴露给其他 App 查询;
- 相册/文件类 App 把图片、文件通过
content://URI 分享给其他 App; - 自己的业务 App 想把一部分数据库数据提供给另一个 App 使用;
- 桌面小组件、搜索建议、同步框架需要访问应用内部数据。
ContentProvider 本身不是数据库,它只是“数据访问入口”。真正的数据可以来自:
| 数据来源 | 是否可以通过 ContentProvider 暴露 |
|---|---|
| SQLite | 可以 |
| Room | 可以,本质上仍然可映射到底层查询 |
| 文件 | 可以 |
| SharedPreferences | 可以,但通常不推荐直接暴露 |
| 内存数据 | 可以 |
| 网络缓存数据 | 可以 |
| 图片、音频、视频 | 可以,常见于 FileProvider 或 MediaStore |
二、为什么需要 ContentProvider
Android 每个 App 默认运行在自己的沙箱中,一个 App 不能随便读另一个 App 的私有数据。
如果 App A 想让 App B 访问自己的部分数据,就需要一个“受控出口”。
ContentProvider 解决的核心问题是:
既能跨应用共享数据
又不能把所有私有数据都暴露出去
它的优势是:
| 优势 | 说明 |
|---|---|
| 标准化 | Android 官方提供统一接口 |
| 跨进程 | 底层通过 Binder 完成 IPC |
| 安全可控 | 可以用权限、URI、路径限制访问范围 |
| 面向表结构友好 | 天然支持 Cursor、行、列、查询条件 |
| 可与系统能力结合 | 如联系人、日历、媒体库、搜索建议等 |
三、什么时候需要 ContentProvider
不是所有数据库都需要 ContentProvider。
需要使用的情况
| 场景 | 是否适合 |
|---|---|
| 数据要暴露给其他 App | 适合 |
| 要提供文件给其他 App 打开 | 适合,常用 FileProvider |
| 要实现搜索建议 | 适合 |
| 要给桌面 Widget 提供数据 | 适合 |
| 要与系统联系人、媒体库等交互 | 适合 |
| 多个自家 App 共享数据 | 适合,但要做好权限控制 |
不需要使用的情况
| 场景 | 建议 |
|---|---|
| 数据只在当前 App 内部使用 | 直接用 Room / SQLite / DataStore |
| 只是 Activity 之间传数据 | 用 Intent / ViewModel / Repository |
| 只是进程内共享对象 | 不需要 ContentProvider |
| 只是网络请求缓存 | 直接使用本地数据库或缓存层 |
一句话:
内部数据内部用,不必上 ContentProvider;跨应用共享数据,才考虑 ContentProvider。
四、ContentProvider 与 ContentResolver 的关系
ContentProvider 是“服务端”,ContentResolver 是“客户端”。
App B:调用方
↓
ContentResolver
↓
content://com.example.app.provider/book
↓
系统根据 authority 找到目标 Provider
↓
App A:ContentProvider
↓
SQLite / 文件 / 其他数据源
| 角色 | 说明 |
|---|---|
ContentProvider |
数据提供方,实现 CRUD |
ContentResolver |
数据访问方,调用 CRUD |
Uri |
数据地址,告诉系统访问哪个 Provider 的哪类数据 |
Cursor |
查询结果集 |
ContentValues |
插入或更新时传递的键值对数据 |
五、ContentResolver 的获取方式
在 Context 中可以直接获取:
ContentResolver resolver = getContentResolver();
在 Kotlin 中:
val resolver = contentResolver
注意:
getContentResolver()
不是:
getContextResolver()
后者是错误写法。
六、ContentResolver 的 CRUD 方法
ContentResolver 提供了类似数据库的增删改查方法:
| 方法 | 作用 |
|---|---|
query() |
查询数据 |
insert() |
插入数据 |
update() |
更新数据 |
delete() |
删除数据 |
getType() |
获取 URI 对应 MIME 类型 |
openInputStream() |
读取 URI 指向的文件数据 |
openOutputStream() |
写入 URI 指向的文件数据 |
七、query() 方法参数
常见查询方法:
Cursor cursor = getContentResolver().query(
uri,
projection,
selection,
selectionArgs,
sortOrder
);
参数含义:
| 参数 | 含义 | 类比 SQL |
|---|---|---|
uri |
要访问的数据地址 | 表名或资源路径 |
projection |
要查询的列 | SELECT column1, column2 |
selection |
查询条件 | WHERE name = ? |
selectionArgs |
条件参数 | ? 的实际值 |
sortOrder |
排序方式 | ORDER BY id DESC |
示例:
Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
Cursor cursor = getContentResolver().query(
uri,
new String[]{"id", "name", "author"},
"price > ?",
new String[]{"50"},
"id DESC"
);
对应 SQL 思路大概是:
SELECT id, name, author
FROM Book
WHERE price > 50
ORDER BY id DESC;
但调用方并不会直接写完整 SQL,而是通过参数化接口让 Provider 处理。
八、读取 Cursor 数据
Java 写法:
Cursor cursor = null;
try {
Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
cursor = getContentResolver().query(
uri,
null,
null,
null,
null
);
if (cursor != null) {
while (cursor.moveToNext()) {
int id = cursor.getInt(cursor.getColumnIndexOrThrow("id"));
String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
String author = cursor.getString(cursor.getColumnIndexOrThrow("author"));
Log.d("ProviderDemo", "id=" + id + ", name=" + name + ", author=" + author);
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
Kotlin 写法:
val uri = Uri.parse("content://com.example.databasetest.provider/book")
contentResolver.query(
uri,
null,
null,
null,
null
)?.use { cursor ->
val idIndex = cursor.getColumnIndexOrThrow("id")
val nameIndex = cursor.getColumnIndexOrThrow("name")
val authorIndex = cursor.getColumnIndexOrThrow("author")
while (cursor.moveToNext()) {
val id = cursor.getInt(idIndex)
val name = cursor.getString(nameIndex)
val author = cursor.getString(authorIndex)
Log.d("ProviderDemo", "id=$id, name=$name, author=$author")
}
}
注意点:
query()可能返回null;Cursor用完必须关闭;- 推荐使用
getColumnIndexOrThrow(),字段不存在时能更快暴露问题; - Kotlin 推荐使用
use {}自动关闭。
九、insert() 插入数据
Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
ContentValues values = new ContentValues();
values.put("name", "Android开发艺术探索");
values.put("author", "任玉刚");
values.put("pages", 500);
values.put("price", 79.0);
Uri newUri = getContentResolver().insert(uri, values);
insert() 的返回值通常是新插入数据对应的 URI,例如:
content://com.example.databasetest.provider/book/1
十、update() 更新数据
Uri uri = Uri.parse("content://com.example.databasetest.provider/book/1");
ContentValues values = new ContentValues();
values.put("price", 69.0);
int rows = getContentResolver().update(
uri,
values,
null,
null
);
如果更新集合 URI:
Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
ContentValues values = new ContentValues();
values.put("price", 69.0);
int rows = getContentResolver().update(
uri,
values,
"name = ?",
new String[]{"Android开发艺术探索"}
);
返回值是受影响的行数。
十一、delete() 删除数据
删除单条:
Uri uri = Uri.parse("content://com.example.databasetest.provider/book/1");
int rows = getContentResolver().delete(
uri,
null,
null
);
按条件删除:
Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
int rows = getContentResolver().delete(
uri,
"price < ?",
new String[]{"10"}
);
返回值是删除的行数。
十二、ContentProvider 和 SQLiteDatabase 的区别
| 对比项 | SQLiteDatabase | ContentProvider |
|---|---|---|
| 使用范围 | App 内部数据库操作 | 跨应用数据访问接口 |
| 是否跨进程 | 通常不跨进程 | 支持跨进程 |
| 操作入口 | 表名、SQL、SQLite API | URI、ContentResolver |
| 安全控制 | 主要靠 App 沙箱 | 可配置权限、URI 授权 |
| 数据来源 | SQLite | SQLite、文件、内存、网络缓存等 |
| 典型结果 | Cursor | Cursor / Uri / 文件流 |
可以这样理解:
SQLiteDatabase 更像“数据库操作工具”
ContentProvider 更像“对外开放的数据接口层”
十三、常见系统 ContentProvider
Android 系统本身也提供了很多 Provider:
| Provider | 作用 |
|---|---|
| ContactsContract | 联系人 |
| CalendarContract | 日历 |
| MediaStore | 图片、视频、音频 |
| Settings | 系统设置 |
| CallLog | 通话记录 |
| DocumentsProvider | 文档访问框架 |
例如访问图片通常会用到 MediaStore;访问联系人通常会用到 ContactsContract。
十四、核心记忆
ContentProvider 的核心不是“数据库”,而是“跨应用数据访问接口”。
重点记住:
ContentResolver 负责访问
ContentProvider 负责提供
Uri 负责定位数据
Cursor 负责返回查询结果
ContentValues 负责传递插入/更新数据
Binder 负责跨进程通信
权限机制负责安全控制
01、ContentProvider基础与ContentResolver使用
https://lautung.com/archives/01%E3%80%81contentprovider%E5%9F%BA%E7%A1%80%E4%B8%8Econtentresolver%E4%BD%BF%E7%94%A8