02、Content URI、UriMatcher 与 MIME 类型

一、Content URI 是什么

Content URI 是 ContentProvider 暴露数据的地址。

标准格式:

content://authority/path/id

示例:

content://com.example.databasetest.provider/book
content://com.example.databasetest.provider/book/1

拆开看:

部分 示例 说明
scheme content:// 表示这是 ContentProvider URI
authority com.example.databasetest.provider Provider 的唯一标识
path /book 表示访问哪类数据
id /1 可选,表示访问某一条具体数据

二、authority 的作用

authority 用来唯一标识一个 ContentProvider。

一般写法:

应用包名.provider

例如:

com.example.databasetest.provider

Manifest 中注册时也要写同一个 authority:

<provider
    android:name=".DatabaseProvider"
    android:authorities="com.example.databasetest.provider"
    android:exported="true" />

调用方访问时:

Uri uri = Uri.parse("content://com.example.databasetest.provider/book");

系统就是通过 authority 找到对应的 Provider。

三、path 的作用

path 用来区分 Provider 内部的不同数据集合。

例如同一个 Provider 中有两张表:

Book
Category

可以设计成:

content://com.example.databasetest.provider/book
content://com.example.databasetest.provider/category

这样 bookcategory 就分别表示不同资源。

四、集合 URI 与单条 URI

ContentProvider 通常会设计两类 URI。

1. 集合 URI

表示访问一类数据:

content://com.example.databasetest.provider/book

含义:

访问 Book 表中的多条数据

2. 单条 URI

表示访问某一条数据:

content://com.example.databasetest.provider/book/1

含义:

访问 Book 表中 id = 1 的数据

五、Uri.parse() 的正确写法

正确写法:

Uri uri = Uri.parse("content://com.example.databasetest.provider/book");

错误写法:

Uri uri = new Uri.parse("content://com.example.databasetest.provider/book");

原因:

parse()Uri 的静态方法,不需要 new

六、ContentUris 工具类

如果要拼接 ID,不建议手动字符串拼接,可以使用 ContentUris

拼接 ID

Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
Uri itemUri = ContentUris.withAppendedId(uri, 1);

结果:

content://com.example.databasetest.provider/book/1

解析 ID

long id = ContentUris.parseId(itemUri);

适合处理这种 URI:

content://com.example.databasetest.provider/book/1

七、为什么需要 UriMatcher

Provider 收到调用时,只拿到一个 Uri

它需要判断:

这个 URI 是访问 book 列表?
还是访问 book 中某一条?
还是访问 category 列表?
还是访问 category 中某一条?

这就需要 UriMatcher

八、UriMatcher 通配符

UriMatcher 支持两个常见通配符:

通配符 含义 示例
* 匹配任意文本 book/*
# 匹配数字 book/#

常用规则:

book       -> Book 集合
book/#     -> Book 单条数据
category   -> Category 集合
category/# -> Category 单条数据

九、UriMatcher 基本写法

public class DatabaseProvider extends ContentProvider {

    public static final int BOOK_DIR = 0;
    public static final int BOOK_ITEM = 1;
    public static final int CATEGORY_DIR = 2;
    public static final int CATEGORY_ITEM = 3;

    public static final String AUTHORITY = "com.example.databasetest.provider";

    private static final UriMatcher uriMatcher;

    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
        uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
        uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
        uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
    }
}

注意:

final

不要写成:

fianl

十、match() 的使用

query() 中:

@Override
public Cursor query(
        Uri uri,
        String[] projection,
        String selection,
        String[] selectionArgs,
        String sortOrder
) {
    SQLiteDatabase db = dbHelper.getReadableDatabase();

    switch (uriMatcher.match(uri)) {
        case BOOK_DIR:
            return db.query("Book", projection, selection, selectionArgs, null, null, sortOrder);

        case BOOK_ITEM:
            String bookId = uri.getPathSegments().get(1);
            return db.query("Book", projection, "id = ?", new String[]{bookId}, null, null, sortOrder);

        case CATEGORY_DIR:
            return db.query("Category", projection, selection, selectionArgs, null, null, sortOrder);

        case CATEGORY_ITEM:
            String categoryId = uri.getPathSegments().get(1);
            return db.query("Category", projection, "id = ?", new String[]{categoryId}, null, null, sortOrder);

        default:
            throw new IllegalArgumentException("Unknown URI: " + uri);
    }
}

uri.getPathSegments() 的结果示例:

URI: content://com.example.databasetest.provider/book/1

pathSegments[0] = book
pathSegments[1] = 1

十一、getType() 是什么

getType(Uri uri) 用于返回某个 URI 对应的数据 MIME 类型。

它不是最常用的方法,但自定义 Provider 时必须实现。

十二、MIME 类型规则

Android 对 ContentProvider 的 MIME 类型有固定约定。

1. 多条数据

如果 URI 表示一个数据集合,使用:

vnd.android.cursor.dir/vnd.authority.path

示例:

content://com.example.databasetest.provider/book

对应:

vnd.android.cursor.dir/vnd.com.example.databasetest.provider.book

2. 单条数据

如果 URI 表示一条具体数据,使用:

vnd.android.cursor.item/vnd.authority.path

示例:

content://com.example.databasetest.provider/book/1

对应:

vnd.android.cursor.item/vnd.com.example.databasetest.provider.book

重点区别:

URI 类型 MIME 中间部分
集合 URI vnd.android.cursor.dir
单条 URI vnd.android.cursor.item

十三、getType() 示例

@Override
public String getType(Uri uri) {
    switch (uriMatcher.match(uri)) {
        case BOOK_DIR:
            return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.book";

        case BOOK_ITEM:
            return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.book";

        case CATEGORY_DIR:
            return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.category";

        case CATEGORY_ITEM:
            return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.category";

        default:
            throw new IllegalArgumentException("Unknown URI: " + uri);
    }
}

十四、常见 URI 设计错误

1. authority 不一致

Manifest:

android:authorities="com.example.app.provider"

代码访问:

Uri.parse("content://com.example.other.provider/book")

这样系统找不到 Provider。

2. path 没有加入 UriMatcher

访问:

content://com.example.app.provider/user

但是只注册了:

uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);

结果 match() 会返回 NO_MATCH

3. 单条 URI 没有处理 ID

注册了:

uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);

但是在代码中仍然当成集合查询,没有取出 ID,就会造成逻辑错误。

4. MIME 类型写错

单条 URI 应该返回:

vnd.android.cursor.item/...

不是:

vnd.android.cursor.dir/...

十五、核心记忆

content://authority/path/id

对应关系:

部分 记忆
content:// ContentProvider 专用 scheme
authority 找哪个 Provider
path 找 Provider 中哪类数据
id 找某一条数据
UriMatcher 把 URI 映射成业务代码
getType() 根据 URI 返回 MIME 类型