Content URI 详解:URI 结构、URI 匹配、MIME 类型、UriMatcher 使用
各位同学,今天我们来聊聊 Content Provider 的门牌号——Content URI。说白了,URI 就是 Content Provider 对外暴露的地址。没有它,外部应用根本找不到你的数据在哪儿。
我记得刚接触 Android 时,总觉得 URI 这东西不就是个字符串吗?有什么好讲的。直到有一次,我写了个 Provider,结果第三方应用死活连不上,查了半天才发现是 URI 路径写错了。嗯,从那以后,我再也不敢小看这个「门牌号」了。
URI 结构:拆开来看其实很简单
一个标准的 Content URI 长这样:
content://com.example.app.provider/user/1
我们把它拆开来看,其实就四个部分:
| 组成部分 | 示例 | 说明 |
|---|---|---|
| Scheme | content:// | 固定前缀,表示这是一个 Content Provider 的 URI |
| Authority | com.example.app.provider | 唯一标识,通常是包名+provider 后缀 |
| Path | /user | 数据表或数据集合的路径 |
| ID | /1 | 可选,表示某一条具体记录 |
我个人习惯把 Authority 想象成「小区名」,Path 是「楼栋号」,ID 是「门牌号」。这样一想,是不是好理解多了?
关键点:没有 ID 的 URI 表示操作整个集合,有 ID 的 URI 表示操作单条记录。这是 Content Provider 设计中的核心约定。
URI 匹配:UriMatcher 是怎么工作的?
你想想看,当外部应用传进来一个 URI,Provider 怎么知道它想查哪个表?是查 user 还是查 order?这时候就需要 UriMatcher 登场了。
UriMatcher 说白了就是一个「路径路由器」。它把 URI 字符串解析成整数常量,然后你在代码里 switch 这个常量就行。
来看一个我实际项目中的例子:
public class MyProvider extends ContentProvider {
private static final int USERS = 1;
private static final int USER_ID = 2;
private static final int ORDERS = 3;
private static final int ORDER_ID = 4;
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
uriMatcher.addURI("com.example.app.provider", "user", USERS);
uriMatcher.addURI("com.example.app.provider", "user/#", USER_ID);
uriMatcher.addURI("com.example.app.provider", "order", ORDERS);
uriMatcher.addURI("com.example.app.provider", "order/#", ORDER_ID);
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
switch (uriMatcher.match(uri)) {
case USERS:
// 查询所有用户
break;
case USER_ID:
// 查询单个用户
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
}
}
这里有个细节要注意:# 表示匹配任意数字,* 表示匹配任意文本。我在项目中遇到过有人把 # 和 * 搞混,结果匹配总是不对。记住:# 是数字通配符,* 是文本通配符。
小技巧:UriMatcher 的匹配是从上到下的,所以如果你有多个相似的路径,把更具体的放在前面。比如 /user/me 和 /user/#,应该先添加 /user/me。
MIME 类型:告诉调用方你返回的是什么
MIME 类型,说白了就是告诉调用方:「我返回的数据是啥格式」。Content Provider 支持两种 MIME 类型:
- 单条记录:
vnd.android.cursor.item/vnd.yourdomain.yourentity - 多条记录:
vnd.android.cursor.dir/vnd.yourdomain.yourentity
举个例子,如果我的 Provider 返回用户列表,MIME 类型应该是:
vnd.android.cursor.dir/vnd.example.user
如果返回单个用户:
vnd.android.cursor.item/vnd.example.user
为什么要区分?因为调用方可以根据 MIME 类型判断是操作集合还是单条记录。比如 ContentResolver 的 notifyChange() 方法,会根据 MIME 类型决定如何刷新 UI。
避坑指南:我曾经在项目中忘记重写 getType() 方法,结果返回了 null。调用方拿到 null 后直接崩溃了。记住:getType() 必须返回正确的 MIME 类型,不能偷懒。
UriMatcher 使用的最佳实践
用多了 UriMatcher,我总结了几条经验:
- 常量命名要有意义:别用
CODE_1、CODE_2这种,用USERS、USER_ID一目了然。 - 匹配失败要抛异常:default 分支一定要抛出
IllegalArgumentException,方便调试。 - 路径设计要合理:别把路径搞得太深,一般两层就够了。比如
/user/1可以,/user/1/order/2就有点过了。
说到路径设计,我画了一张图,帮你理清 URI 匹配的整个流程:
看到没?整个流程其实很清晰。URI 进来,UriMatcher 匹配,switch 分发,各走各的路。
总结一下
Content URI 这块,说白了就是三件事:
- URI 结构:content:// + Authority + Path + ID,记住这个公式就行。
- URI 匹配:用 UriMatcher 把字符串转成整数,switch 处理。
- MIME 类型:告诉调用方你返回的是单条还是多条数据。
嗯,内容就这些。下一章我们聊聊 Content Provider 的 CRUD 操作,到时候会用到今天讲的 URI 知识。各位先把这块吃透,后面就顺了。