一、概念
内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证程序被访数据的安全性。不同于文件存储和SharedPreferences存储中的两种全局可读写操作模式,内容提供器可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄露的风险。
(1)如果想要实现跨程序共享数据的功能,官方推荐的方式是使用内容提供器。
(2)内容提供器一定要在AndroidManifest.xml文件中注册才可以使用。
二、用法
内容提供器的用法一般有两种:①一种是使用现有的内容提供器来读取和操作相应程序中的数据,②另一种就是创建自己的内容提供器给我们程序的数据提供外部访问接口。可以通过创建一个类去继承ContentProvider的方式来创建一个自己的内容提供器。ContentProvider类中有6个抽象方法,我们在使用子类继承它的时候,需要将这6个方法全部重写,分别为:onCreate()、query()、insert()、update()、delete()、getType()。
-
onCreate()
初始化内容提供器的时候调用。通常会在这里完成对数据库的创建和升级等操作,返回True表示内容提供器初始化成功,返回false则表示失败。 -
query()
从内容提供器中查询数据。使用uri参数来确定查询哪张表,projection参数用于确定查询哪些列,selection和selectionArgs参数用于约束查询哪些行,sortOrder参数用于对结果进行查询,查询结果存放在Cursor对象中返回。 -
insert()
向内容提供器中添加一条数据。使用uri参数来确定要添加到的表,待添加的数据保存在values参数中。添加完成后,返回一个用于表示这条新纪录的URI。 -
update()
更新内容提供器中已有的数据。使用uri参数来确定更新哪一张表中的数据,新数据保存在values参数中,selection和selectionArgs参数用于约束更新哪些行,受影响的行数将作为返回值返回。 -
delete()
从内容提供器中删除数据。使用uri参数来确定删除哪一张表中的数据,selection和selectionArgs参数用于约束删除哪些行,被删除的行数将作为返回值返回。 -
getType()
根据传入的内容URI来返回相应的MIME类型。该方法也是所有内容提供器都必须提供的一个方法,用于获取Uri对象所对应的MIME类型。一个内容URI所对应的MIME字符串主要由3部分组成,Android对这3个部分做了如下格式规定:
(1)必须以vnd开头。
(2)如果内容URI以路径结尾,则后接android.cursor.dir/,如果内容URI以id结尾,则后接android.cursor.item/。
(3)最后接上vnd.< authority>.< path>。
所以对于content://com.example.app.provider/table1这个内容URI,它对应的MIME类型可以写成:vnd.android.cursor.dir/vnd.com.example.app.provider.table1
所以对于content://com.example.app.provider/table1/1这个内容URI,它对应的MIME类型可以写成:
vnd.android.cursor.item/vnd.com.example.app.provider.table1
每个ContentProvider都有一个公共的URI,这个URI用于表示这个Content Provider所提供的数据。
三、ContentResolver
对于一个应用程序来说,如果想要访问内容提供器中共享的数据,就一定要借助ContentResolver类。可以通过Context的getContentResolver()方法获取到该类的实例。ContentResolver中提供了一系列的方法用于对数据进行CRUD操作,其中insert()方法用于添加数据,update()方法用于更新数据,delete()方法用于删除数据,query()方法用于查询数据。(SQLite Database中也是使用这几个方法来进行CRUD操作的,只是在方法参数上有一些区别)不同于SQLite Database,ContentResolver中的增删改查方法都是不接收表名参数的,而是使用一个Uri参数代替,这个参数被称为内容URI。
(1)query()方法——查询数据
只需要调用Uri.parse()方法就可以将内容URI字符串解析成Uri对象了,我们使用这个Uri对象来查询table1表中的数据,代码如下:
Cursor cursor = getContentResolver().query(
uri,
projection,
selection,
selectionArgs,
sortOrder);
查询完成后返回的仍然是一个Cursor对象,这时我们就可以将数据从Cursor对象中逐个读取出来了。读取的思路是通过移动游标的位置来遍历Cursor的所有行,然后再取出每一行中相应列的数据,代码如下:
if(cursor != null){
while(cursor.moveToNext()){
String column1 = cursor.getString(cursor.getColumnIndex("column1"));
int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
}
cursor.close();
}
注:对query()方法中的参数进行解释
query()方法参数 | 对应SQL部分 | 描述 |
---|---|---|
uri | from table_name | 指定查询某个应用程序下的某一张表 |
projection | select column1,column2 | 指定查询的列明 |
selection | where column = value | 指定where的约束条件 |
selectionArgs | - | 为where中的占位符提供具体的值 |
sortOrder | order by column1,column2 | 指定查询结果的排列方式 |
(2)insert()方法——添加数据
将待添加数据组装到ContentValues中,然后调用ContentResolver的insert()方法,将Uri和ContentValues作为参数传入即可。
ContentValues values = new ContentValues();
values.put("column1", "text");
values.put("column2", 1);
getContentResolver().insert(uri, values);
(3)update()方法——更新数据
调用ContentResolver的update()方法对数据进行更新。
ContentValues values = new ContentValues();
values.put("column1", "");
getContentResolver().update(uri, values, "column1 = ? and column2 = ?", new String[]{"text", "1"});
注:上述代码使用了selection和selectionArgs参数来对想要更新的数据进行约束,以防止所有的行都会受影响。
(4)delete()方法——删除数据
调用ContentResolver的delete()方法将数据进行删除。
getContentResolver().delete(uri, "column2 = ?", new String[]{"1"});
四、内容URI
内容URI:可以非常清楚地表达出我们想要访问哪个程序中哪张表里地数据。
内容URI给内容提供器中的数据建立了唯一标识符,它主要由两部分组成:authority和path。
authority是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式来进行命名。比如某个程序的包名是com.example.app,那么该程序对应的authority就可以命名为com.example.app.provider。
path则是用于对同一应用程序中不同的表做区分,通常都会添加到authority的后面。比如某数据库存在表table1,那么可以将path命名为/table1,然后把authority和path进行组合,内容URI就变成了com.example.app.provider/table1。此外还需要在字符串头部加上协议声明,因此,内容URI最标准的格式写法如下:
//内容URI的格式如下
//1、标准的内容URI
content://com.example.app.provider/table1
//2、给内容URI加上一个id
//表示期望访问的是com.example.app这个应用table1表中id为1的数据
content://com.example.app.provider/table1/1
//在得到了内容URI字符串后,我们还需要将它解析为Uri对象才可以作为参数传入。
//只需要调用Uri.parse()方法,就可以将内容URI字符串解析chengUri对象。
Uri uri = Uri.parse("content://com.example.app.provider/table1");
内容URI的格式主要就只有以上两种,以路径结尾就表示期望访问该表中的所有数据,以id结尾就表示期望访问该表中拥有相应id的数据。我们可以使用通配符的方式来分别匹配这两种数据格式的内容URI,规则如下:
- *:表示匹配任意长度的任意字符。
- #:表示匹配任意长度的数字。
因此,一个能够匹配任意表的内容URI格式就可以写为:
content://com.example.app.provider/*
而一个能够匹配table1表中任意一行数据的内容URI格式就可以写成:
content://com.example.app.provider/table1/#
在得到了内容URI字符串之后,我们还需要将其解析成Uri对象才可以作为参数传入。解析的方法也相当简单,代码如下:
// 只需要调用Uri.parse()方法就可以将内容URI字符串解析成Uri对象了
Uri uri = Uri.parse("content://com.example.app.provider/table1");
五、ContentProvider、ContentResolver、ContentObserver有什么区别?
(1)ContentProvider
把一个应用程序的私有数据(如数据库)信息暴露给其他应用程序,让其他应用程序可以访问到这些私有数据。在ContentProvider中有对应的增删改查方法,如果要让其他应用程序访问,则需要对外暴露一个URI路径。
实现各个应用程序间数据共享,用来提供内容给别的应用操作。如联系人应用中就使用了ContentProvider,可以在自己应用中读取和修改联系人信息,不过需要获取相应的权限。它也只是一个中间件,真正的数据源是文件或SQLite等。
(2)ContentResolver
根据ContentProvider的URI路径,对数据进行CRUD的操作等。
内容解析者,用于获取内容提供者提供的数据,通过ContentResolver.notifyChange(uri)发出消息。
(3)ContentObserver
可以理解为Android系统包装好的回调,当ContentProvider数据发送变化时,会执行回调中的方法。
ContentResolver发送通知,ContentObserver监听通知。
内容监听者,可以监听数据的改变状态,观察特定Uri引起的数据库变化,继而做一些相应的处理,类似于数据库中的触发器,当ContentObserver所观察的Uri发生变化时,便会触发它。