Android使用ContentProvider监听数据库

来源:互联网 发布:美亚保险 淘宝 编辑:程序博客网 时间:2024/06/05 15:00

一 前言

         在前面的一篇文章《ContentProvider简介》中对ContentProvider及其相关的几个类做了介绍,接下来要做的工作就是搞明白如何给ContentProvider设计监听,如何使用ContentProvider实现对数据库的监听,那么为什么要了解这个东西呢?这个是我曾经去一家公司面试的时候遇到的问题,当时是在聊即时通讯,面试官问道:“群聊储存聊天信息时,你时如何监听数据库的变化呢?”当时在下还未曾做过即时通讯,所以回答的很不好,面试过后回来就goole了这个问题,整理一下思路就放到那里了,如今公司的任务不是很重,就想着把之前学习的内容整理下分享到博客,希望能够跟更多的志同道合的朋友们进行交流。

二 ContentObserve类

1 简介       
        毫无疑问,ContentObserve就是用来监听ContentProvider的类了,因为无论是自定义的ContentProvider,还是系统提供的ContentProvider,在实现insert、update、delete方法时,都调用下面一行的代码:
//通知所用注册该Uri的监听者,数据发生了变化
getContext().getContentResolver().notifyChange(uri,null);
这行代码会通知所有注册该Uri的监听者,该ContentProvider的共享数据发生变化了,这样一来就可以及时去更新界面显示的内容。注册该监听器的方式如下:
public final void registerContentObserver(Uri uri, boolean notifyForDescendants,ContentObserver observer)
说明:该方法是ContentResolver类中的方法,所以可以通过该类的对象实例可以进行注册;
  • 参数uri:该监听器所要监听的ContentProvide的uri
  • 参数notifyForDescendants: 假设注册监听的Uri为 content://abc notifyForDescendants为 true,那么Uri为content://abc/lzb、 content://abc/xxx的数据发生变化时,也能触发监听器,否则不能触发监听器
  • 参数observer:监听器实例
2 实现
/** 内容提供者监听类 */
private ContentObserver contentObserver = new ContentObserver(handler) {
@Override
public boolean deliverSelfNotifications() {
Log.i(TAG ,"deliverSelfNotifications");
return super.deliverSelfNotifications();
}

/**
* ContentProvider的内容发生变化时,触发该方法
* @param selfChange
*/
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Log.i(TAG ,"onChange-------->");
}
/**
* ContentProvider的内容发生变化时,触发该方法 这个与上方法相比多个Uri
* @param selfChange
*/
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
Log.i(TAG ,"onChange-------->" + uri);
handler.sendEmptyMessage(0x123);
}
};
说明:在创建ContentObserver实例时,需要参数Handle对象,因此:
private Handler handler  = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == 0x123){
Toast.makeText(MainActivity.this, "数据发生变化", Toast.LENGTH_SHORT).show();
}
return false;
}
});

三 监听用户发出的短信信息

      在实现监听自己的数据库之前我们先来看看监听系统提供的ContentProvider,示例代码如下:
public class SmsActivity extends AppCompatActivity {
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == 0x123){
Uri uri= Uri.parse("content://sms/sent");
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
StringBuffer buffer = new StringBuffer();
while (cursor.moveToNext()){
buffer.append("地址:").append(cursor.getString(cursor.getColumnIndex("address")));
buffer.append("收件人:").append(cursor.getString(cursor.getColumnIndex("person")));
buffer.append("内容:").append(cursor.getString(cursor.getColumnIndex("body")));
buffer.append("\n");
}
txt_body.setText(buffer.toString());
Toast.makeText(SmsActivity.this, buffer.toString(), Toast.LENGTH_SHORT).show();
}
return false;
}
});

/** 内容提供者监听类 */
private ContentObserver contentObserver = new ContentObserver(handler) {
@Override
public boolean deliverSelfNotifications() {
return super.deliverSelfNotifications();
}

/**
* ContentProvider的内容发生变化时,触发该方法
* @param selfChange
*/
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
handler.sendEmptyMessage(0x123);
}
/**
* ContentProvider的内容发生变化时,触发该方法 这个与上方法相比多个Uri
* @param selfChange
*/
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
// handler.sendEmptyMessage(0x123);

}
};
private TextView txt_body;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sms);
txt_body = (TextView) findViewById(R.id.txt_body);
Uri uri= Uri.parse("content://sms");
getContentResolver().registerContentObserver(uri,true,contentObserver);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
requestPermissions(new String[]{Manifest.permission.READ_SMS},0x234);
}
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 0x234){
if (grantResults[0] != 0){
finish();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
getContentResolver().unregisterContentObserver(contentObserver);
}
}
说明:上述示例对值为content://sms/的Uri进行监听,一旦该Uri对应的ContentProvider发生变化时,就会触发onChange方法,然后查询发件箱,显示其所有的内容。
【知识扩展】
有关短信的Uri:
/**信息箱 全*/
public final static String SMS_URI_ALL = "content://sms/";
/**收件箱*/
public final static String SMS_URI_INBOX = "content://sms/inbox";
/**发件箱*/
public final static String SMS_URI_SEND = "content://sms/sent";
/**草稿箱*/
public final static String SMS_URI_DRAFT = "content://sms/draft";
sms主要结构:

_id:短信序号,如100
thread_id:对话的序号,如100,与同一个手机号互发的短信,其序号是相同的
address:发件人地址,即手机号,如+8613811810000
person:发件人,如果发件人在通讯录中则为具体姓名,陌生人为null
date:日期,long型,如1256539465022,可以对日期显示格式进行设置
protocol:协议0SMS_RPOTO短信,1MMS_PROTO彩信
read:是否阅读0未读,1已读
status:短信状态-1接收,0complete,64pending,128failed
type:短信类型1是接收到的,2是已发出
body:短信具体内容
service_center:短信服务中心号码编号,如+8613800755500

四 实现数据库监听(重点)

        上面,我们已经实现了对系统的ContentProvider监听,那么接下来就是实现自定义ContentProvider对自己的数据库进行监听,大致步骤如下:
(1)自定义数据库帮助类继承SQLiteOpenHelp,创建自己的数据库;
(2)自定义ContentProvider继承ContentProvider,实现其抽象方法;
(3)在配置文件中注册与配置该ContentProvider,如果想要外部应用可以访问该ContentProvider的数据实现共享,要把exported属性值设置为true;
(4)使用UriMatcher工具类,注册可用的Uri,确保访问者的Uri的正确性;
(5)在自定义的ContentProvider的insert、query、update、delete方法中实现数据库的CRUD操作(其实就是ContentProvider绑定自己的数据库),在必要的地方通知注册该Uri的监听者,该ContentProvider的共享数据已经发生了变化;
(6)创建Handle对象,把该Handle对象作为参数去创建监听器对象(ContentObserver),并在OnChange方法中实现自己的业务(共享数据发生变化时,会触发该方法);
(7)注册监听;
(8)应用推出时,反注册该监听;
接下来,我们看看具体的代码如何实现的。
1 自定义SQLiteOpenHelp类
public class SQLiteOpenHelperUtil extends SQLiteOpenHelper {
/**数据库名*/
private static final String DB_NAME = "DBStudent";
/**表名*/
public static final String TABLE_NAME = "GradeOne";
public static String ID = "_id";
public static String STUDENT_ID = "studentId";
public static String MESSAGE = "message";
public static String SQL = "create table if not exists GradeOne ( _id integer primary key autoincrement, studentId integer , message varchar ) ";
public SQLiteOpenHelperUtil(Context context) {
super(context, DB_NAME, null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(SQL);
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

}
}
说明:这个很简单就是使用SQL语句在自己的数据库中创建一个表。
2 自定义ContentProvider类
public class MyContentProvide extends ContentProvider{
@Override
public boolean onCreate() {
return false;
}
@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
@Nullable
@Override
public String getType(Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}
说明:具体每个方法的实现下面介绍。
3 注册与配置该ContentProvider
<provider
android:name=".MyContentProvide"
android:authorities="com.lzb.provide.myContentProvide"
android:exported="true" />
说明:上述代码属性authorities的值为com.lzb.provide.myContentProvide ,那么要访问该ContentProvider的数据,Uri标识必须是com.lzb.provide.myContentProvide 。
4 使用UriMatcher工具类,注册可用的Uri
/**标识码*/
public static final int CODE_ID_1 = 1;
public static final int CODE_ID_2 = 2;
public static final String HOST = "com.lzb.provide.myContentProvide";
/**路径*/
public static final String PATH = "students";
/**初始化UriMatcher工具类*/
private static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
// # 为通配符
uriMatcher.addURI(HOST,PATH,CODE_ID_1);
uriMatcher.addURI(HOST,PATH + "/#",CODE_ID_2);
}
说明:关于UriMatcher在《ContentProvider简介》中介绍过,这里不在累述。
5 ContentProvider绑定自己的数据库并实现CRUD操作(重点)
(1)onCreate方法的实现
public boolean onCreate() {
sqLiteOpenHelperUtil = new SQLiteOpenHelperUtil(getContext());
return sqLiteOpenHelperUtil != null;
}
说明:该方法在ContentProvider创建后会被调用,当其他应用程序第一次访问ContentProvide时,该ContentProvider会被创建出来,并立即回调该onCreate方法,在这个方法中我们创建数据库的帮助类,用于下面的读写操作。
(2)getType方法的实现
public String getType(Uri uri) {
//根据前面注册的Uri返回对应的标识码,如果找不到返回-1
int code = uriMatcher.match(uri);
switch (code){
case CODE_ID_1:
//vnd.android.cursor.dir/
return SHARE_LIST_TYPE;
case CODE_ID_2:
//vnd.android.cursor.item/
return SHARE_ITEM_TYPE;
default:
throw new IllegalArgumentException("Unknown Uir :" + uri);
}
}

说明:该方法用于获取当前Uri所代表的数据的MIME类型。如果该Uri对应数据可能包括多条记录,那么MIME类型字符串应该以 vnd.android.cursor.dir/开头;如果该Uri对应的数据只包含一条记录,那么返回MIME类型字符串应该以vnd.android.cursor.item/开头。
(3)insert方法的实现
public Uri insert(Uri uri, ContentValues values) {
// 首先是看Uri和我们自定义的是否匹配,,匹配则将数据属性插入到数据库中并同志更新
SQLiteDatabase sqLiteDatabase = sqLiteOpenHelperUtil.getWritableDatabase();
if (uriMatcher.match(uri) != CODE_ID_1) {
throw new IllegalArgumentException("Unknown/Invalid URI : " + uri);
}
//行 id
long rowID = sqLiteDatabase.insert(SQLiteOpenHelperUtil.TABLE_NAME,null,values);
if (rowID != -1){ //添加数据成功
//通知所用注册该Uri的监听者,数据发生了变化
getContext().getContentResolver().notifyChange(uri,null);
}
return Uri.parse(SCHEME + "://" + HOST + "/" + PATH + "/" + values.getAsInteger(SQLiteOpenHelperUtil.STUDENT_ID));
}
说明:该方法根据Uri把values数据插入到对应的ContentProvider中(这里是自己的数据库),这个方法也很简单就是数据库的插入操作,但要注意,在插入成功后,不要忘记执行代码行:getContext().getContentResolver().notifyChange(uri,null)通知注册该Uri的监听者,数据发生了变化。
(4)query方法的实现
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// 首先是看Uri和我们自定义的是否匹配,,匹配则将数据属性插入到数据库中并同志更新
SQLiteDatabase sqLiteDatabase = sqLiteOpenHelperUtil.getReadableDatabase();
//SQLiteQueryBuilder是一个构造SQL查询语句的辅助类
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
//根据前面注册的Uri返回对应的标识码,如果找不到返回-1
int code = uriMatcher.match(uri);
Cursor cursor = null;
switch (code){
case CODE_ID_1:
builder.setTables(SQLiteOpenHelperUtil.TABLE_NAME);
break;
case CODE_ID_2:
//设置要查询的表名
builder.setTables(SQLiteOpenHelperUtil.TABLE_NAME);
//添加条件
builder.appendWhere(SQLiteOpenHelperUtil.STUDENT_ID + "=" + uri.getPathSegments().get(1));
break;
default:
throw new IllegalArgumentException("Unknown Uir :" + uri);
}
//使用负责类开始查询数据库
cursor = builder.query(sqLiteDatabase,projection,selection,selectionArgs,null,null,sortOrder);
return cursor;
}
说明:该方法根据Uri查询匹配selection条件的所有记录,但是对于标识码为CODE_ID_1,CODE_ID_2的对应Uri不一样,查询的条件不一样;对于CODE_ID_2对应的Uri多了个后面的学生ID号(一般情况下,Uri后面跟的ID是行号,这里示例需要定义为学生ID号,具体开发过程根据实际情况自定义),那么其查询的条件也相应多了个,因此如果访问者访问的Uri是标识码CODE_ID_2对应的Uri,那么就使用SQLiteQueryBuilder把这个条件添加上,
否则直接设置表名就可以了,然后进行数据库的查询操作返回游标尺就可以了。
(5)update方法的实现
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
// 首先是看Uri和我们自定义的是否匹配,,匹配则将数据属性插入到数据库中并同志更新
SQLiteDatabase sqLiteDatabase = sqLiteOpenHelperUtil.getWritableDatabase();
//根据前面注册的Uri返回对应的标识码,如果找不到返回-1
int code = uriMatcher.match(uri);
long studentId = 0;
String where ;
switch (code){
case CODE_ID_1:
if (!TextUtils.isEmpty(selection)){
where = "(" + selection + ")";
}else {
where = "";
}
break;
case CODE_ID_2:
if (!TextUtils.isEmpty(selection)){
where = "(" + selection + ") AND ";
}else {
where = "";
}
String segment = uri.getPathSegments().get(1);
studentId = Long.valueOf(segment);
where += "(" + SQLiteOpenHelperUtil.STUDENT_ID + "=" + studentId + ")";
break;
default:
throw new IllegalArgumentException("Unknown Uir :" + uri);
}
//受影响行数
int count = 0;
if (values.size() > 0 ){
count = sqLiteDatabase.update(SQLiteOpenHelperUtil.TABLE_NAME,values,where,selectionArgs);
getContext().getContentResolver().notifyChange(uri,null);
}
return count;
}

说明:该方法根据Uri更新匹配selection条件的所有记录,values为数据源,这个类似上面的查询操作,对于标识码为CODE_ID_1,CODE_ID_2的对应Uri更新条件不一样,不过这里不能使用SQLiteQueryBuilder帮助类了,要用原始的方式添加条件,再进行更新操作,最后不要忘记了执行代码行:getContext().getContentResolver().notifyChange(uri,null)通知注册该Uri的监听者,数据发生了变化。

(6)delete方法的实现

public int delete(Uri uri, String selection, String[] selectionArgs) {
// 首先是看Uri和我们自定义的是否匹配,,匹配则将数据属性插入到数据库中并同志更新
SQLiteDatabase sqLiteDatabase = sqLiteOpenHelperUtil.getWritableDatabase();
//根据前面注册的Uri返回对应的标识码,如果找不到返回-1
int code = uriMatcher.match(uri);
long studentId = 0;
String where ;
switch (code){
case CODE_ID_1:
if (!TextUtils.isEmpty(selection)){
where = "(" + selection + ")";
}else {
where = "";
}
break;
case CODE_ID_2:
if (!TextUtils.isEmpty(selection)){
where = "(" + selection + ") AND ";
}else {
where = "";
}
String segment = uri.getPathSegments().get(1);
studentId = Long.valueOf(segment);
where += "(" + SQLiteOpenHelperUtil.STUDENT_ID + "=" + studentId + ")";
break;
default:
throw new IllegalArgumentException("Unknown Uir :" + uri);
}
//受影响行数
int count = sqLiteDatabase.delete(SQLiteOpenHelperUtil.TABLE_NAME, where, selectionArgs);
getContext().getContentResolver().notifyChange(uri,null);
return count;
}

说明:分析同上。

MyContentProvider完整实现代码

6 创建监听器对象

private Uri uri;
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == 0x123){
if (uri != null){
Cursor cursor = contentResolver.query(uri,null,null,null,null);
if (null != cursor){
cursor.moveToNext();
int id = cursor.getInt(cursor.getColumnIndex(SQLiteOpenHelperUtil.STUDENT_ID));
String message = cursor.getString(cursor.getColumnIndex(SQLiteOpenHelperUtil.MESSAGE));
Toast.makeText(MainActivity.this, ("ID=" + id + " ; message=" + message), Toast.LENGTH_SHORT).show();
}
}else {
Toast.makeText(MainActivity.this, "数据发生变化", Toast.LENGTH_SHORT).show();
}
}
return false;
}
});

/** 内容提供者监听类 */
private ContentObserver contentObserver = new ContentObserver(handler) {
@Override
public boolean deliverSelfNotifications() {
Log.i(TAG ,"deliverSelfNotifications");
return super.deliverSelfNotifications();
}

@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Log.i(TAG ,"onChange-------->");
}

@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
Log.i(TAG ,"onChange-------->" + uri);
handler.sendEmptyMessage(0x123);
}
};

7 注册监听

protected void onResume() {
super.onResume();
contentResolver = getContentResolver();
/**
* 参数:
* 一 uri:该监听器所要监听的ContentProvide的uri
* 二 notifyForDescendants:假设注册监听的Uri为 content://abc notifyForDescendants为 true
* 那么Uri为content://abc/lzb、 content://abc/xxx的数据发生变化时,
* 也能触发监听器,否则不能触发监听器
* 三 observer:监听器实例
*/
contentResolver.registerContentObserver(MyContentProvide.CONTENT_URI, true, contentObserver);
}

8 反注册监听

@Override
protected void onDestroy() {
super.onDestroy();
contentResolver.unregisterContentObserver(contentObserver);
}

Activity完整实现代码

完整项目代码

五 ContentProvider暴露测试

      当然对上述的ContentProvider进行暴露测试:

完整暴露测试项目示例代码

参考:

《疯狂Android讲义》

http://blog.csdn.net/qinjuning/article/details/7047607

http://blog.csdn.net/a497393102/article/details/44223219





阅读全文
1 0
原创粉丝点击