第十章:ContentProvider与数据共享
ContentProvider,说白了就是Android系统给应用之间共享数据开的一扇门。我刚开始接触Framework时,总觉得它就是个「高级数据库封装」,后来踩过几次坑才明白——它真正的价值在于跨进程通信的标准化。
10.1 ContentProvider原理
ContentProvider的核心,是Binder驱动的跨进程数据通道。每个Provider在AMS(ActivityManagerService)中注册时,会分配一个唯一的authority。你想想看,这就像给每个数据源发了个门牌号。
关键点:ContentProvider本身不存储数据,它只是数据的「代理人」。真正的数据可能在SQLite、文件、网络,甚至内存里。
我在项目中遇到过一个问题:某个Provider的query方法返回了空Cursor,但数据库里明明有数据。后来发现是Binder传输的Cursor对象超过了1MB的Binder事务缓冲区限制。嗯,这里要注意——大数据量一定要分页。
10.2 跨进程数据访问
跨进程访问ContentProvider,本质上是客户端通过ContentResolver发起Binder调用。流程是这样的:
- 客户端调用ContentResolver.query()
- 内部通过Binder调用到Provider所在进程
- Provider执行query,返回Cursor
- Cursor通过Binder跨进程传递(实际是CursorWindow的共享内存机制)
我个人习惯在Provider的query方法里加日志,方便排查调用来源。曾经有个第三方SDK频繁调用我的Provider,导致主线程卡顿——加个线程检查就解决了。
避坑指南:我曾经在ContentProvider的onCreate()里做了耗时操作,结果应用启动直接ANR。记住,Provider的onCreate()运行在主线程,千万别干重活。
10.3 Uri匹配机制
Uri匹配是ContentProvider的「路由系统」。每个Provider会注册一组Uri规则,匹配到对应操作。我习惯用UriMatcher来实现:
public class MyProvider extends ContentProvider {
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
private static final int CODE_USERS = 1;
private static final int CODE_USER_ID = 2;
static {
sUriMatcher.addURI("com.example.provider", "users", CODE_USERS);
sUriMatcher.addURI("com.example.provider", "users/#", CODE_USER_ID);
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
switch (sUriMatcher.match(uri)) {
case CODE_USERS:
// 查询所有用户
break;
case CODE_USER_ID:
// 查询单个用户
String userId = uri.getLastPathSegment();
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
}
}
为什么用#而不是*?#匹配数字,*匹配任意文本。我建议严格区分,避免Uri解析出错。
小技巧:UriMatcher的匹配是从上到下的,把更具体的规则放在前面。我曾经把通配符规则放在前面,导致精确匹配永远走不到。
10.4 Cursor与ContentObserver
Cursor是数据查询的结果集,但跨进程传递时有个特殊机制——它背后是CursorWindow,一块共享内存。客户端和Provider进程通过这块内存直接读写数据,避免了数据拷贝。
ContentObserver则是数据变化的「监听器」。当Provider的数据发生变化时,会通知所有注册的Observer。我习惯在数据变更后调用:
getContext().getContentResolver().notifyChange(uri, null);
这样所有监听这个Uri的Observer都会收到回调。曾经有个同事忘了调用notifyChange,导致UI一直显示旧数据——排查了半天才发现。
核心要点:ContentObserver的回调发生在Provider所在进程,但Observer本身是跨进程注册的。系统通过Binder把通知跨进程传递回去。
10.5 知识体系总览
下面这张图,是我梳理的ContentProvider核心知识结构。你看,它其实就围绕三个问题:数据怎么暴露?怎么访问?怎么通知变化?
10.6 实战经验总结
最后,分享几个我在项目中积累的经验:
- 权限控制:Provider的readPermission和writePermission一定要配,别裸奔。我曾经见过一个Provider没配权限,任何应用都能读用户数据。
- 批量操作:用ContentProviderOperation实现批量事务,比逐条调用高效得多。我优化过一个同步模块,批量操作后耗时从30秒降到2秒。
- 临时文件:大文件共享用FileProvider,别自己写文件传输逻辑。Android官方已经封装好了,直接用就行。
- 生命周期:Provider所在进程被杀后,所有Cursor都会失效。记得在客户端做重连机制。
个人建议:写Provider时,一定要考虑多线程并发。我习惯用ReadWriteLock来保护数据,读多写少的场景性能提升很明显。
ContentProvider这套机制,说白了就是Android系统对「数据共享」这个需求的标准化解决方案。理解它的原理,你就能写出更健壮、更高效的数据共享代码。嗯,这部分内容就到这里。