AndroidContentProvider ContentResolver和ContentObserver的使用

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. public class ContactsContentProvider extends ContentProvider{
02.  
03. @Override
04. public boolean onCreate() {
05. // TODO Auto-generated method stub
06. return false;
07. }
08.  
09. @Override
10. public int delete(Uri arg0, String arg1, String[] arg2) {
11. // TODO Auto-generated method stub
12. return 0;
13. }
14.  
15. @Override
16. public String getType(Uri arg0) {
17. // TODO Auto-generated method stub
18. return null;
19. }
20.  
21. @Override
22. public Uri insert(Uri arg0, ContentValues arg1) {
23. // TODO Auto-generated method stub
24. return null;
25. }
26.  
27. @Override
28. public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3,
29. String arg4) {
30. // TODO Auto-generated method stub
31. return null;
32. }
33.  
34. @Override
35. public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
36. // TODO Auto-generated method stub
37. return 0;
38. }
39.  
40. }



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

01. public class ProviderMetaData {
02.  
03. public static final String AUTHORITY = "com.johnny.contactsprovider";
04. public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
05.  
06. public static final class ContactsData implements BaseColumns{
07. public static final String TABLE_NAME = "contacts";
08. public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, TABLE_NAME);
09.  
10. public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact";
11. public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact";
12.  
13. public static final String CONTACT_NAME = "name";
14. public static final String CONTACT_TELEPHONE = "telephone";
15. public static final String CONTACT_CREATE_DATE = "create_date";
16. public static final String CONTACT_CONTENT = "content";
17. public static final String CONTACT_GROUP = "group_name";
18.  
19. public static final String DEFAULT_ORDERBY = "create_date DESC";
20.  
21. public static final String 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. static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
02. static final HashMap<String, String> CONTACTS_PROJECTION_MAP = new HashMap<String, String>();
03. private static final int CONTACTS = 1;
04. private static final int CONTACTS_ID = 2;
05. static{
06. final UriMatcher matcher = URI_MATCHER;
07. matcher.addURI(ProviderMetaData.AUTHORITY, "contacts", CONTACTS);
08. matcher.addURI(ProviderMetaData.AUTHORITY, "contacts/#", CONTACTS_ID);
09.  
10. HashMap<String, String> 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. private class DatabaseHelper extends SQLiteOpenHelper{
02.  
03. static final String DATABASE_NAME = "test.db";
04. static final int DATABASE_VERSION = 1;
05.  
06. public DatabaseHelper(Context context) {
07. super(context, DATABASE_NAME, null, DATABASE_VERSION);
08. // TODO Auto-generated constructor stub
09. }
10.  
11. @Override
12. public void onCreate(SQLiteDatabase db) {
13. // TODO Auto-generated method stub
14. db.execSQL(ContactsData.SQL_CREATE_TABLE);
15. }
16.  
17. @Override
18. public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
19. // TODO Auto-generated method stub
20. onCreate(db);
21. }
22.  
23. }


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

01. @Override
02. public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
03. String sortOrder) {
04. // TODO Auto-generated method stub
05. SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
06. switch(URI_MATCHER.match(uri)){
07. case CONTACTS_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. case CONTACTS:
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, nullnull, orderBy);
27.  
28. return cursor;
29. }



返回的是一个Cursor对象,它是一个行集合,包含0和多个记录,类似于JDBC中的ResultSet,可以前后移动游标,得到每行每列中的数据。注意的是,使用它需要调用moveToFirst(),因为游标默认是在第一行之前。
(7)实现insert方法,实现把记录插入到基础数据库中,然后返回新创建的记录的URI。
01. @Override
02. public Uri insert(Uri uri, ContentValues values) {
03. // TODO Auto-generated method stub
04. SQLiteDatabase db = mDbHelper.getWritableDatabase();
05. long id = db.insertOrThrow(ContactsData.TABLE_NAME, null, values);
06.  
07. // 更新数据时,通知其他ContentObserver
08. getContext().getContentResolver().notifyChange(ContactsData.CONTENT_URI, null);
09.  
10. if(id > 0){
11. return ContentUris.withAppendedId(uri, id);
12. }
13. return null;
14. }


(8) 实现update方法,根据传入的列值和where字句来更新记录,返回更新的记录数,看源码:
01. @Override
02. public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
03. // TODO Auto-generated method stub
04. SQLiteDatabase db = mDbHelper.getWritableDatabase();
05. int modified = 0;
06. switch(URI_MATCHER.match(uri)){
07. case CONTACTS_ID:
08. selection = DatabaseUtils.concatenateWhere(selection,ContactsData.TABLE_NAME + "._id=?");
09. selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
10. new String[]{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. case CONTACTS:
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. return modified;
24. }


notifyChange函数是在更新数据时,通知其他监听对象。
(9)实现delete方法,该方法返回删除的记录数。
01. @Override
02. public int delete(Uri uri, String selection, String[] selectionArgs) {
03. // TODO Auto-generated method stub
04. SQLiteDatabase db = mDbHelper.getWritableDatabase();
05. int deleted = 0;
06. switch(URI_MATCHER.match(uri)){
07. case CONTACTS_ID:
08. selection = DatabaseUtils.concatenateWhere(selection,ContactsData.TABLE_NAME + "._id=?");
09. selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
10. new String[]{Long.toString(ContentUris.parseId(uri))});
11. Log.d("Test""selectionArgs 0"+selectionArgs);
12. deleted = db.delete(ContactsData.TABLE_NAME, selection, selectionArgs);
13. break;
14. case CONTACTS:
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. return deleted;
24. }


(10) 实现getType方法,根据URI返回MIME类型,这里主要用来区分URI是获取集合还是单条记录,这个方法在这里暂时没啥用处,在使用Intent时有用。
01. @Override
02. public String getType(Uri uri) {
03. // TODO Auto-generated method stub
04. switch(URI_MATCHER.match(uri)){
05. case CONTACTS:
06. return ContactsData.CONTENT_TYPE;
07. case CONTACTS_ID:
08. return ContactsData.CONTENT_ITEM_TYPE;
09. //        default:
10. //            throw new IllegalArgumentException("Unknow URI: " + uri);
11. }
12. return null;
13. }


(11) 在AndroidManifest.xml中注册该ContentProvider,这样系统才找得到,当然你也可以设置相关的权限,这里就不设置了
1. <provider
2. android:name="com.johnny.testcontentprovider.ContactsContentProvider"
3. android:authorities="com.johnny.contactsprovider">
4.  
5. </provider>


(12)到现在为止,自定义ContentProvider的全部代码已经完成,下面创建一个简单的应用来测试一下。
主要测试insert、update、delete、query这四个函数。
01. private void insertContact1(){
02. ContentValues values = new ContentValues();
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. private void deleteContact1(){
12. int count = getContentResolver().delete(ContactsData.CONTENT_URI, ContactsData.CONTACT_NAME+"='James'"null);
13. Log.d("Test""count = "+count);
14. }
15.  
16. private void updateContact1(){
17. ContentValues values = new ContentValues();
18. values.put(ContactsData.CONTACT_TELEPHONE, "16666666666");
19. int count = getContentResolver().update(ContactsData.CONTENT_URI,values, ContactsData.CONTACT_NAME+"='James'"null);
20. Log.d("Test""count = "+count);
21. }
22.  
23. private void queryContact1(){
24. Cursor cursor = this.getContentResolver().query(ContactsData.CONTENT_URI, null, ContactsData.CONTACT_NAME+"='James'"nullnull);
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. long createDate = 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. private ContentObserver mContentObserver = new ContentObserver(new Handler()) {
02.  
03. @Override
04. public void onChange(boolean selfChange) {
05. // TODO Auto-generated method stub
06. Log.d("Test""mContentObserver onChange");
07. super.onChange(selfChange);
08. }
09.  
10. };
11. @Override
12. protected void onCreate(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, new PlaceholderFragment()).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. private class DatabaseHelper extends SQLiteOpenHelper{
02.  
03. static final String DATABASE_NAME = "test.db";
04. static final int DATABASE_VERSION = 2;
05.  
06. public DatabaseHelper(Context context) {
07. super(context, DATABASE_NAME, null, DATABASE_VERSION);
08. // TODO Auto-generated constructor stub
09. }
10.  
11. @Override
12. public void onCreate(SQLiteDatabase db) {
13. // TODO Auto-generated method stub
14. db.execSQL(ContactsData.SQL_CREATE_TABLE);
15. }
16.  
17. @Override
18. public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
19. // TODO Auto-generated method stub
20. Log.d("Test""onUpgrade oldVersion = "+oldVersion+", newVersion = "+newVersion);
21. //onCreate(db);
22. for(int i = oldVersion+1;i <= newVersion;i++){
23. switch(i){
24. case 2:
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. private void modifyContact1(){
02. ContentValues values = new ContentValues();
03. values.put(ContactsData.CONTACT_GROUP, "Miami");
04. int count = getContentResolver().update(ContactsData.CONTENT_URI,values, ContactsData.CONTACT_NAME+"='James'"null);
05. Log.d("Test""count = "+count);
06. }
07.  
08. private void insertContact2(){
09. ContentValues values = new ContentValues();
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. }

结果如下:

 

\

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值