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.@Override04.public boolean onCreate() {05.// TODO Auto-generated method stub06.return false;07.}08. 09.@Override10.public int delete(Uri arg0, String arg1, String[] arg2) {11.// TODO Auto-generated method stub12.return 0;13.}14. 15.@Override16.public String getType(Uri arg0) {17.// TODO Auto-generated method stub18.return null;19.}20. 21.@Override22.public Uri insert(Uri arg0, ContentValues arg1) {23.// TODO Auto-generated method stub24.return null;25.}26. 27.@Override28.public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3,29.String arg4) {30.// TODO Auto-generated method stub31.return null;32.}33. 34.@Override35.public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {36.// TODO Auto-generated method stub37.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";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 stub09.}10. 11.@Override12.public void onCreate(SQLiteDatabase db) {13.// TODO Auto-generated method stub14.db.execSQL(ContactsData.SQL_CREATE_TABLE);15.}16. 17.@Override18.public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {19.// TODO Auto-generated method stub20.onCreate(db);21.}22. 23.}(6) 现在来分别实现第一步中未实现的5个方法,先来实现query方法,这里借助SQLiteQueryBuilder来为查询设置投影映射以及设置相关查询条件,看源码实现:
01.@Override02.public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,03.String sortOrder) {04.// TODO Auto-generated method stub05.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, null, null, orderBy);27. 28.return cursor;29.}返回的是一个Cursor对象,它是一个行集合,包含0和多个记录,类似于JDBC中的ResultSet,可以前后移动游标,得到每行每列中的数据。注意的是,使用它需要调用moveToFirst(),因为游标默认是在第一行之前。
(7)实现insert方法,实现把记录插入到基础数据库中,然后返回新创建的记录的URI。
01.@Override02.public Uri insert(Uri uri, ContentValues values) {03.// TODO Auto-generated method stub04.SQLiteDatabase db = mDbHelper.getWritableDatabase();05.long id = db.insertOrThrow(ContactsData.TABLE_NAME, null, values);06. 07.// 更新数据时,通知其他ContentObserver08.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.@Override02.public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {03.// TODO Auto-generated method stub04.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.// 更新数据时,通知其他ContentObserver21.getContext().getContentResolver().notifyChange(ContactsData.CONTENT_URI, null);22. 23.return modified;24.}notifyChange函数是在更新数据时,通知其他监听对象。
(9)实现delete方法,该方法返回删除的记录数。
01.@Override02.public int delete(Uri uri, String selection, String[] selectionArgs) {03.// TODO Auto-generated method stub04.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.// 更新数据时,通知其他ContentObserver21.getContext().getContentResolver().notifyChange(ContactsData.CONTENT_URI, null);22. 23.return deleted;24.}(10) 实现getType方法,根据URI返回MIME类型,这里主要用来区分URI是获取集合还是单条记录,这个方法在这里暂时没啥用处,在使用Intent时有用。
01.@Override02.public String getType(Uri uri) {03.// TODO Auto-generated method stub04.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.<provider2.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'", 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.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.@Override04.public void onChange(boolean selfChange) {05.// TODO Auto-generated method stub06.Log.d("Test", "mContentObserver onChange");07.super.onChange(selfChange);08.}09. 10.};11.@Override12.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 stub09.}10. 11.@Override12.public void onCreate(SQLiteDatabase db) {13.// TODO Auto-generated method stub14.db.execSQL(ContactsData.SQL_CREATE_TABLE);15.}16. 17.@Override18.public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {19.// TODO Auto-generated method stub20.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.}结果如下:

AndroidContentProvider ContentResolver和ContentObserver的使用的更多相关文章
- ContentProvider、ContentResolver、ContentObserver之间的关系
ContentProvider.ContentResolver.ContentObserver之间的关系 ContentPRrovider: * 四大组件的内容提供者,主要用于对外提供数据 * 实现各 ...
- Android ContentProvider、ContentResolver和ContentObserver的使用
1.ContentProvider.ContentResolver和ContentObserver ContentProvider是Android的四大组件之中的一个,可见它在Android中的作用非 ...
- ContentProvider 、 ContentResolver 、 ContentObserver
说说ContentProvider . ContentResolver . ContentObserver 之间的关系**a. ContentProvider 内容提供者,用于对外提供数据 b. Co ...
- ContentResolver,ContentProvider,ContentObserver使用记录
版权声明:本文出自汪磊的博客,转载请务必注明出处. 本篇博客只是记录一下ContentProvider的使用(这部分工作中用的比较少总是忘记),没有太深入研究.已经熟练掌握使用方式,想深入了解内部机制 ...
- ContentProvider,ContentResolver和ContentObserver 使用
1 ContentProvider内容提供者 四大组件之一,实现不同程序实现数据的共享.联系人应用就使用了ContentProvider,比如你在自己的应用可以读取和修改联系人的数据(获得相应权限). ...
- ContentProvider要点复习
ContentProvider要点复习 ContentProvider作为四大组件之一,发挥着举足轻重的作用.与之相关联的另外两个类分别是ContentResolver和ContentObserver ...
- 【Android】面试宝典
Android面试 1. 内容介绍................................................................................... ...
- 安卓面试题 Android interview questions
安卓面试题 Android interview questions 作者:韩梦飞沙 2017年7月3日,14:52:44 1. 要做一个尽可能流畅的ListView,你平时在 ...
- Android数据存储五种方式
1 使用SharedPreferences存储数据:常用于做本地缓存 2 文件存储数据:(1)data/data/<package name>/files目录内 (2)SDCard内 ...
随机推荐
- Qt for Android遇到的几个错误解决[Win7 + Qt5.6 +jdk 8u91]
[1]SDK Manager无法更新Android SDK安装后需要运行SDK Manager下载安装包,默认从google网站下载,但被GWF和谐了,感谢一位网友提供的镜像站点.具体步骤是:运行SD ...
- C++STL_类模板举例
#include<stdio.h> #include<typeinfo.h> template <class T1,class T2> class A{ T1 i; ...
- 经典排序算法(Java实现)
以下程序均将数据封装于DataWrap数据包装类中,如下所示: //数据包装类 class DataWrap implements Comparable<DataWrap> { int d ...
- 查询无序列表中第K小元素
当需要在无需列表中寻找第k小的元素时,一个显然的方法是将所有数据进行排序,然后检索k个元素.这种方法的运行时间为O(n log(n)). 无序列表调用分区函数将自身分解成两个子表,其长度为i和n-i. ...
- AndroidUI 布局动画-布局内容改变动画
实现一个点击菜单动画添加按钮,点击按钮移除当前按钮的动画效果: <LinearLayout xmlns:android="http://schemas.android.com/apk/ ...
- java.lang.OutOfMemoryError: PermGen space 解决方案
只需两步: 将值改为512或者1024,然后CTRL+S,重启tomcat 和eclipse即可.
- SPOJ 1435 - Vertex Cover(树形DP,树的最小点覆盖)
算是个经典题目了,很模板的树形DP题目 做这个题的时候一开始就想到树形DP了,可是由于各种原因没写出来,代码太糟烂了,赛后还是改了好久才过的 dp(u,0)=sum(dp(v,1)): dp(u,1) ...
- collectionView关于点击事件
-(void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)i ...
- CLR via C# 阅读笔记
1.char在C#中为16位Unicode字符:int 映射到System.Int32;long映射到System.Int64. 2.重载时C#不考虑返回值,而CLR允许返回值不同,方法名和参数相同的 ...
- wamp 虚拟目录的设置(转载)
现在先来配置虚拟主机:1.先打开apache的配置文件httpd.conf,并去掉#Include conf/extra/httpd-vhosts.conf前面的#!!2.打开apache的apach ...