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 负责跨进程通信
权限机制负责安全控制