AndroidContentProvider ContentResolver和ContentObserver的使用

http://www.it165.net/pro/html/201406/15820.html

1、ContentProvider、ContentResolver和ContentObserver

ContentProvider是Android的四大组件之一,可见它在Android中的作用非同小可。它主要的作用是:实现各个应用程序之间的(跨应用)数据共享,比如联系人应用中就使用了ContentProvider,你在自己的应用中可以读取和修改联系人的数据,不过需要获得相应的权限。其实它也只是一个中间人,真正的数据源是文件或者SQLite等。

一个应用实现ContentProvider来提供内容给别的应用来操作, 通过ContentResolver来操作别的应用数据,当然在自己的应用中也可以。

ContentObserver——内容观察者,目的是观察(捕捉)特定Uri引起的数据库的变化,继而做一些相应的处理,它类似于数据库技术中的触发器(Trigger),当ContentObserver所观察的Uri发生变化时,便会触发它。触发器分为表触发器、行触发器,相应地ContentObserver也分为“表“ContentObserver、“行”ContentObserver,当然这是与它所监听的Uri MIME Type有关的。

2、Contacts Demo

1)、基本功能实现

接下来通过一个简单的存储联系人信息的demo,来学习怎么创建自定义的ContentProvider,这里数据源选用SQLite,最常用的也是这个。

(1) 创建一个类NoteContentProvider,继承ContentProvider,需要实现下面5个方法:

query

insert

update

delete

getType

view sourceprint?

01.publicclassContactsContentProviderextendsContentProvider{

02.

03.@Override

04.publicbooleanonCreate() {

05.// TODO Auto-generated method stub

06.returnfalse;

07.}

08.

09.@Override

10.publicintdelete(Uri arg0, String arg1, String[] arg2) {

11.// TODO Auto-generated method stub

12.return0;

13.}

14.

15.@Override

16.publicString getType(Uri arg0) {

17.// TODO Auto-generated method stub

18.returnnull;

19.}

20.

21.@Override

22.publicUri insert(Uri arg0, ContentValues arg1) {

23.// TODO Auto-generated method stub

24.returnnull;

25.}

26.

27.@Override

28.publicCursor query(Uri arg0, String[] arg1, String arg2, String[] arg3,

29.String arg4) {

30.// TODO Auto-generated method stub

31.returnnull;

32.}

33.

34.@Override

35.publicintupdate(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {

36.// TODO Auto-generated method stub

37.return0;

38.}

39.

40.}

(2)先来设计一个数据库,用来联系人信息,主要包含_ID,name,telephone,create_date,content五个字段。group_name字段等后面升级部分再做使用。创建ProviderMetaData类,封装URI和数据库、表、字段相关信息,源码如下:

view sourceprint?

01.publicclassProviderMetaData {

02.

03.publicstaticfinalString AUTHORITY ="com.johnny.contactsprovider";

04.publicstaticfinalUri AUTHORITY_URI = Uri.parse("content://"+ AUTHORITY);

05.

06.publicstaticfinalclassContactsDataimplementsBaseColumns{

07.publicstaticfinalString TABLE_NAME ="contacts";

08.publicstaticfinalUri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, TABLE_NAME);

09.

10.publicstaticfinalString CONTENT_TYPE ="vnd.android.cursor.dir/contact";

11.publicstaticfinalString CONTENT_ITEM_TYPE ="vnd.android.cursor.item/contact";

12.

13.publicstaticfinalString CONTACT_NAME ="name";

14.publicstaticfinalString CONTACT_TELEPHONE ="telephone";

15.publicstaticfinalString CONTACT_CREATE_DATE ="create_date";

16.publicstaticfinalString CONTACT_CONTENT ="content";

17.publicstaticfinalString CONTACT_GROUP ="group_name";

18.

19.publicstaticfinalString DEFAULT_ORDERBY ="create_date DESC";

20.

21.publicstaticfinalString SQL_CREATE_TABLE ="CREATE TABLE "+ TABLE_NAME +" ("

22.+ _ID +" INTEGER PRIMARY KEY,"

23.+ CONTACT_NAME +" VARCHAR(50),"

24.+ CONTACT_TELEPHONE +" VARCHAR(11),"

25.+ CONTACT_CONTENT +" TEXT,"

26.+ CONTACT_CREATE_DATE +" INTEGER"

27.+");";

28.}

29.}

AUTHORITY代表授权,该字符串和在Android描述文件AndroidManifest.xml中注册该ContentProvider时的android:authorities值一样,ContactsData继承BaseColumns,后者提供了标准的_id字段,表示行ID。

熟悉Content Provider(内容提供者)的应该知道,我们可以通过UriMatcher类注册不同类型的Uri,我们可以通过这些不同的Uri来查询不同的结果。根据Uri返回的结果,Uri Type可以分为:返回多条数据的Uri、返回单条数据的Uri。

Android遵循类似的约定来定义MIME类型,每个内容类型的Android MIME类型有两种形式:多条记录(集合)和单条记录。

多条记录

vnd.android.cursor.dir/contact

单条记录

vnd.android.cursor.item/contact

vnd表示这些类型和子类型具有非标准的、供应商特定的形式。Android中类型已经固定好了,不能更改,只能区别是集合还是单条具体记录,子类型/之后的内容可以按照格式随便填写。在使用Intent时,会用到MIME这玩意,根据Mimetype打开符合条件的活动。

(3) ContentProvider是根据URI来获取数据的,那它怎么区分不同的URI呢,因为无论是获取笔记列表还是获取一条笔记都是调用query方法,现在来实现这个功能。需要用到类UriMatcher,该类可以帮助我们识别URI类型,下面看实现源码:

view sourceprint?

01.staticfinalUriMatcher URI_MATCHER =newUriMatcher(UriMatcher.NO_MATCH);

02.staticfinalHashMap CONTACTS_PROJECTION_MAP =newHashMap();

03.privatestaticfinalintCONTACTS =1;

04.privatestaticfinalintCONTACTS_ID =2;

05.static{

06.finalUriMatcher matcher = URI_MATCHER;

07.matcher.addURI(ProviderMetaData.AUTHORITY,"contacts", CONTACTS);

08.matcher.addURI(ProviderMetaData.AUTHORITY,"contacts/#", CONTACTS_ID);

09.

10.HashMap map = CONTACTS_PROJECTION_MAP;

11.map.put(ContactsData._ID, ContactsData._ID);

12.map.put(ContactsData.CONTACT_NAME, ContactsData.CONTACT_NAME);

13.map.put(ContactsData.CONTACT_TELEPHONE, ContactsData.CONTACT_TELEPHONE);

14.map.put(ContactsData.CONTACT_CONTENT, ContactsData.CONTACT_CONTENT);

15.map.put(ContactsData.CONTACT_CREATE_DATE, ContactsData.CONTACT_CREATE_DATE);

16.}

这段代码是NoteContentProvider类中的,UriMatcher的工作原理:首先需要在UriMatcher中注册URI模式,每一个模式跟一个唯一的编号关联,注册之后,在使用中就可以根据URI得到对应的编号,当模式不匹配时,UriMatcher将返回一个NO_MATCH常量,这样就可以区分了。

(4) 还需为查询设置一个投影映射,主要是将抽象字段映射到数据库中真实的字段名称,因为这些字段有时是不同的名称,既抽象字段的值可以不跟数据库中的字段名称一样。这里使用HashMap来完成,key是抽象字段名称,value对应数据库中的字段名称,不过这里我把两者的值设置是一样的,在NoteContentProvider.java中添加如上面所示的代码。

(5) 在NoteContentProvider.java中创建一个内部类DatabaseHelper,继承自SQLiteOpenHelper,完成数据库表的创建、更新,这样可以通过它获得数据库对象,相关代码如下。

view sourceprint?

01.privateclassDatabaseHelperextendsSQLiteOpenHelper{

02.

03.staticfinalString DATABASE_NAME ="test.db";

04.staticfinalintDATABASE_VERSION =1;

05.

06.publicDatabaseHelper(Context context) {

07.super(context, DATABASE_NAME,null, DATABASE_VERSION);

08.// TODO Auto-generated constructor stub

09.}

10.

11.@Override

12.publicvoidonCreate(SQLiteDatabase db) {

13.// TODO Auto-generated method stub

14.db.execSQL(ContactsData.SQL_CREATE_TABLE);

15.}

16.

17.@Override

18.publicvoidonUpgrade(SQLiteDatabase db,intoldVersion,intnewVersion) {

19.// TODO Auto-generated method stub

20.onCreate(db);

21.}

22.

23.}

(6) 现在来分别实现第一步中未实现的5个方法,先来实现query方法,这里借助SQLiteQueryBuilder来为查询设置投影映射以及设置相关查询条件,看源码实现:

view sourceprint?

01.@Override

02.publicCursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,

03.String sortOrder) {

04.// TODO Auto-generated method stub

05.SQLiteQueryBuilder queryBuilder =newSQLiteQueryBuilder();

06.switch(URI_MATCHER.match(uri)){

07.caseCONTACTS_ID:

08.queryBuilder.setTables(ContactsData.TABLE_NAME);

09.queryBuilder.setProjectionMap(CONTACTS_PROJECTION_MAP);

10.queryBuilder.appendWhere(ContactsData.TABLE_NAME +"._id="+Long.toString(ContentUris.parseId(uri)));

11.break;

12.caseCONTACTS:

13.queryBuilder.setTables(ContactsData.TABLE_NAME);

14.queryBuilder.setProjectionMap(CONTACTS_PROJECTION_MAP);

15.break;

16.}

17.

18.String orderBy;

19.if(TextUtils.isEmpty(sortOrder))

20.{

21.orderBy = ContactsData.DEFAULT_ORDERBY;

22.}else{

23.orderBy = sortOrder;

24.}

25.SQLiteDatabase db = mDbHelper.getReadableDatabase();

26.Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs,null,null, orderBy);

27.

28.returncursor;

29.}

返回的是一个Cursor对象,它是一个行集合,包含0和多个记录,类似于JDBC中的ResultSet,可以前后移动游标,得到每行每列中的数据。注意的是,使用它需要调用moveToFirst(),因为游标默认是在第一行之前。

(7)实现insert方法,实现把记录插入到基础数据库中,然后返回新创建的记录的URI。

view sourceprint?

01.@Override

02.publicUri insert(Uri uri, ContentValues values) {

03.// TODO Auto-generated method stub

04.SQLiteDatabase db = mDbHelper.getWritableDatabase();

05.longid = db.insertOrThrow(ContactsData.TABLE_NAME,null, values);

06.

07.// 更新数据时,通知其他ContentObserver

08.getContext().getContentResolver().notifyChange(ContactsData.CONTENT_URI,null);

09.

10.if(id >0){

11.returnContentUris.withAppendedId(uri, id);

12.}

13.returnnull;

14.}

(8) 实现update方法,根据传入的列值和where字句来更新记录,返回更新的记录数,看源码:

view sourceprint?

01.@Override

02.publicintupdate(Uri uri, ContentValues values, String selection, String[] selectionArgs) {

03.// TODO Auto-generated method stub

04.SQLiteDatabase db = mDbHelper.getWritableDatabase();

05.intmodified =0;

06.switch(URI_MATCHER.match(uri)){

07.caseCONTACTS_ID:

08.selection = DatabaseUtils.concatenateWhere(selection,ContactsData.TABLE_NAME +"._id=?");

09.selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,

10.newString[]{Long.toString(ContentUris.parseId(uri))});

11.Log.d("Test","selectionArgs 0"+selectionArgs);

12.modified = db.update(ContactsData.TABLE_NAME, values, selection, selectionArgs);

13.break;

14.caseCONTACTS:

15.modified = db.update(ContactsData.TABLE_NAME, values, selection, selectionArgs);

16.Log.d("Test","selectionArgs 1"+selectionArgs);

17.break;

18.}

19.

20.// 更新数据时,通知其他ContentObserver

21.getContext().getContentResolver().notifyChange(ContactsData.CONTENT_URI,null);

22.

23.returnmodified;

24.}

notifyChange函数是在更新数据时,通知其他监听对象。

(9)实现delete方法,该方法返回删除的记录数。

view sourceprint?

01.@Override

02.publicintdelete(Uri uri, String selection, String[] selectionArgs) {

03.// TODO Auto-generated method stub

04.SQLiteDatabase db = mDbHelper.getWritableDatabase();

05.intdeleted =0;

06.switch(URI_MATCHER.match(uri)){

07.caseCONTACTS_ID:

08.selection = DatabaseUtils.concatenateWhere(selection,ContactsData.TABLE_NAME +"._id=?");

09.selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,

10.newString[]{Long.toString(ContentUris.parseId(uri))});

11.Log.d("Test","selectionArgs 0"+selectionArgs);

12.deleted = db.delete(ContactsData.TABLE_NAME, selection, selectionArgs);

13.break;

14.caseCONTACTS:

15.deleted = db.delete(ContactsData.TABLE_NAME, selection, selectionArgs);

16.Log.d("Test","selectionArgs 1"+selectionArgs);

17.break;

18.}

19.

20.// 更新数据时,通知其他ContentObserver

21.getContext().getContentResolver().notifyChange(ContactsData.CONTENT_URI,null);

22.

23.returndeleted;

24.}

(10) 实现getType方法,根据URI返回MIME类型,这里主要用来区分URI是获取集合还是单条记录,这个方法在这里暂时没啥用处,在使用Intent时有用。

view sourceprint?

01.@Override

02.publicString getType(Uri uri) {

03.// TODO Auto-generated method stub

04.switch(URI_MATCHER.match(uri)){

05.caseCONTACTS:

06.returnContactsData.CONTENT_TYPE;

07.caseCONTACTS_ID:

08.returnContactsData.CONTENT_ITEM_TYPE;

09.//        default:

10.//            throw new IllegalArgumentException("Unknow URI: " + uri);

11.}

12.returnnull;

13.}

(11) 在AndroidManifest.xml中注册该ContentProvider,这样系统才找得到,当然你也可以设置相关的权限,这里就不设置了

view sourceprint?

1.

2.android:name="com.johnny.testcontentprovider.ContactsContentProvider"

3.android:authorities="com.johnny.contactsprovider">

4.

5.

(12)到现在为止,自定义ContentProvider的全部代码已经完成,下面创建一个简单的应用来测试一下。

主要测试insert、update、delete、query这四个函数。

view sourceprint?

01.privatevoidinsertContact1(){

02.ContentValues values =newContentValues();

03.values.put(ContactsData.CONTACT_NAME,"James");

04.values.put(ContactsData.CONTACT_TELEPHONE,"18888888888");

05.values.put(ContactsData.CONTACT_CONTENT,"NBA Star");

06.values.put(ContactsData.CONTACT_CREATE_DATE, System.currentTimeMillis());

07.Uri uri = getContentResolver().insert(ContactsData.CONTENT_URI, values);

08.Log.d("Test","uri = "+uri);

09.}

10.

11.privatevoiddeleteContact1(){

12.intcount = getContentResolver().delete(ContactsData.CONTENT_URI, ContactsData.CONTACT_NAME+"='James'",null);

13.Log.d("Test","count = "+count);

14.}

15.

16.privatevoidupdateContact1(){

17.ContentValues values =newContentValues();

18.values.put(ContactsData.CONTACT_TELEPHONE,"16666666666");

19.intcount = getContentResolver().update(ContactsData.CONTENT_URI,values, ContactsData.CONTACT_NAME+"='James'",null);

20.Log.d("Test","count = "+count);

21.}

22.

23.privatevoidqueryContact1(){

24.Cursor cursor =this.getContentResolver().query(ContactsData.CONTENT_URI,null, ContactsData.CONTACT_NAME+"='James'",null,null);

25.Log.e("test ","count="+ cursor.getCount());

26.cursor.moveToFirst();

27.while(!cursor.isAfterLast()) {

28.String name = cursor.getString(cursor.getColumnIndex(ContactsData.CONTACT_NAME));

29.String telephone = cursor.getString(cursor.getColumnIndex(ContactsData.CONTACT_TELEPHONE));

30.longcreateDate = cursor.getLong(cursor.getColumnIndex(ContactsData.CONTACT_CREATE_DATE));

31.Log.e("Test","name: "+ name);

32.Log.e("Test","telephone: "+ telephone);

33.Log.e("Test","date: "+ createDate);

34.

35.cursor.moveToNext();

36.}

37.cursor.close();

38.}

(13)创建数据库监听器ContentObserver

在MainActivity中加入以下代码:

view sourceprint?

01.privateContentObserver mContentObserver =newContentObserver(newHandler()) {

02.

03.@Override

04.publicvoidonChange(booleanselfChange) {

05.// TODO Auto-generated method stub

06.Log.d("Test","mContentObserver onChange");

07.super.onChange(selfChange);

08.}

09.

10.};

11.@Override

12.protectedvoidonCreate(Bundle savedInstanceState) {

13.super.onCreate(savedInstanceState);

14.setContentView(R.layout.activity_main);

15.

16.if(savedInstanceState ==null) {

17.getSupportFragmentManager().beginTransaction()

18..add(R.id.container,newPlaceholderFragment()).commit();

19.}

20.

21.getContentResolver().registerContentObserver(ContactsData.CONTENT_URI,true, mContentObserver);

22.

23.}

每次通过insert、delete、update改变数据库内容时,都会调用ContentObserver的onChange方法,因此,可以在这个方法内做出针对数据库变化的反应,比如更新UI等。

2)、数据库的升级

当应用发布一段时间之后,我们需要改变数据库的结构,那么就需要对数据库的升级了:

将DatabaseHelper类中的DATABASE_VERSION设置为2,并且在onUpgrade函数中实现升级的代码:

view sourceprint?

01.privateclassDatabaseHelperextendsSQLiteOpenHelper{

02.

03.staticfinalString DATABASE_NAME ="test.db";

04.staticfinalintDATABASE_VERSION =2;

05.

06.publicDatabaseHelper(Context context) {

07.super(context, DATABASE_NAME,null, DATABASE_VERSION);

08.// TODO Auto-generated constructor stub

09.}

10.

11.@Override

12.publicvoidonCreate(SQLiteDatabase db) {

13.// TODO Auto-generated method stub

14.db.execSQL(ContactsData.SQL_CREATE_TABLE);

15.}

16.

17.@Override

18.publicvoidonUpgrade(SQLiteDatabase db,intoldVersion,intnewVersion) {

19.// TODO Auto-generated method stub

20.Log.d("Test","onUpgrade oldVersion = "+oldVersion+", newVersion = "+newVersion);

21.//onCreate(db);

22.for(inti = oldVersion+1;i <= newVersion;i++){

23.switch(i){

24.case2:

25.db.execSQL("ALTER TABLE "+ ContactsData.TABLE_NAME +" ADD COLUMN "+ ContactsData.CONTACT_GROUP +" TEXT");

26.break;

27.}

28.}

29.}

30.

31.}

下面是升级前后数据库的结果:

用下面代码为DATABASE_VERSION = 2的数据库中的James设在组别和加入Howard联系人:

view sourceprint?

01.privatevoidmodifyContact1(){

02.ContentValues values =newContentValues();

03.values.put(ContactsData.CONTACT_GROUP,"Miami");

04.intcount = getContentResolver().update(ContactsData.CONTENT_URI,values, ContactsData.CONTACT_NAME+"='James'",null);

05.Log.d("Test","count = "+count);

06.}

07.

08.privatevoidinsertContact2(){

09.ContentValues values =newContentValues();

10.values.put(ContactsData.CONTACT_NAME,"Howard");

11.values.put(ContactsData.CONTACT_TELEPHONE,"13333333333");

12.values.put(ContactsData.CONTACT_CONTENT,"NBA Star");

13.values.put(ContactsData.CONTACT_GROUP,"Rockets");

14.values.put(ContactsData.CONTACT_CREATE_DATE, System.currentTimeMillis());

15.Uri uri = getContentResolver().insert(ContactsData.CONTENT_URI, values);

16.Log.d("Test","uri = "+uri);

17.}

结果如下:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容