Android ContentProvider、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
public class ContactsContentProvider extends ContentProvider{ @Override
public boolean onCreate() {
// TODO Auto-generated method stub
return false;
} @Override
public int delete(Uri arg0, String arg1, String[] arg2) {
// TODO Auto-generated method stub
return 0;
} @Override
public String getType(Uri arg0) {
// TODO Auto-generated method stub
return null;
} @Override
public Uri insert(Uri arg0, ContentValues arg1) {
// TODO Auto-generated method stub
return null;
} @Override
public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3,
String arg4) {
// TODO Auto-generated method stub
return null;
} @Override
public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
// TODO Auto-generated method stub
return 0;
} }
(2)先来设计一个数据库,用来联系人信息,主要包括_ID,name,telephone,create_date,content五个字段。group_name字段等后面升级部分再做使用。
创建ProviderMetaData类,封装URI和数据库、表、字段相关信息,源代码例如以下:
public class ProviderMetaData { public static final String AUTHORITY = "com.johnny.contactsprovider";
public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY); public static final class ContactsData implements BaseColumns{
public static final String TABLE_NAME = "contacts";
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, TABLE_NAME); public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact"; public static final String CONTACT_NAME = "name";
public static final String CONTACT_TELEPHONE = "telephone";
public static final String CONTACT_CREATE_DATE = "create_date";
public static final String CONTACT_CONTENT = "content";
public static final String CONTACT_GROUP = "group_name"; public static final String DEFAULT_ORDERBY = "create_date DESC"; public static final String SQL_CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " ("
+ _ID + " INTEGER PRIMARY KEY,"
+ CONTACT_NAME + " VARCHAR(50),"
+ CONTACT_TELEPHONE + " VARCHAR(11),"
+ CONTACT_CONTENT +" TEXT,"
+ CONTACT_CREATE_DATE + " INTEGER"
+ ");" ;
}
}
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类型。以下看实现源代码:
static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
static final HashMap<String, String> CONTACTS_PROJECTION_MAP = new HashMap<String, String>();
private static final int CONTACTS = 1;
private static final int CONTACTS_ID = 2;
static{
final UriMatcher matcher = URI_MATCHER;
matcher.addURI(ProviderMetaData.AUTHORITY, "contacts", CONTACTS);
matcher.addURI(ProviderMetaData.AUTHORITY, "contacts/#", CONTACTS_ID); HashMap<String, String> map = CONTACTS_PROJECTION_MAP;
map.put(ContactsData._ID, ContactsData._ID);
map.put(ContactsData.CONTACT_NAME, ContactsData.CONTACT_NAME);
map.put(ContactsData.CONTACT_TELEPHONE, ContactsData.CONTACT_TELEPHONE);
map.put(ContactsData.CONTACT_CONTENT, ContactsData.CONTACT_CONTENT);
map.put(ContactsData.CONTACT_CREATE_DATE, ContactsData.CONTACT_CREATE_DATE);
}
这段代码是NoteContentProvider类中的。UriMatcher的工作原理:首先须要在UriMatcher中注冊URI模式。每个模式跟一个唯一的编号关联,注冊之后,在使用中就能够依据URI得到相应的编号。当模式不匹配时,UriMatcher将返回一个NO_MATCH常量,这样就能够区分了。
(4) 还需为查询设置一个投影映射,主要是将抽象字段映射到数据库中真实的字段名称,由于这些字段有时是不同的名称。既抽象字段的值能够不跟数据库中的字段名称一样。
这里使用HashMap来完毕,key是抽象字段名称,value相应数据库中的字段名称,只是这里我把两者的值设置是一样的,在NoteContentProvider.java中加入如上面所看到的的代码。
(5) 在NoteContentProvider.java中创建一个内部类DatabaseHelper。继承自SQLiteOpenHelper,完毕数据库表的创建、更新,这样能够通过它获得数据库对象,相关代码例如以下。
private class DatabaseHelper extends SQLiteOpenHelper{ static final String DATABASE_NAME = "test.db";
static final int DATABASE_VERSION = 1; public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
// TODO Auto-generated constructor stub
} @Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
db.execSQL(ContactsData.SQL_CREATE_TABLE);
} @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
onCreate(db);
} }
(6) 如今来分别实现第一步中未实现的5个方法,先来实现query方法。这里借助SQLiteQueryBuilder来为查询设置投影映射以及设置相关查询条件。看源代码实现:
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
// TODO Auto-generated method stub
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
switch(URI_MATCHER.match(uri)){
case CONTACTS_ID:
queryBuilder.setTables(ContactsData.TABLE_NAME);
queryBuilder.setProjectionMap(CONTACTS_PROJECTION_MAP);
queryBuilder.appendWhere(ContactsData.TABLE_NAME + "._id="+Long.toString(ContentUris.parseId(uri)));
break;
case CONTACTS:
queryBuilder.setTables(ContactsData.TABLE_NAME);
queryBuilder.setProjectionMap(CONTACTS_PROJECTION_MAP);
break;
} String orderBy;
if(TextUtils.isEmpty(sortOrder))
{
orderBy = ContactsData.DEFAULT_ORDERBY;
} else {
orderBy = sortOrder;
}
SQLiteDatabase db = mDbHelper.getReadableDatabase();
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, orderBy); return cursor;
}
返回的是一个Cursor对象,它是一个行集合,包括0和多个记录,类似于JDBC中的ResultSet,能够前后移动游标。得到每行每列中的数据。注意的是。使用它须要调用moveToFirst(),由于游标默认是在第一行之前。
(7)实现insert方法,实现把记录插入到基础数据库中。然后返回新创建的记录的URI。
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
SQLiteDatabase db = mDbHelper.getWritableDatabase();
long id = db.insertOrThrow(ContactsData.TABLE_NAME, null, values); // 更新数据时,通知其它ContentObserver
getContext().getContentResolver().notifyChange(ContactsData.CONTENT_URI, null); if(id > 0){
return ContentUris.withAppendedId(uri, id);
}
return null;
}
(8) 实现update方法,依据传入的列值和where字句来更新记录,返回更新的记录数,看源代码:
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
SQLiteDatabase db = mDbHelper.getWritableDatabase();
int modified = 0;
switch(URI_MATCHER.match(uri)){
case CONTACTS_ID:
selection = DatabaseUtils.concatenateWhere(selection,ContactsData.TABLE_NAME + "._id=?");
selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
new String[]{Long.toString(ContentUris.parseId(uri))});
Log.d("Test", "selectionArgs 0"+selectionArgs);
modified = db.update(ContactsData.TABLE_NAME, values, selection, selectionArgs);
break;
case CONTACTS:
modified = db.update(ContactsData.TABLE_NAME, values, selection, selectionArgs);
Log.d("Test", "selectionArgs 1"+selectionArgs);
break;
} // 更新数据时,通知其它ContentObserver
getContext().getContentResolver().notifyChange(ContactsData.CONTENT_URI, null); return modified;
}
notifyChange函数是在更新数据时,通知其它监听对象。
(9)实现delete方法,该方法返回删除的记录数。
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
SQLiteDatabase db = mDbHelper.getWritableDatabase();
int deleted = 0;
switch(URI_MATCHER.match(uri)){
case CONTACTS_ID:
selection = DatabaseUtils.concatenateWhere(selection,ContactsData.TABLE_NAME + "._id=? ");
selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
new String[]{Long.toString(ContentUris.parseId(uri))});
Log.d("Test", "selectionArgs 0"+selectionArgs);
deleted = db.delete(ContactsData.TABLE_NAME, selection, selectionArgs);
break;
case CONTACTS:
deleted = db.delete(ContactsData.TABLE_NAME, selection, selectionArgs);
Log.d("Test", "selectionArgs 1"+selectionArgs);
break;
} // 更新数据时,通知其它ContentObserver
getContext().getContentResolver().notifyChange(ContactsData.CONTENT_URI, null); return deleted;
}
(10) 实现getType方法,依据URI返回MIME类型。这里主要用来区分URI是获取集合还是单条记录。这种方法在这里临时没啥用处,在使用Intent时实用。
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
switch(URI_MATCHER.match(uri)){
case CONTACTS:
return ContactsData.CONTENT_TYPE;
case CONTACTS_ID:
return ContactsData.CONTENT_ITEM_TYPE;
// default:
// throw new IllegalArgumentException("Unknow URI: " + uri);
}
return null;
}
(11) 在AndroidManifest.xml中注冊该ContentProvider,这样系统才找得到,当然你也能够设置相关的权限。这里就不设置了
<provider
android:name="com.johnny.testcontentprovider.ContactsContentProvider"
android:authorities="com.johnny.contactsprovider"> </provider>
(12)到如今为止。自己定义ContentProvider的所有代码已经完毕。以下创建一个简单的应用来測试一下。
主要測试insert、update、delete、query这四个函数。
private void insertContact1(){
ContentValues values = new ContentValues();
values.put(ContactsData.CONTACT_NAME, "James");
values.put(ContactsData.CONTACT_TELEPHONE, "18888888888");
values.put(ContactsData.CONTACT_CONTENT, "NBA Star");
values.put(ContactsData.CONTACT_CREATE_DATE, System.currentTimeMillis());
Uri uri = getContentResolver().insert(ContactsData.CONTENT_URI, values);
Log.d("Test", "uri = "+uri);
} private void deleteContact1(){
int count = getContentResolver().delete(ContactsData.CONTENT_URI, ContactsData.CONTACT_NAME+"='James'", null);
Log.d("Test", "count = "+count);
} private void updateContact1(){
ContentValues values = new ContentValues();
values.put(ContactsData.CONTACT_TELEPHONE, "16666666666");
int count = getContentResolver().update(ContactsData.CONTENT_URI,values, ContactsData.CONTACT_NAME+"='James'", null);
Log.d("Test", "count = "+count);
} private void updateContact1ID(){
ContentValues values = new ContentValues();
values.put(ContactsData.CONTACT_TELEPHONE, "17777777");
int count = getContentResolver().update(ContentUris.withAppendedId(ContactsData.CONTENT_URI,2),values, null, null);
Log.d("Test", "count = "+count);
} private void queryContact1(){
//Cursor cursor = this.getContentResolver().query(ContactsData.CONTENT_URI, null, ContactsData.CONTACT_NAME+"='James'", null, null);
Cursor cursor = this.getContentResolver().query(ContactsData.CONTENT_URI, null, ContactsData.CONTACT_NAME+"=? ", new String [] {"James"}, null);
Log.e("test ", "count=" + cursor.getCount());
cursor.moveToFirst();
while(!cursor.isAfterLast()) {
String name = cursor.getString(cursor.getColumnIndex(ContactsData.CONTACT_NAME));
String telephone = cursor.getString(cursor.getColumnIndex(ContactsData.CONTACT_TELEPHONE));
long createDate = cursor.getLong(cursor.getColumnIndex(ContactsData.CONTACT_CREATE_DATE));
Log.e("Test", "name: " + name);
Log.e("Test", "telephone: " + telephone);
Log.e("Test", "date: " + createDate); cursor.moveToNext();
}
cursor.close();
}
在插入两个数据后分别运行updateContact1()和updateContact1ID()函数。你会发现他们两个分别运行了ContactsContentProvider.update的两个不同地方:分别为case CONTACTS:和case CONTACTS_ID:,如今能够理解
matcher.addURI(ProviderMetaData.AUTHORITY, "contacts", CONTACTS);
matcher.addURI(ProviderMetaData.AUTHORITY, "contacts/#", CONTACTS_ID);
的不同含义了吧?
(13)创建数据库监听器ContentObserver
在MainActivity中增加下面代码:
private ContentObserver mContentObserver = new ContentObserver(new Handler()) { @Override
public void onChange(boolean selfChange) {
// TODO Auto-generated method stub
Log.d("Test", "mContentObserver onChange");
super.onChange(selfChange);
} };
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment()).commit();
} getContentResolver().registerContentObserver(ContactsData.CONTENT_URI, true, mContentObserver); }
每次通过insert、delete、update改变数据库内容时。都会调用ContentObserver的onChange方法,因此。能够在这种方法内做出针对数据库变化的反应。比方更新UI等。
2)、数据库的升级
当应用公布一段时间之后,我们须要改变数据库的结构,那么就须要对数据库的升级了:
将DatabaseHelper类中的DATABASE_VERSION设置为2,而且在onUpgrade函数中实现升级的代码:
private class DatabaseHelper extends SQLiteOpenHelper{ static final String DATABASE_NAME = "test.db";
static final int DATABASE_VERSION = 2; public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
// TODO Auto-generated constructor stub
} @Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
db.execSQL(ContactsData.SQL_CREATE_TABLE);
} @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
Log.d("Test", "onUpgrade oldVersion = "+oldVersion+", newVersion = "+newVersion);
//onCreate(db);
for(int i = oldVersion+1;i <= newVersion;i++){
switch(i){
case 2:
db.execSQL("ALTER TABLE " + ContactsData.TABLE_NAME + " ADD COLUMN " + ContactsData.CONTACT_GROUP + " TEXT");
break;
}
}
} }
也要记得要对onCreate做对应的改动,是用户清除数据时又一次建表是也会生效
public static final String SQL_CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " ("
+ _ID + " INTEGER PRIMARY KEY,"
+ CONTACT_NAME + " VARCHAR(50),"
+ CONTACT_TELEPHONE + " VARCHAR(11),"
+ CONTACT_CONTENT +" TEXT,"
+ CONTACT_CREATE_DATE + " INTEGER,"
+ CONTACT_GROUP + " TEXT"
+ ");" ;
以下是升级前后数据库的结果:
用以下代码为DATABASE_VERSION = 2的数据库中的James设在组别和增加Howard联系人:
private void modifyContact1(){
ContentValues values = new ContentValues();
values.put(ContactsData.CONTACT_GROUP, "Miami");
int count = getContentResolver().update(ContactsData.CONTENT_URI,values, ContactsData.CONTACT_NAME+"='James'", null);
Log.d("Test", "count = "+count);
} private void insertContact2(){
ContentValues values = new ContentValues();
values.put(ContactsData.CONTACT_NAME, "Howard");
values.put(ContactsData.CONTACT_TELEPHONE, "13333333333");
values.put(ContactsData.CONTACT_CONTENT, "NBA Star");
values.put(ContactsData.CONTACT_GROUP, "Rockets");
values.put(ContactsData.CONTACT_CREATE_DATE, System.currentTimeMillis());
Uri uri = getContentResolver().insert(ContactsData.CONTENT_URI, values);
Log.d("Test", "uri = "+uri);
}
private void queryContacts1(){
Cursor cursor = this.getContentResolver().query(ContactsData.CONTENT_URI, null, ContactsData.CONTACT_GROUP+" IN ('Miami','Rockets')", null, null);
Log.e("Test ", "count=" + cursor.getCount());
cursor.moveToFirst();
while(!cursor.isAfterLast()) {
String name = cursor.getString(cursor.getColumnIndex(ContactsData.CONTACT_NAME));
String telephone = cursor.getString(cursor.getColumnIndex(ContactsData.CONTACT_TELEPHONE));
long createDate = cursor.getLong(cursor.getColumnIndex(ContactsData.CONTACT_CREATE_DATE));
Log.e("Test", "name: " + name);
Log.e("Test", "telephone: " + telephone);
Log.e("Test", "date: " + createDate);
cursor.moveToNext();
}
cursor.close();
}
结果例如以下:
參考:http://codingnow.cn/android/1078.html
Android ContentProvider、ContentResolver和ContentObserver的使用的更多相关文章
- ContentProvider、ContentResolver、ContentObserver之间的关系
ContentProvider.ContentResolver.ContentObserver之间的关系 ContentPRrovider: * 四大组件的内容提供者,主要用于对外提供数据 * 实现各 ...
- ContentProvider 、 ContentResolver 、 ContentObserver
说说ContentProvider . ContentResolver . ContentObserver 之间的关系**a. ContentProvider 内容提供者,用于对外提供数据 b. Co ...
- AndroidContentProvider ContentResolver和ContentObserver的使用
1.ContentProvider.ContentResolver和ContentObserver ContentProvider是Android的四大组件之一,可见它在Android中 的作用非同小 ...
- Android ContentProvider数据共享
一.构造一个自己的Provider实现App之间数据共享 1.我们先来了解一下 Uri(统一资源定位符) 定义:每一个Content Provider使用一个公开的URI唯一标示其数据集,Andr ...
- Android基础 : Android ContentProvider
Android 应用程序通过ContentProvider实现方式统一的数据共享功能. 外界的程序通过ContentResolver接口可以访问ContentProvider提供的数据,在Activi ...
- Android ContentProvider 简单学习
当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据.以前我们学习过文件的操作模式,通过指定文件的操作模式为Context.MODE_WORL ...
- Android ContentProvider 简介
当在系统中部署一个又一个Android应用之后,系统里将会包含多个Android应用,有时候就需要在小同的应用之问芡亭数据,比如现在有一个短信接收应用,用户想把接收到的陌生短信的发信人添加到联系人管理 ...
- Android ContentProvider完整案例
ContentData类,提供数据常量: /** * 提供ContentProvider对外的各种常量,当外部数据需要访问的时候,就可以参考这些常量操作数据. * @author HB * */ pu ...
- android contentprovider内容提供者
contentprovider内容提供者:让其他app可以访问私有数据库(文件) 1.AndroidManifest.xml 配置provider <?xml version="1.0 ...
随机推荐
- 初学 Haskell 练习:算24点
其中用到了 Monad 做不确定性计算.运行速度很快. -- woodfox, Oct 10, 2014 import Control.Applicative import Control.Monad ...
- c语言中使用宏,需要注意的的几点
使用#define来定义一些宏,进行一些简洁的替换甚至一些带参数的宏,在linux c代码中很常见,说明它很好.很有用, 但是它也有一些复杂的规矩和陷阱需要注意,下面我记录一些,仅供参考. 1.当使用 ...
- Diamond 3.5简易教程(一)------工程的建立
测试环境(win10 x64 软件Diamond 3.5 x64) 软件下载地址:http://files.latticesemi.com/Diamond/3.5/3.5.0.102_Diamond_ ...
- JMeter学习笔记(六)-负载与监听
1. 场景设计 场景设计的原则:忠于用户实际操作,组合用户的各种操作到场景中来. JMeter场景主要通过线程组设置来完成的,对于复杂场景还需要与逻辑控制器配合完成. 2.场景设置 JMeter线程组 ...
- 日期时间函数(1)-time()&gmtime()&strftime()&localtime()
◆time() 取得当前时间.此函数会返回从公元1970年1月1日的UTC时间从0时0分0秒算起到现在所经过的秒数.如果参数t为非空指针的话, 此函数也会将返回值存到t指针所指的内存. 成功则返回秒数 ...
- 【转】10张图带你深入理解Docker容器和镜像
[转自]:http://dockone.io/article/783 待续
- 常见电源品牌大揭密(转贴自pceva,作者royalk)
常见电源品牌大揭密(转贴自pceva,作者royalk) 介绍电源品牌代工厂之前,必须介绍一下电源分类: 标准电源 标准电源就是电脑城装机用得最多的电源,性能正常,外观一般,原生接线,也没有什么风扇停 ...
- 汇编入门学习笔记 (九)—— call和ret
疯狂的暑假学习之 汇编入门学习笔记 (九)-- call和ret 參考: <汇编语言> 王爽 第10章 call和ret都是转移指令. 1. ret和retf ret指令:用栈中的数据 ...
- 【Unity笔记】UGUI的Image、RawImage控件
Image控件只能使用Sprite图片,RawImage通常使用Texture类型图片.项目设为2D模式后导入的图片Texture Type会自动转为Sprite. 没有选择源图片时,可以只选择颜色. ...
- Linux下Kill函数用法
http://www.cnblogs.com/winnxm/archive/2010/01/22/1654502.html 用于向任何进程组或进程发送信号. #include <sys/type ...