4、ContentResolver 操作:通过 ContentResolver 查询、插入、更新、删除数据
好,咱们接着聊。上一章我们把 ContentProvider 的底层原理和注册机制讲透了,这一章我带你上手实操——用 ContentResolver 去增删改查。
说白了,ContentResolver 就是客户端和 Provider 之间的桥梁。你作为 App 开发者,大部分时候根本不需要自己写 Provider,但一定会频繁调用 Resolver 去访问系统通讯录、媒体库、日历这些数据。所以这块儿必须吃透。
4.1 获取 ContentResolver 实例
在 Activity 或 Fragment 里,直接调用 getContentResolver() 就行。我习惯在 Application 或 BaseActivity 里提前持有这个实例,避免到处传 Context。
// 在 Activity 中
ContentResolver resolver = getContentResolver();
// 在非 Context 类中,需要传入 Context
ContentResolver resolver = context.getContentResolver();
Application 类获取 Resolver,避免内存泄漏。
4.2 查询数据(query)
查询是最常用的操作。ContentResolver 的 query() 方法返回一个 Cursor,你可以把它理解成数据库查询结果集的游标。
// 查询通讯录中的联系人
Uri uri = ContactsContract.Contacts.CONTENT_URI;
String[] projection = {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME
};
String selection = ContactsContract.Contacts.DISPLAY_NAME + " LIKE ?";
String[] selectionArgs = new String[]{"%张三%"};
String sortOrder = ContactsContract.Contacts.DISPLAY_NAME + " ASC";
Cursor cursor = resolver.query(
uri, // 内容 URI
projection, // 要查询的列(null 表示所有列)
selection, // 查询条件(WHERE 子句)
selectionArgs,// 条件参数
sortOrder // 排序
);
if (cursor != null) {
while (cursor.moveToNext()) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(
ContactsContract.Contacts._ID));
String name = cursor.getString(cursor.getColumnIndexOrThrow(
ContactsContract.Contacts.DISPLAY_NAME));
// 处理数据...
}
cursor.close(); // 千万别忘了关闭!
}
4.3 插入数据(insert)
插入操作返回新插入行的 URI,里面包含了自动生成的 ID。我个人习惯先构建 ContentValues,再调用 insert。
// 向通讯录插入一条联系人
ContentValues values = new ContentValues();
values.put(ContactsContract.Contacts.DISPLAY_NAME, "李四");
values.put(ContactsContract.Contacts.STARRED, 1); // 标记为星标联系人
Uri newUri = resolver.insert(
ContactsContract.Contacts.CONTENT_URI,
values
);
if (newUri != null) {
long newId = ContentUris.parseId(newUri);
Log.d("TAG", "插入成功,新ID: " + newId);
}
你想想看,为什么 insert 返回的是 URI 而不是直接返回 ID?因为 Provider 可能对 URI 做了路径映射,返回 URI 更灵活。嗯,这里要注意,有些 Provider 的 insert 实现可能不支持批量插入,需要逐条操作。
4.4 更新数据(update)
更新操作返回受影响的行数。我建议更新前先确认条件是否准确,避免误更新。
// 更新联系人姓名
ContentValues updateValues = new ContentValues();
updateValues.put(ContactsContract.Contacts.DISPLAY_NAME, "李四(已更新)");
String where = ContactsContract.Contacts._ID + " = ?";
String[] whereArgs = new String[]{"123"};
int rowsUpdated = resolver.update(
ContactsContract.Contacts.CONTENT_URI,
updateValues,
where,
whereArgs
);
if (rowsUpdated > 0) {
Log.d("TAG", "更新成功,影响行数: " + rowsUpdated);
}
4.5 删除数据(delete)
删除操作同样返回受影响的行数。我曾经在测试环境里误删了整张表的数据,还好有备份。所以生产环境一定要加条件。
// 删除指定联系人
String deleteWhere = ContactsContract.Contacts._ID + " = ?";
String[] deleteArgs = new String[]{"123"};
int rowsDeleted = resolver.delete(
ContactsContract.Contacts.CONTENT_URI,
deleteWhere,
deleteArgs
);
Log.d("TAG", "删除了 " + rowsDeleted + " 条记录");
4.6 批量操作(applyBatch)
当需要同时插入、更新多条记录时,用 applyBatch() 可以保证事务性。我一般在同步联系人、批量导入数据时用这个。
// 批量插入两条联系人
ArrayList<ContentProviderOperation> operations = new ArrayList<>();
operations.add(ContentProviderOperation.newInsert(
ContactsContract.Contacts.CONTENT_URI)
.withValue(ContactsContract.Contacts.DISPLAY_NAME, "王五")
.build());
operations.add(ContentProviderOperation.newInsert(
ContactsContract.Contacts.CONTENT_URI)
.withValue(ContactsContract.Contacts.DISPLAY_NAME, "赵六")
.build());
ContentProviderResult[] results = resolver.applyBatch(
ContactsContract.AUTHORITY,
operations
);
for (ContentProviderResult result : results) {
Log.d("TAG", "批量操作结果: " + result.uri);
}
4.7 核心流程一览
下面这张图把 ContentResolver 的完整调用链路画出来了,你一看就明白。
4.8 常见问题与避坑
| 问题 | 原因 | 解决方案 |
|---|---|---|
| query 返回 null | URI 不匹配或 Provider 未注册 | 检查 AndroidManifest 中的 authority 是否一致 |
| insert 返回 null | Provider 的 insert 实现返回了 null | 查看 Provider 源码,确认 URI 匹配逻辑 |
| update 返回 0 | 条件不匹配或没有权限 | 检查 selection 参数和权限声明 |
| delete 抛异常 | 数据库约束或外键冲突 | 检查 Provider 的异常处理,添加 try-catch |
| 跨进程数据不同步 | 没有调用 notifyChange | Provider 中必须调用 getContext().getContentResolver().notifyChange() |
adb shell content query --uri content://xxx 直接在命令行测试 Provider 是否正常工作,比写代码调试快多了。
好了,这一章的内容就这些。ContentResolver 的操作其实不复杂,核心就是记住四个方法加上批量操作。你只要把 URI、投影、条件这几个参数搞明白,剩下的就是熟练度的问题了。
公众号:蓝海资料掘金营,微信deep3321