Android ContentProvider 详解
一、ContentProvider 是什么
ContentProvider 是 Android 四大组件之一,用于跨应用数据共享 和统一的数据访问接口。它封装了底层数据存储(SQLite、文件、网络等),对外提供标准化的 CRUD 操作。
核心定位:
- 数据共享:不同应用之间安全地共享数据
- 统一接口 :对外提供标准化的
insert/query/update/delete/getType接口 - 权限控制:通过 URI 和权限机制控制数据访问粒度
- 底层透明:调用方无需关心数据实际存储方式(SQLite、文件、网络等)
注意:如果仅在应用内部使用数据,直接使用 SQLiteOpenHelper 或 Room 即可,无需 ContentProvider。
二、核心原理
架构设计
应用A(数据提供方) 应用B(数据消费方)
↓ ↓
ContentProvider(封装数据) ContentResolver(统一入口)
↓ ↓
SQLite / File / Network ContentResolver.query/insert/update/delete
↓ ↓
底层数据存储 通过 URI 定位 + 权限校验
URI 格式
content://com.example.provider/user/1
│ │ │ │
│ │ │ └── 具体记录 ID(可选)
│ │ └────── 表名 / 路径
│ └─────────────────────────── 授权标识(Authority)
└────────────────────────────────── 协议前缀(固定为 content://)
核心类
Context
↓
getContentResolver()
↓
ContentResolver(抽象类,应用层统一入口)
↓
ApplicationContentResolver(ContextImpl 内部类)
↓
ContentProviderProxy(Binder 代理,位于调用方进程)
↓
【Binder 跨进程】
↓
ContentProviderNative / ContentProvider.Transport(Binder 实体,位于提供方进程)
↓
ContentProvider(抽象基类,用户继承实现)
↓
具体 Provider 实现(如 UserProvider)
三、与 SQLite 的区别
| 特性 | ContentProvider | SQLiteOpenHelper / Room |
|---|---|---|
| 使用范围 | 跨应用数据共享 | 应用内部数据存储 |
| 访问方式 | 通过 URI + ContentResolver | 直接调用 DAO / SQL |
| 权限控制 | ✅ 支持细粒度权限控制 | ❌ 无 |
| 实现复杂度 | 高(需继承 ContentProvider) | 低 |
| 性能 | 有 IPC 开销 | 直接访问,性能更好 |
| 线程安全 | 系统管理 | 需自行管理 |
| 适用场景 | 通讯录、媒体库、日历等系统数据 | 应用私有数据 |
四、完整使用示例
1. 定义 Contract(契约类)
java
public class UserContract {
// Authority
public static final String AUTHORITY = "com.example.provider";
// URI 基础
public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + AUTHORITY);
// 表路径
public static final String PATH_USERS = "users";
// 完整 URI
public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon()
.appendPath(PATH_USERS)
.build();
// MIME 类型
public static final String CONTENT_TYPE =
"vnd.android.cursor.dir/vnd.com.example.users";
public static final String CONTENT_ITEM_TYPE =
"vnd.android.cursor.item/vnd.com.example.users";
// 表字段
public static class Columns {
public static final String _ID = BaseColumns._ID;
public static final String NAME = "name";
public static final String AGE = "age";
public static final String EMAIL = "email";
}
}
2. 实现 ContentProvider
java
public class UserProvider extends ContentProvider {
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
private static final int USERS = 100; // 多行
private static final int USER_ID = 101; // 单行
static {
sUriMatcher.addURI(UserContract.AUTHORITY, "users", USERS);
sUriMatcher.addURI(UserContract.AUTHORITY, "users/#", USER_ID);
}
private UserDbHelper mDbHelper;
@Override
public boolean onCreate() {
mDbHelper = new UserDbHelper(getContext());
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection,
@Nullable String selection, @Nullable String[] selectionArgs,
@Nullable String sortOrder) {
SQLiteDatabase db = mDbHelper.getReadableDatabase();
Cursor cursor;
switch (sUriMatcher.match(uri)) {
case USERS:
cursor = db.query("users", projection, selection, selectionArgs,
null, null, sortOrder);
break;
case USER_ID:
long id = ContentUris.parseId(uri);
cursor = db.query("users", projection,
"_id=?", new String[]{String.valueOf(id)},
null, null, sortOrder);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
// 注册内容观察者,数据变化时自动通知
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
SQLiteDatabase db = mDbHelper.getWritableDatabase();
long id;
switch (sUriMatcher.match(uri)) {
case USERS:
id = db.insert("users", null, values);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
if (id > 0) {
Uri resultUri = ContentUris.withAppendedId(uri, id);
// 通知观察者数据变化
getContext().getContentResolver().notifyChange(resultUri, null);
return resultUri;
}
throw new SQLException("Failed to insert row into " + uri);
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection,
@Nullable String[] selectionArgs) {
SQLiteDatabase db = mDbHelper.getWritableDatabase();
int rows;
switch (sUriMatcher.match(uri)) {
case USERS:
rows = db.delete("users", selection, selectionArgs);
break;
case USER_ID:
long id = ContentUris.parseId(uri);
rows = db.delete("users", "_id=?",
new String[]{String.valueOf(id)});
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
if (rows > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return rows;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values,
@Nullable String selection, @Nullable String[] selectionArgs) {
SQLiteDatabase db = mDbHelper.getWritableDatabase();
int rows;
switch (sUriMatcher.match(uri)) {
case USERS:
rows = db.update("users", values, selection, selectionArgs);
break;
case USER_ID:
long id = ContentUris.parseId(uri);
rows = db.update("users", values, "_id=?",
new String[]{String.valueOf(id)});
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
if (rows > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return rows;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
switch (sUriMatcher.match(uri)) {
case USERS:
return UserContract.CONTENT_TYPE;
case USER_ID:
return UserContract.CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
}
}
3. SQLite 数据库辅助类
java
public class UserDbHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "users.db";
private static final int DATABASE_VERSION = 1;
public UserDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE users (" +
"_id INTEGER PRIMARY KEY AUTOINCREMENT," +
"name TEXT NOT NULL," +
"age INTEGER," +
"email TEXT)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS users");
onCreate(db);
}
}
4. AndroidManifest 注册
xml
<provider
android:name=".provider.UserProvider"
android:authorities="com.example.provider"
android:exported="true"
android:readPermission="com.example.provider.READ_USER"
android:writePermission="com.example.provider.WRITE_USER"
android:grantUriPermissions="true" />
5. 其他应用访问(消费方)
java
// 查询
public void queryUsers(Context context) {
Cursor cursor = context.getContentResolver().query(
UserContract.CONTENT_URI,
new String[]{UserContract.Columns._ID,
UserContract.Columns.NAME,
UserContract.Columns.AGE},
null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
long id = cursor.getLong(cursor.getColumnIndex(UserContract.Columns._ID));
String name = cursor.getString(cursor.getColumnIndex(UserContract.Columns.NAME));
int age = cursor.getInt(cursor.getColumnIndex(UserContract.Columns.AGE));
Log.d("User", "id=" + id + ", name=" + name + ", age=" + age);
}
cursor.close();
}
}
// 插入
public void insertUser(Context context, String name, int age, String email) {
ContentValues values = new ContentValues();
values.put(UserContract.Columns.NAME, name);
values.put(UserContract.Columns.AGE, age);
values.put(UserContract.Columns.EMAIL, email);
Uri uri = context.getContentResolver().insert(UserContract.CONTENT_URI, values);
Log.d("User", "Inserted: " + uri);
}
// 更新
public void updateUser(Context context, long id, String newName) {
ContentValues values = new ContentValues();
values.put(UserContract.Columns.NAME, newName);
Uri uri = ContentUris.withAppendedId(UserContract.CONTENT_URI, id);
int rows = context.getContentResolver().update(uri, values, null, null);
Log.d("User", "Updated " + rows + " rows");
}
// 删除
public void deleteUser(Context context, long id) {
Uri uri = ContentUris.withAppendedId(UserContract.CONTENT_URI, id);
int rows = context.getContentResolver().delete(uri, null, null);
Log.d("User", "Deleted " + rows + " rows");
}
6. 注册内容观察者(监听数据变化)
java
public class UserObserver extends ContentObserver {
public UserObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
Log.d("UserObserver", "Data changed: " + uri);
// 刷新 UI 或重新查询
}
}
// 注册
UserObserver observer = new UserObserver(new Handler(Looper.getMainLooper()));
getContentResolver().registerContentObserver(
UserContract.CONTENT_URI,
true, // true = 监听子 URI 变化
observer
);
// 注销
getContentResolver().unregisterContentObserver(observer);
五、系统 ContentProvider
Android 系统提供了大量内置 ContentProvider:
| Provider | Authority | 用途 |
|---|---|---|
| Contacts | com.android.contacts |
联系人数据 |
| MediaStore | media |
图片、音频、视频 |
| Calendar | com.android.calendar |
日历事件 |
| CallLog | com.android.calllog |
通话记录 |
| Browser | com.android.browser |
浏览器书签/历史 |
| Settings | settings |
系统设置 |
| Downloads | downloads |
下载管理 |
读取系统联系人示例
java
// 需要权限:READ_CONTACTS
Cursor cursor = getContentResolver().query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
new String[]{
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER
},
null, null, null
);
if (cursor != null) {
while (cursor.moveToNext()) {
String name = cursor.getString(0);
String number = cursor.getString(1);
Log.d("Contact", name + ": " + number);
}
cursor.close();
}
六、注意事项
1. 权限声明
xml
<!-- 提供方声明权限 -->
<permission android:name="com.example.provider.READ_USER"
android:protectionLevel="normal" />
<permission android:name="com.example.provider.WRITE_USER"
android:protectionLevel="normal" />
<!-- 消费方申请权限 -->
<uses-permission android:name="com.example.provider.READ_USER" />
<uses-permission android:name="com.example.provider.WRITE_USER" />
2. 线程安全
java
// ⚠️ ContentProvider 的 CRUD 方法可能被多线程同时调用
// 需确保 SQLiteDatabase 操作线程安全
// ✅ 使用 synchronized 或 SQLiteDatabase 自带锁
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
synchronized (this) {
// 数据库操作
}
}
3. 性能优化
java
// ⚠️ 每次 IPC 有性能开销,避免频繁小数据量操作
// ✅ 批量操作使用 ContentProviderOperation
ArrayList<ContentProviderOperation> operations = new ArrayList<>();
for (User user : users) {
operations.add(ContentProviderOperation.newInsert(UserContract.CONTENT_URI)
.withValue(UserContract.Columns.NAME, user.name)
.withValue(UserContract.Columns.AGE, user.age)
.build());
}
getContentResolver().applyBatch(UserContract.AUTHORITY, operations);
4. 数据变化通知
java
// ✅ 数据修改后必须通知观察者,否则消费方无法感知变化
getContext().getContentResolver().notifyChange(uri, null);
// 消费方注册观察者监听变化
getContentResolver().registerContentObserver(uri, true, observer);
5. Android 11+ 包可见性
xml
<!-- Android 11+ 需要声明要访问的其他应用 -->
<queries>
<provider android:authorities="com.example.provider" />
</queries>
6. 不要滥用 ContentProvider
java
// ❌ 错误:应用内部数据也用 ContentProvider
// 应用内部直接使用 Room / SQLiteOpenHelper 即可
// ✅ 正确:仅在需要跨应用共享数据时使用 ContentProvider
七、总结
| 问题 | 答案 |
|---|---|
| ContentProvider 是什么? | Android 跨应用数据共享组件,封装底层存储 |
| 核心原理? | 通过 URI 定位数据,AMS 调度,IPC 通信 |
| 与 SQLite 区别? | ContentProvider 是封装层,SQLite 是存储层;Provider 支持跨应用 |
| 适用场景? | 通讯录、媒体库等系统数据;应用间数据共享 |
| 现代替代方案? | 应用内部用 Room;跨应用用 FileProvider / 自定义 Provider |
| 注意事项? | 权限控制、线程安全、IPC 性能开销、数据变化通知 |
一句话总结:ContentProvider 是 Android 的"数据共享网关",通过 URI 和权限机制实现安全跨应用数据访问;应用内部数据管理优先使用 Room,仅在需要跨应用共享时才使用 ContentProvider。
| 层级 | 实现机制 |
|---|---|
| 应用层 | ContentResolver 统一入口,封装底层细节 |
| Framework | ApplicationContentResolver → ActivityThread 缓存管理 |
| 系统服务 | AMS 统一调度 Provider 启动、权限校验、观察者管理 |
| IPC 机制 | Binder 跨进程调用,CursorWindow 共享内存零拷贝传输 |
| 线程模型 | onCreate 主线程,CRUD 默认 Binder 线程池 |
| 权限控制 | AMS checkComponentPermission,基于 UID 校验 |
另一句话总结 :ContentProvider 的底层本质是 Binder IPC + 共享内存 + AMS 进程调度,调用方通过 ContentResolver 获取 Binder 代理,跨进程调用 Provider 的 CRUD 方法;Cursor 数据通过 ashmem 共享内存实现零拷贝传输,权限校验由 AMS 在调用前统一拦截。