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
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和数据库、表、字段相关信息,源码如下:
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类型,下面看实现源码:
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,完成数据库表的创建、更新,这样可以通过它获得数据库对象,相关代码如下。
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来为查询设置投影映射以及设置相关查询条件,看源码实现:
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。
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字句来更新记录,返回更新的记录数,看源码:
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方法,该方法返回删除的记录数。
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时有用。
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,这样系统才找得到,当然你也可以设置相关的权限,这里就不设置了
1.
2.android:name="com.johnny.testcontentprovider.ContactsContentProvider"
3.android:authorities="com.johnny.contactsprovider">
4.
5.
(12)到现在为止,自定义ContentProvider的全部代码已经完成,下面创建一个简单的应用来测试一下。
主要测试insert、update、delete、query这四个函数。
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中加入以下代码:
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函数中实现升级的代码:
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联系人:
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.}
结果如下: