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, null, null, 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'", 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.@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.}

结果如下:

最新文章

  1. 25 highest paying companies: Which tech co outranks Google, Facebook and Microsoft?
  2. jieba中文分词的.NET版本:jieba.NET
  3. VMware10.06精简版安装后台运行
  4. TCP字节流和UDP数据报区别
  5. HDU 1564 Play a game (找规律博弈)
  6. hadoop2 作业执行过程之yarn调度执行
  7. Ural1076(km算法)
  8. 不只是打车软件,中国车主们赋予了Uber更多意义
  9. JavaScript高级程序设计56.pdf
  10. css三角形绘制
  11. spring笔记(一)
  12. web.xml中classpath:和classpath*: 有什么区别?
  13. cocos2D-x 3.5 引擎解析之--引用计数(Ref),自己主动释放池(PoolManager),自己主动释放池管理器( AutoreleasePool)
  14. Entity Framework 学习中级篇1—EF支持复杂类型的实现
  15. ecshop和jQuery冲突
  16. 对于CocoaPods的简单理解,实践安装使用过程和常见问题
  17. springcloud 入门 10 (eureka高可用)
  18. NCBI上查看SNP位点在哪个基因座上(locus)
  19. JPA实体类中常用的注解
  20. jupyter notebook 小笔记

热门文章

  1. javascript圆形排列
  2. 矩阵转置 O(1)空间
  3. 给ecshop后台增加管理功能页面
  4. java web基础环境搭建
  5. 普林斯顿大学算法课 Algorithm Part I Week 3 比较器 Comparators
  6. 【Python】iichats —— 命令行下的局域网聊天程序
  7. 怎样学习使用libiconv库
  8. android 计时器,倒计时
  9. 3种SQL语句分页写法
  10. SQL中的去重操作