Content Provider

提供了统一对外的数据访问接口,可以把自己应用的数据提供出去给的应用程序使用,可以对你的数据进行增删改查,例如 通讯录就是其中的一种。

要想创建一个 Content Provider 需要进行下面几部操作:

  1. 继承 ContentProvider 类,重写 onCreate、getType、insert、update、query、delete 方法。
  2. 在 AndroidManifest.xml 文件中对 Provider 进行注册。
    <provider android:name="classpath" android:authorities="com.exmaple.provider.applicationname" />
  3. 发布 Content Provider 的 URI 地址,每个Content Provider 都应该使用一个公有的静态 CONTENT_URI 属性来公开它的授权
    Public static final Uri CONTENT_URI = Uri.parse("content://com.exmaple.provider.applicationname/elements")

通过 Android 中的 Uri 访问 Content Provider,Uri主要包含下面几部分:
content://com.example.earthquake/contentprovider/quakes/1

  • scheme:Content Provider 的 scheme 是 content://
  • content:是 Content Provider 的唯一标示,外部程序可以根据这个标示找到对应的 Content Provider
  • path:标示我们要操作的数据

我们一般提供全部查询和指定条数两种支持查询模式:其中一个非常有用的类 UriMatcher,它是一个非常有用的类,可以分析 URI 并确定它的形势。

// 创建两个常量来区分不同的 URI 请求
private static final int ALLROWS = 1;
private static final int SINGLE_ROW = 2;

private static final UriMatcher uriMatcher;

// 填充 UriMatcher 对象,其中 element 结尾的 URI 对应请求所有的数据
// 以 elements/rowid 结尾的 URI 代表请求单行数据
static {
    uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    uriMatcher.addURI("com.exmaple.message.provider", "elements", ALLROWS);
    uriMatcher.addURI("com.exmaple.message.provider", "elements/#", SINGLE_ROW);
}

在同一个 Content Provider 中,可以使用同样的技术来公开其他的 URI,这些 URI 代表了不同的数据子集或数据库中不同的表。

区分了全表和单行查询后,就可以很容易的使用 SQLiteQueryBuilder 类对一个查询应用额外的选择条件,如下面的代码所示:

SQLiteQueryBuilder sqb = new SQLiteQueryBuilder();

// 如果是行查询,用传入的行限制结果集
switch (uriMatcher.match(uri)) {
case SINGLE_ROW:
    String rowID = uri.getPathSegments().get(1);
    sqb.appendWhere(KEY_ID + "=" + rowID);
default:
    break;
}

常用帮助方法:

UriMatcher:用于匹配对应的 Uri 路径
ContentUris:用于操作 Uri 路径后面的 id 部分

  • withAppendId 通过该方法把 主 Uri 和 对应的 id 传入进去,组装返回对应的 uri 对象

当应用程序启动时,每个 Content Provider 的 onCreate 处理程序会再应用程序的主线程中被调用。
和之前的数据库操作一样,最好使用 SQLiteOpenHelper 来延迟打开(必要的地方创建)底层的数据库,直到 Content Provider 的查询或事务方法需要时再打开或创建它。

@Override
public boolean onCreate() {
    // 构建一个底层数据库
    // 延迟打开数据库,直到需要执行
    // 一个查询或者事务时再打开
    sqLiteHelper = new MessageSQLiteHelper(getContext(),
        MessageSQLiteHelper.DATABASE_NAME, null,
        MessageSQLiteHelper.DATABASE_VERSION);

    return true;
}


实现 Content Provider 查询

要想使用 Content Provider 就必须实现 query 和 getType 方法。Content Provider 使用这些方法来访问底层数据,无需知道底层的数据结构和实现。
Content Provider 的最常见场景就是访问一个 SQLite 数据库,但在这些方法中,你可以访问任何的数据库(包括文件或应用程序实例变量)。
UriMatcher 对象应用于完善事务处理和查询请求,而 SQLiteQueryBuilder 是执行基于行查询的便利辅助类。

public class StudentContentProvider extends ContentProvider {

    private SimpleSQLiteHelper sqLiteHelper;

    // 创建两个常量来区分不同的 URI 请求
    public static final int ALLROWS = 1;
    public static final int SINGLE_ROW = 2;

    public static final Uri CONTENT_URI = Uri.parse("content://com.example.contentprovider/students");

    private static final UriMatcher uriMatcher;

    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI("com.example.contentprovider", "students", ALLROWS);
        uriMatcher.addURI("com.example.contentprovider", "students/#",
                SINGLE_ROW);
    }

    @Override
    public boolean onCreate() {
        sqLiteHelper = new SimpleSQLiteHelper(getContext(),
                SimpleSQLiteHelper.DATABASE_NAME, null,
                SimpleSQLiteHelper.DATABASE_VERSION);
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {

        SQLiteDatabase db;
        try {
            db = sqLiteHelper.getWritableDatabase();
        } catch (SQLiteException e) {
            db = sqLiteHelper.getReadableDatabase();
        }

        // 设定分组和聚合条件
        String groupBy = null;
        String having = null;

        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();

        switch (uriMatcher.match(uri)) {
        case SINGLE_ROW:
            String rowID = uri.getPathSegments().get(1);
            queryBuilder.appendWhere(SimpleSQLiteHelper.KEY_ID + " = " + rowID);
            break;

        default:
            break;
        }

        // 指定要执行查询的表,根据需要,这可以是一个特定的表或者一个连接
        queryBuilder.setTables(SimpleSQLiteHelper.DATABASE_TABLE);

        // 执行查询操作
        Cursor cursor = queryBuilder.query(db, projection, selection,
                selectionArgs, groupBy, having, sortOrder);

        return cursor;
    }

    @Override
    public String getType(Uri uri) {
        // 为一个 Content Provider URI 返回一个字符串,它标识了 MIME 类型
        switch (uriMatcher.match(uri)) {
        case ALLROWS:
            return "vnd.android.cursor.dir/vnd.example.contentprovider.elemental";

        case SINGLE_ROW:
            return "vnd.android.cursor.item/vnd.example.contentprovider.elemental";

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

    @Override
    public Uri insert(Uri uri, ContentValues values) {

        // 打开一个可写的数据库连接
        SQLiteDatabase db = sqLiteHelper.getWritableDatabase();

        String nullColumnHack = null;

        // 向表中插入值
        long id = db.insert(SimpleSQLiteHelper.DATABASE_TABLE, nullColumnHack,
                values);

        if (id > -1) {

            // 构造并返回插入行的 URI
            Uri insertedId = ContentUris.withAppendedId(CONTENT_URI, id);

            // 通知所有的观察者,数据集已经改变
            getContext().getContentResolver().notifyChange(insertedId, null);

            return insertedId;
        }

        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {

        // 打开一个可写的数据库连接
        SQLiteDatabase db = sqLiteHelper.getWritableDatabase();

        // 如果是行 URI,限定删除行为指定的行
        switch (uriMatcher.match(uri)) {
        case SINGLE_ROW:
            String rowID = uri.getPathSegments().get(1);
            selection = SimpleSQLiteHelper.KEY_ID + " = " + rowID
                    + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ")" : "");
            break;

        default:
            break;
        }

        // 要想返回删除的项的数量,必须指定一条 where 子句,要删除所有的行并返回一个值,则传入 1
        if (selection == null) {
            selection = "1";
        }

        // 执行删除操作
        int deleteCount = db.delete(SimpleSQLiteHelper.DATABASE_TABLE,
                selection, selectionArgs);

        // 通知所有的观察者,数据集已经改变
        getContext().getContentResolver().notifyChange(uri, null);


        return deleteCount;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
            String[] selectionArgs) {

        // 打开一个可写的数据库连接
        SQLiteDatabase db = sqLiteHelper.getWritableDatabase();

        // 如果是行 URI,限定删除行为指定的行
        switch (uriMatcher.match(uri)) {
        case SINGLE_ROW:
            String rowID = uri.getPathSegments().get(1);
            selection = SimpleSQLiteHelper.KEY_ID
                    + " = "
                    + rowID
                    + (!TextUtils.isEmpty(selection) ? " AND (" + selection
                            + ")" : "");
            break;

        default:
            break;
        }

        // 执行更新
        int updateCount = db.update(SimpleSQLiteHelper.DATABASE_TABLE, values,
                selection, selectionArgs);

        // 通知所有的观察者,数据集已经改变
        getContext().getContentResolver().notifyChange(uri, null);

        return updateCount;
    }

}


Content Resolver

可以通过 Content Resolver 来访问 Content Provider 提供的数据。可以通过 getContext().getContentResolver() 方法来获取一个 Content Resolver 对象。


监听 Content Provider 中的数据变化

如果 共享的数据发生变化,可以在 Content Provider 发生数据变化时调用 getCotentResolver().notifyChange(uri, null) 来通知注册在此 URI 上的访问者


ytwman
294 声望7 粉丝