Android四大组件之ContentProvider

Android四大组件之Activity
Android四大组件之Service
Android四大组件之BroadcastReceiver
Android四大组件之ContentProvider

一、内容提供器简介

内容提供器(ContentProvider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访问数据的安全性。目前,使用内容提供器是Android实现跨进程共享数据的标准方式。
不同于文件存储和SharedPreferences存储中的两种全局可读写操作模式,内容提供者可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄露的风险。

二、访问其他程序中的数据

内容提供器的用法一般有两种,一种是使用现有的内容提供器来读取和操作相应程序中的数据,另一种是创建自己的内容提供器给我们程序的数据提供外部访问接口。如果一个应用程序通过内容提供器对其数据提供了外部访问接口,那么任何其他的应用程序就都可以对这部分数据进行访问。Android系统中自带的电话簿、短信。多媒体等程序都提供了类似的访问接口,这就使得第三方应用程序可以充分地利用使用这些数据。

2.1 ContentResolver的基本用法

对于每一个应用程序来说,如果想要访问费用提供器中共享的数据,就一定要借助ContentResolver类,可以通过Context中的getContentResolver()方法获取到该类的实例。
ContentResolver中提供了一系列的方法用于对数据进行CRUD操作,其中insert()方法用于添加数据,update()方法用于更新数据,delete()方法用于删除数据,query()方法用于查询数据。这种方式和SQLiteDatabase中方法基本类似,只不过在方法参数上稍微有些区别。
不同于SQLiteDatabase,ContentResolver中的增删改查方法都是不接收表名参数的,而是使用一个Uri参数代替,这个参数被程蓉内容URI。内容URI给内容提供器中的数据建立了唯一表示符,主要由两部分组成:authority和path。authority是用于对不同的应用程序进行区分的,一般为了避免冲突,都会采用程序包名的方式进行命名。例如com.monkey.contentprovider。path则是用于对同一引用程序中的不同表名做区分的,通常天骄到authority的后面。将authority和path进行组合,再加上头布局的协议声明,就可以完成标准的URI格式写法。如:
content://com.monkey.contentprovider/table1
content://com.monkey.contentprovider/table2
由此可以看出,内容URI可以非常清楚的表达出我们想要访问哪个程序中哪张表的数据。也正以为如此,ContentResolver中的增删改查方法才都接收Uri对象作为参数,因为如果使用表名的话,系统将无法得知我们期望访问的是哪个应用程序里的表。
在得到内容URI字符串后,我们还需要将它解析成Uri对象才可以最为参数穿日。具体解析方法如下:

Uri uri = Uri.parse("content://com.monkey.contentprovider/table1");

下来我们就可以使用这个Uri对象来查询table1表中的数据了,代码如下:

Cursor cursor = getContentResolver().query(uri,projection,selection,selectionArgs,sortOrder);

这些参数和sql中query()方法的参数很像,我们通过比较来学习下:

query()方法参数 对应sql部分 描述
uri from table_name 指定查询某个应用程序下的某张表
projection select column1,column2 指定禅熏的列名
selection where column = value 指定where的约束条件
selectionArgs - 为where中的占位符提供具体的值
sortOrder order by column1 指定查询结果的排序方式
2.1.1 查询

查询完成后返回的仍然是一个Cursor对象,这是我们可以将数据冲Cursor对象中逐个读取出来。通过移动游标的位置来遍历Cursor的所有上,然后再取出每一行中相应列的数据


if (null != cursor){
    while (cursor.moveToNext()){
        String colunm1 = cursor.getString(cursor.getColumnIndex("colunm1"));
        int colunm2 = cursor.getInt(cursor.getColumnIndex("colunm1"));
    }
    //使用完关闭
    cursor.close();
}

2.1.2 增加

向table1表中添加一条数据,具体实现如下:


ContentValues contentValues = new ContentValues();
contentValues.put("colunm1","test");
contentValues.put("colunm2",1);
getContentResolver().insert(uri,contentValues);

2.1.3 修改

由此看出,是将待添加的数据组装到ContentValues中,然后调用ContentResolver的insert()方法,将uri和ContentValues作为参数传入。

更新数据,修改column1的值,可以使用ContentResolver的update()方法实现:


ContentValues contentValues = new ContentValues();
contentValues.put("colunm1","测试");
getContentResolver().update(uri,contentValues,"column1 = test",new String[]{"test","1"});

2.1.4 删除

删除数据,调用ContentResolver的delete()方法删除。具体代码如下:


getContentResolver().delete(uri,"column1 = '测试'",new String[]{"测试","1"});

以上就是ContentResolver中的增删改查,需要注意的是uri这个参数。

三、创建自己的内容提供器

在上面,我们学习了如何在自己的程序中访问其他应用程序的数据。我们只需要获取到该应用程序的内容uri,然后借助
ContentResolver进行CRUD操作就可以实现。下面我们学习下怎样创建自己的内容提供器。

3.1 创建内容提供器

要想实现跨程序共享数据的功能,官方推荐使用内容提供器吗,可以通过继承ContentProvider的方式实现。ContentProvider类中有6个抽象方法,我们在使用子类继承的时候,需要重写这6个方法,代码如下:

public class MyContentProvider extends ContentProvider {
    public MyContentProvider() {
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // Implement this to handle requests to delete one or more rows.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public String getType(Uri uri) {
        // TODO: Implement this to handle requests for the MIME type of the data
        // at the given URI.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // TODO: Implement this to handle requests to insert a new row.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public boolean onCreate() {
        // TODO: Implement this to initialize your content provider on startup.
        return false;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        // TODO: Implement this to handle query requests from clients.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        // TODO: Implement this to handle requests to update one or more rows.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

下面了解下这6个方法:
1.onCreate()
初始化内容提供者的时候调用。通常会在这里完成对数据库的创建和升级操作。返回true,表示内容提供器初始化成功,false则失败。只有当存在ContentResolver访问我们程序数据时,它才会被初始化。
2.query()
查询数据。使用uri参数来确定查询那张表,projection参数用于确定查询那些列,selection和selectionArgs参数用户约束查询那些行,sortOrder参数用于对结果进行排序,查询结果存放在Curosr对象中返回。
3.insert()
添加数据。使用uri参数来确定要添加到的表,待添加的数据保存在values参数中。添加完成后,返回一个用于表示这条新纪录的URI。
4.update()
修改已有数据。使用uri参数来确定更新那一张表中的数据,新数据保存在values参数中,selection和selectionArgs参数用于约束那些行,将受影响的行数作为返回值返回。
5.delete()
删除数据。使用uri参数来确定删除哪一张表中的数据,selection和selectionArgs参数用户约束删除那些行,被删除的行数将作为参数值返回。
6.getType()
根据传入的内容uri来返回相应的MIME类型(在下文中会介绍什么是MIME类型)。

3.2 UriMatcher类

在ContentProvider 中注册uri ,根据 uri 匹配 ContentProvider 中对应的数据表。具体代码如下:


    public static final int URI_CODE_TALBE1 = 1;
    public static final int URI_CODE_TALBE2 = 2;
    private static UriMatcher uriMatcher;

    static {
        //创建uriMaticher对象
        //常量UriMatcher.NO_MATCH  = 不匹配任何路径的返回码
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

        //在ContentProvider 中注册URI(addURI())
        // 若URI资源路径 = content://com.monkey.contentprovider/table1 ,则返回注册码URI_CODE_TALBE1
        uriMatcher.addURI("com.monkey.contentprovider", "table1", URI_CODE_TALBE1);
        // 若URI资源路径 = content://com.monkey.contentprovider/table2 ,则返回注册码URI_CODE_TALBE2
        uriMatcher.addURI("com.monkey.contentprovider", "table2", URI_CODE_TALBE2); 
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        Cursor cursor = null;
        switch (uriMatcher.match(uri)) {
            case URI_CODE_TALBE1:
               // cursor = 查询table1中的数据
                break;
            case URI_CODE_TALBE2:
                // cursor = 查询table2中的数据
                break;
        }
      return cursor;
    }

我们在上面创建了UriMatcher实例,通过addaddURI()方法,将我们期匹配的内容uri格式传进去,当query()方法被调用的时候,将会通过UriMatcher的match()方法匹配传进来的uri,我们就可以知道调用方需要访问什么数据了。对于insert()、update()、delete()方法的实现是类似的。

3.3 MIME数据类型

指定某个扩展名的文件用某种应用程序来打开
MIME类型组成
每种MIME类型 由2部分组成 = 类型 + 子类型

// 类型 = text、子类型 = html
text/html //超文本标记语言文本
// 类型 = application、子类型 = vnd.android.package-archive
application/vnd.android.package-archive /APK文件(安卓系统)

在内容提供器中getType()方法用于获取Uri对象所对应的MIME类型
android做了如下格式规定:

  • 如果内容URI以路径结尾,则后接android.cursor.dir/ ,如果内容URI以id结尾,则后接android.cursor.item/。
  • 后面接上vnd.<authority>.<path>,必须以vnd开头。

对于content://com.monkey.contentprovider/table1这个内容URI,所对应的MIME类型为android.cursor.dir/vnd.com.monkey.contentprovider/table1
对于content://com.monkey.contentprovider/table1/5这个内容URI,所对应的MIME类型为android.cursor.item/vnd.com.monkey.contentprovider/table1
在getType()中的实现如下:

    public static final int URI_CODE_TALBE1 = 1;
    public static final int URI_CODE_TALBE2 = 2;
    public static final int URI_CODE_TALBE1_ITEM = 3;
    private static UriMatcher uriMatcher;

    static {
        //创建uriMaticher对象
        //常量UriMatcher.NO_MATCH  = 不匹配任何路径的返回码
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

        //在ContentProvider 中注册URI(addURI())
        // 若URI资源路径 = content://com.monkey.contentprovider/table1 ,则返回注册码URI_CODE_TALBE1
        uriMatcher.addURI("com.monkey.contentprovider", "table1", URI_CODE_TALBE1);
        // 若URI资源路径 = content://com.monkey.contentprovider/table2 ,则返回注册码URI_CODE_TALBE2
        uriMatcher.addURI("com.monkey.contentprovider", "table2", URI_CODE_TALBE2);
        // 若URI资源路径 = content://com.monkey.contentprovider/table1/5 ,则返回注册码URI_CODE_TALBE1_ITEM
        uriMatcher.addURI("com.monkey.contentprovider", "table1/#", URI_CODE_TALBE1_ITEM);
    }

    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)) {
            case URI_CODE_TALBE1:
                return "android.cursor.dir/vnd.com.monkey.contentprovider/table1";
            case URI_CODE_TALBE2:
                return "android.cursor.dir/vnd.com.monkey.contentprovider/table2";
            case URI_CODE_TALBE1_ITEM:
                return "android.cursor.item/vnd.com.monkey.contentprovider/table2";
            default:
                break;
        }
        return null;
    }

推荐阅读更多精彩内容