四大组件

ContentProvider 相关

2019-07-29  本文已影响2人  wind_sky

一. 介绍

ContentProvider 管理对结构化数据集的访问。它们封装数据,并提供用于定义数据安全性的机制。 ContentProvider 是连接一个进程中的数据与另一个进程中运行的代码的标准界面。ContentProvider 以一个或多个表(与

在关系型数据库中找到的表类似)的形式将数据呈现给外部应用。 行表示提供程序收集的某种数据类型的实例,行中的每个列表示为实例收集的每条数据。我们对于ContentProvider 的主要使用就是:

(1)访问系统数据,如联系人、多媒体等

(2)存储数据

(3)提供给其他应用访问本应用数据的接口

二. 使用

应用从具有 ContentResolver 客户端对象的内容提供程序访问数据。 此对象具有调用提供程序对象(ContentProvider 的某个具体子类的实例)中同名方法的方法。 ContentResolver 方法可提供持续存储的基本“CRUD”(创建、检索、更新和删除)功能。

客户端应用进程中的 ContentResolver 对象和拥有提供程序的应用中的 ContentProvider 对象可自动处理跨进程通信。 ContentProvider 还可充当其数据存储区和表格形式的数据外部显示之间的抽象层。

注意:访问ContentProvider 时可能需要一些权限,这在后面会提到。

1. 创建ContentProvider

在着手开发ContentProvider 之前要考虑一下是否真的有必要这样做,如果我们想提供下列一项或多项功能,则需要进行开发:

如果完全是在自己的应用中使用,则不需要ContentProvider,使用 SQLite 数据库即可。

开发ContentProvider 有以下步骤:

(1)数据设计

数据存储的方式上面也有介绍,但是也有一些设计的技巧:

(2)设计URI

内容 URI 是用于在提供程序中标识数据的 URI。内容 URI 包括整个ContentProvider 的符号名称(其authorities)和一个指向表的名称(path)。 当调用客户端方法来访问提供程序中的表时,该表的内容 URI 将是其参数之一。

比如用户字典的URI

content://user_dictionary/words

其中,user_dictionary 字符串是提供程序的authority ,words 字符串是表的路径。 字符串 content://(schema)始终显示,并将此标识为内容 URI。

ContentProvider 通常具有单一authority,该authority 充当其 Android 内部名称,通常可以采用 "包名+ .provider"的形式,如 "com.whx.test.provider" ;

通常通过追加指向单个表的路径来根据authority 创建内容 URI。 例如,有两个表:table1 和 table2,则可以通过合并authority 来生成 内容 URI com.whx.test.provider/table1 和 com.whx.test.provider/table2。path 并不限定于单个段,也无需为每一级path都创建一个表。

处理URI 中的ID

按照惯例,ContentProvider 通过接受末尾具有行所对应 ID 值的内容 URI 来提供对表中单个行的访问。 同样按照惯例,提供程序会将该 ID 值与表的 _ID 列进行匹配,并对匹配的行执行请求的访问。

这一惯例为访问提供程序的应用的常见设计模式提供了便利。应用会对提供程序执行查询,并使用 CursorAdapterListView 显示生成的 Cursor。 定义 CursorAdapter 的条件是, Cursor 中的其中一个列必须是 _ID

用户随后从 UI 上显示的行中选取其中一行,以查看或修改数据。 应用会从支持 ListViewCursor 中获取对应行,获取该行的 _ID 值,将其追加到内容 URI,然后向提供程序发送访问请求。 然后,提供程序便可对用户选取的特定行执行查询或修改。

(3)继承ContentProvider 类,并实现相关的方法

其他应用在访问数据时都是通过ContentResolver 来实现,ContentResolver最终都调用了ContentProvider,ContentProvider是Android的四大组件之一,是一个抽象类,我们要对其进行继承并实现相关方法来实现对数据的操作。

有以下方法是必须要实现的:onCreate、insert、query、update、delete、getType。

这些CRUD方法和ContentResolver 中的方法签名是相同的。

在实现这些方法时应考虑以下事项:

实现 onCreate() 方法

Android 系统会在启动ContentProvider 时调用 onCreate()。只应该在此方法中执行运行快速的初始化任务,并将数据库创建和数据加载推迟到提供程序实际收到数据请求时进行。 如果在 onCreate() 中执行长时间的任务,则会减慢提供程序的启动速度, 进而减慢提供程序对其他应用的响应速度。

实现 query() 方法

ContentProvider.query() 方法必须返回 Cursor 对象。如果失败,则会引发 Exception。 如果使用 SQLite 数据库作为数据存储,则只需返回由 SQLiteDatabase 类的其中一个 query() 方法返回的 Cursor。 如果查询不匹配任何行,应该返回一个 Cursor 实例(其 getCount() 方法返回 0)。只有当查询过程中出现内部错误时,才应该返回 null。

如果不使用 SQLite 数据库作为数据存储,就应该使用 Cursor 的其中一个具体子类。 例如,在 MatrixCursor 类实现的游标中,每一行都是一个 Object 数组。 对于此类,请使用 addRow() 来添加新行。

注意,Android 系统必须能够跨进程边界传播 Exception,这些异常可能有助于处理查询错误。

实现 insert() 方法

insert() 方法会使用 ContentValues 参数中的值向相应表中添加新行。 如果 ContentValues 参数中未包含列名称,可能需要在ContentProvider 代码或数据库架构中提供其默认值。

此方法应该返回新行的内容 URI。要想构建此方法,请使用 withAppendedId() 向表的内容 URI 追加新行的 _ID(或其他主键)值。

实现 delete() 方法

delete() 方法不需要从数据存储中实际删除行。 如果将同步适配器与提供程序一起使用,应该考虑为已删除的行添加“删除”标志,而不是将行整个移除。 同步适配器可以检查是否存在已删除的行,并将它们从服务器中移除,然后再将它们从提供程序中删除。

实现 update() 方法

update() 方法采用 insert() 所使用的相同 ContentValues 参数,以及 delete() 和 ContentProvider.query() 所使用的相同 selection 和 selectionArgs 参数。 这样一来,就可以在这些方法之间重复使用代码。

实现 getType() 方法

ContentProvider 类具有两个返回 MIME 类型的方法:getType 和 getStreamTypes(系统在ContentProvider 提供文件时要求实现的方法)

getType() 方法会返回一个 MIME 格式的 String,后者描述内容 URI 参数返回的数据类型。URI 参数可以是模式,而不是特定 URI;在这种情况下,应该返回与匹配该模式的内容 URI 关联的数据类型。对于文本、HTML 或 JPEG 等常见数据类型,getType() 应该为该数据返回标准 MIME 类型。 IANA MIME Media Types 网站上提供了这些标准类型的完整列表。

1)表的MIME 类型

对于指向一个或多个表数据行的内容 URI,getType() 应该以 Android 供应商特有 MIME 格式返回 MIME 类型:

例如,如果提供程序的authority是 com.example.app.provider,并且它公开了一个名为 table1 的表,则 table1 中多个行的 MIME 类型是:

vnd.android.cursor.dir/vnd.com.example.provider.table1

对于 table1 的单个行,MIME 类型是:

vnd.android.cursor.item/vnd.com.example.provider.table1

2)文件的MIME 类型

如果ContentProvider 提供文件,就需要实现 getStreamTypes()。 该方法会让ContentProvider 可以为给定内容 URI 返回的文件返回一个 MIME 类型 String 数组。 应该通过 MIME 类型过滤器参数过滤提供的 MIME 类型,以便只返回客户端想处理的那些 MIME 类型。

例如,假设ContentProvider 以 .jpg、.png 和 .gif 格式文件形式提供照片图像。 如果应用调用 ContentResolver.getStreamTypes() 时使用了过滤器字符串 image/*(任何是“图像”的内容),则 ContentProvider.getStreamTypes() 方法应返回数组:

{ "image/jpeg", "image/png", "image/gif"}

如果应用只对 .jpg 文件感兴趣,则可以在调用 ContentResolver.getStreamTypes() 时使用过滤器字符串 */jpeg,ContentProvider.getStreamTypes() 应返回:

{"image/jpeg"}

如果ContentProvider 未提供过滤器字符串中请求的任何 MIME 类型,则 getStreamTypes() 应返回 null。

(4)处理ContentProvider的相关权限

Android 中提供了各方面的权限和访问,关于数据存储方面的权限要点如下

如果想使用ContentProvider 权限来控制对数据的访问,则应将数据存储在内部文件、SQLite 数据库或“云”中(例如,远程服务器上),而且我们应该保持文件和数据库被应用所私有。

即使底层数据为私有数据,所有应用仍可从ContentProvider 读取数据或向其写入数据,因为在默认情况下,ContentProvider 未设置权限。 要想改变这种情况,可以使用属性或 <provider> 元素的子元素在manifest 文件中为ContentProvider 设置权限。 可以设置适用于整个提供程序、特定表甚至特定记录的权限,或者设置同时适用于这三者的权限。

可以通过manifest 文件中的一个或多个 <permission> 元素为ContentProvider 定义权限。要使权限对我们的ContentProvider 具有唯一性,需要为 android:name 属性使用 Java 风格作用域。 例如,将读取权限命名为 com.whx.test.provider.permission.READ_PROVIDER。

ContentProvider 提供的权限有不同的作用域,更细化的权限优先于作用域更大的权限,下面是一些权限的作用域:

(5)manifest 文件中注册

ContentProvider 是Android 的四大组件之一,所以要想使用,必须在manifest 文件中进行注册,像上面提到的设置权限等,也是在注册时设置。

在<provider> 元素下,必须设置 android:authorities 属性和android:name属性。另外还有一些其他属性,如权限信息,以及下面的启动和控制属性,信息属性等。

启动和控制属性:

这些属性决定 Android 系统如何以及何时启动提供程序、提供程序的进程特性以及其他运行时设置:

信息属性:

提供程序的可选图标和标签:

三. ContentProvider 的创建实例

MyContentProvider

 public class MyContentProvider extends ContentProvider{

    private static final int TEST = 100;

    private static final String DB_NAME = "my_database";
    private static final String TABLE_NAME = "main";
    private static final String SQL_CREATE = "CREATE TABLE " + TABLE_NAME +
            " (" +
            "_id INTEGER PRIMARY KEY, " +
            "word TEXT, " +
            "frequency INTEGER, " +
            "locale TEXT )";

    private MyDatabaseHelper mDbHelper;
    private SQLiteDatabase db;

    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        sUriMatcher.addURI(MyContract.CONTENT_AUTHORITY, MyContract.PATH_TEST, TEST);
    }
    @Override
    public boolean onCreate() {
        mDbHelper = new MyDatabaseHelper(getContext());
        db = mDbHelper.getWritableDatabase();

        return true;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        Uri returnUri = Uri.EMPTY;
        long _id;

        switch (sUriMatcher.match(uri)) {
            case TEST:
                _id = db.insert(MyContract.PATH_TEST, null, values);
                if (_id > 0) {
                    returnUri = MyContract.buildUri(_id);
                } else {
                    throw new SQLException("failed to insert");
                }
                break;
        }
        return returnUri;
    }

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

        switch (sUriMatcher.match(uri)) {
            case TEST:
                db.update(MyContract.PATH_TEST, values, selection, selectionArgs);
        }
        return 0;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        Cursor cursor = null;

        switch (sUriMatcher.match(uri)) {
            case TEST:
                cursor = db.query(MyContract.PATH_TEST, projection, selection, selectionArgs, null, null, sortOrder);
                break;
        }
        return cursor;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        int res = 0;
        switch (sUriMatcher.match(uri)) {
            case TEST:
                res = db.delete(TABLE_NAME, selection, selectionArgs);
        }
        return res;
    }

    static class MyDatabaseHelper extends SQLiteOpenHelper {

        public MyDatabaseHelper(Context context) {
            super(context, DB_NAME, null, 1);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL(SQL_CREATE);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

        }
    }
}

上一篇 下一篇

猜你喜欢

热点阅读