当我们需要时刻查询某个数据库值是否发生了变化,如果开启一个线程对其时刻监测,就会导致的开销很大。这时我们需要内容观察者,来时刻检测内容是否发生变化。
则使用ContentObserver的情况主要有一下两者情况:
1、需要频繁检测的数据库或者某个数据是否发生改变,如果使用线程去操作,很不经济而且很耗时 ;
2、在用户不知晓的情况下对数据库做一些事件,比如:悄悄发送信息、拒绝接受短信黑名单等;
一、监测内容变化:
抽象类ContentResolver类中的方法原型如下:
public final void registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer)
功能:为指定的Uri注册一个ContentObserver派生类实例,当给定的Uri发生改变时,回调该实例对象去处理。
参数:uri 需要观察的Uri(需要在UriMatcher里注册,否则该Uri也没有意义了)
notifyForDescendents 为false 表示精确匹配,即只匹配该Uri
为true 表示可以同时匹配其派生的Uri
observer ContentObserver的派生类实例
public final void unregisterContentObserver(ContentObserver observer)
功能:取消对给定Uri的观察
参数: observer ContentObserver的派生类实例
这里是一个监测短信变化的案例:(运行这个软件时不要按返回键,那样会退出,应该按HOME键,使其在后台运行,这样就能检测其变化的内容)
<span style="font-size:14px;">package com.example.ContentView;
import android.app.Activity;
import android.content.ContentResolver;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://sms/");
resolver.registerContentObserver(uri, true, new MyObserver(
new Handler()));
}
private class MyObserver extends ContentObserver {
public MyObserver(Handler handler) {
super(handler);
}
// 当观察者观察到数据库的内容变化了,调用这个方法
// 观察到消息邮箱里面有一条数据库内容变化的通知
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Toast.makeText(MainActivity.this, "数据库的内容变化了", 1).show();
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://sms/");
Cursor cursor = resolver.query(uri, new String[] { "address",
"type", "date", "body" }, null, null, null);
cursor.moveToFirst();
String address = cursor.getString(0);
String body = cursor.getString(3);
System.out.println(address + "------" + body);
cursor.close();
}
}
}
</span>
运行结果:
二、读取联系人:
联系人的表比较复杂,所以想要获取其数据,可以先去将其数据库内容导出,看它有些什么内容。
可以发现联系人的数据库主要由三张基本表组成,分别是raw_contacts,data,mimetypes:
如图,读取所有的联系人的步骤已经列出来了
这需要注意的是,第二步和第三步的步骤是这样的,但是实际上不是依次读取data,mimetypes这两张表,而是读取view_data视图
<span style="font-size:14px;">package com.example.ReadContacts;
import android.app.Activity;
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View view) {
ContentResolver resolver = getContentResolver();
//获取raw_contacts表对应的uri
Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
Uri dateUri = Uri.parse("content://com.android.contacts/data");
Cursor cursor = resolver.query(uri, null, null, null, null);
while (cursor.moveToNext()) {
// 1.查询raw_contacts表,获取联系人的id
String id = cursor.getString(cursor.getColumnIndex("contact_id"));
if (id != null) {
//2.根据id查询data表,获取id对应联系人的数据
Cursor dataCursor = resolver.query(dateUri, null,
"raw_contact_id = ?", new String[] { id }, null);
while (dataCursor.moveToNext()) {
String data1 = dataCursor.getString(dataCursor
.getColumnIndex("data1"));
//这里不能填写mimetype_id,因为读的是
String mimetype = dataCursor.getString(dataCursor
.getColumnIndex("mimetype"));
System.out.println("data1:" + data1);
System.out.println("mimetype:" + mimetype);
}
dataCursor.close();
System.out.println("--------------");
}
}
cursor.close();
}
}
</span>
这里的布局代码和权限添加省略,记得就行。
运行结果是:
如果把mimetype改为mimetype_id,logcat会显示:
这里另外谈一个细节问题:倘若把上面的代码中的 if (id != null) 判断删去,然后删去一条联系人再执行程序读取联系人记录,这时就会发生错误:
错误显示的是第一行为null,读取不出联系人,而我们在通讯录中看到的记录却有可见的第一个联系人。
注意,这里说的是可见的联系人,既然这么说,那么就说明还有通讯录上看不到的联系人。
没错。
我把通讯录中的第一个人删去,而在系统中实际上并未删去,而是将这个联系人的contact_id设置为null,这样通讯录就读不出联系人了,这个联系人也就从通讯录中消失了:
如图所示的contact_id的被设置为null,就是被删去的记录,虽然删去了,但是在联系人的数据库中仍然存在。
Google工程师这样设计的原因是每个人的android都可以注册一个Google的帐号,这样通讯录就会云备份到云存储上,可是怎么更新云备份呢?如果我们直接将数据从数据库中删去,那么云备份就是重新遍历一次,把所有的联系人重新备份一次,这样工程量就比较大,效率也很低。所以Google工程师就把通讯录每个联系人设置一个contact_id,如果删去,就将其设置为null,这样同步备份的时候,只需要遍历contact_id,只将null值的联系人从云端删去,这样就提高的效率。
所以在读取联系人的时候,需要加一个限定的条件,将null的联系人排除出去,不用读它。
三、保存联系人到系统通讯录
类似写一条短信到系统,将联系人写入联系人通讯录,都可以实现将备份的记录恢复到系统。
这里的案例只是简单的将联系人写入通讯录,使用到内容提供者的插入方法:
具体的步骤可以查看二中的第二图,写联系人。
package com.example.writeContact;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View view) {
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
Uri dateUri = Uri.parse("content://com.android.contacts/data");
ContentValues values = new ContentValues();
// 获取最后一个人的ID是多少
Cursor cursor = resolver.query(uri, new String[] { "_id" }, null, null,
null);
cursor.moveToLast();
int lastId = cursor.getInt(0);
int newId = lastId + 1;
//1.在raw_contacts表中添加一个新的id
values.put("_id", newId);
resolver.insert(uri, values);
// 2.使用id向data表中添加数据
// 添加电话
ContentValues phoneValue = new ContentValues();
phoneValue.put("data1", "88888888");
phoneValue.put("mimetype", "vnd.android.cursor.item/phone_v2");
phoneValue.put("raw_contact_id", newId);
resolver.insert(dateUri, phoneValue);
// 添加邮件
ContentValues emailValue = new ContentValues();
emailValue.put("data1", "qiudong@gmail.com");
emailValue.put("mimetype", "vnd.android.cursor.item/email_v2");
emailValue.put("raw_contact_id", newId);
resolver.insert(dateUri, emailValue);
// 添加名字
ContentValues nameValue = new ContentValues();
nameValue.put("data1", "leifeng");
nameValue.put("mimetype", "vnd.android.cursor.item/name");
nameValue.put("raw_contact_id", newId);
resolver.insert(dateUri, nameValue);
Toast.makeText(this, "添加成功", 0).show();
}
}
记得添加权限: