ContentProvider
ContentProvider 简介
- 内容提供者是应用程序之间共享数据的接口
- 使用ContentProvider 共享数据的好处是统一了数据访问方式
- 内容提供者中数据更改可被监听
ContentProvider使用
- 定义类继承ContentProvider,根据需要重写内部方法(增删改查)
- 在清单文件的节点下进行配置,标签中需要指定name 和authorities 属性
name 为类名,包名从程序Package 开始,以“.”开始
authorities:是访问Provider 时的路径,要唯一
exported:true, 意思是是否对外发布,系统默认为false,要设置为true。
创建一个ContentProvider
示例 代码
public class PersonContentProvider extends ContentProvider {
// 用于存放并匹配个Uri 标识信息,一般在静态代码块中对其信息进行初始化操作
private static UriMatcher matcher;
// 声明一个用于操作数据库对象
private PersonOpenHelper openHelper;
// 主机名信息:对应清单文件的authorities 属性
private static final String AUTHORITY = "com.itheima.person";
// 数据库表名
private static final String TABLE_PERSON_NAME = "person";
// Uri 匹配成功的返回码
private static final int PERSON_INSERT_CODE = 1000;
private static final int PERSON_DELETE_CODE = 10001;
private static final int PERSON_UPDATE_CODE = 10002;
private static final int PERSON_QUERYALL_CODE = 10003;
private static final int PERSON_QUERYONE_CODE = 10004;
// 静态代码块,用于初始化UriMatcher
static {
// NO_MATCH:没有Uri 匹配的时候返回的状态码(-1)
matcher = new UriMatcher(UriMatcher.NO_MATCH);
// 添加一个分机号:
// 对person
// 表进行添加操作,如果Uri=content://com.itheima.person/person/insert,则返回PERSON_INSERT_CODE
matcher.addURI(AUTHORITY, "person/insert", PERSON_INSERT_CODE);
// 对person 表进行删除操作,如果Uri=content: // com.itheima.person/person/delete,
//则返回PERSON_DELETE_CODE
matcher.addURI(AUTHORITY, "person/delete", PERSON_DELETE_CODE);
// 对person 表进行修改操作,如果Uri=
content: // com.itheima.person/person/update,则返回PERSON_UPDATE_CODE
matcher.addURI(AUTHORITY, "person/update", PERSON_UPDATE_CODE);
// 对person
// 表进行查询所有操作,如果Uri=content://com.itheima.person/person,则返回PERSON_QUERYALL_CODE
matcher.addURI(AUTHORITY, "person", PERSON_QUERYALL_CODE);
// 对person 表进行查询单个操作,如果Uri=
content: // com.itheima.person/person/#,(#:代表数字)则返回PERSON_QUERYONE_CODE
matcher.addURI(AUTHORITY, "person/#", PERSON_QUERYONE_CODE);
}
@Override
public boolean onCreate() {
// 内容提供者中,获取contenxt,是通过getContext,与测试类一样,不能再成员变量,构造函数中调用,但是可以再onCreate
// 方法中获取。
openHelper = new PersonOpenHelper(getContext());
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// 用匹配器去匹配uri,如果匹配成功则返回匹配器中对应的状态码
int matchCode = matcher.match(uri);
SQLiteDatabase db = openHelper.getReadableDatabase();
switch (matchCode) {
case PERSON_QUERYALL_CODE:
return db.query(TABLE_PERSON_NAME, projection, selection,
selectionArgs, null, null, sortOrder);
case PERSON_QUERYONE_CODE:
// 使用ContentUris 工具类解析出uri 中的id
long parseId = ContentUris.parseId(uri);
return db.query(TABLE_PERSON_NAME, projection, "id=?",
new String[] { parseId + "" }, null, null, sortOrder);
default:
throw new IllegalArgumentException("Uri 匹配失败:" + uri);
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = openHelper.getWritableDatabase();
// 新插入对象的id
long id = db.insert(TABLE_PERSON_NAME, null, values);
db.close();
// 使用ContentUris 工具类将id 追加到uri 中,返回给客户
return ContentUris.withAppendedId(uri, id);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = openHelper.getWritableDatabase();
// 返回删除的个数
int count = db.delete(TABLE_PERSON_NAME, selection, selectionArgs);
// 关闭数据库
db.close();
return count;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
SQLiteDatabase db = openHelper.getWritableDatabase();
// 返回更新的个数
int count = db.update(TABLE_PERSON_NAME, values, selection,
selectionArgs);
// 更新数据库
db.close();
return count;
}
@Override
public String getType(Uri uri) {
return null;
}
}
访问ContentProvider
- 外部程序只需知道内容提供者的Uri 路径信息,通过内容解析器ContentResolver 即可调用内容提供者。
- 如果内容提供者所在应用程序,没有启动,其他应用程序访问调用它时,就会启动它。
启动时,会创建这个内容提供者。
测试query(单个)方法
public void queryOne() {
ContentResolver resolver = getContext().getContentResolver();
Uri uri = Uri.parse("content://com.itheima.person/person/2");
Cursor cursor = resolver.query(uri, new String[] { "name", "age",
"phone", "address" }, null, null, null);
while (cursor.moveToNext()) {
String name = cursor.getString(0);
int age = cursor.getInt(1);
String phone = cursor.getString(2);
String address = cursor.getString(3);
System.out.println(name + "/" + age + "/" + phone + "/" + address);
}
cursor.close();
}
测试query(多个)方法
public void queryAll() {
ContentResolver resolver = getContext().getContentResolver();
Uri uri = Uri.parse("content://com.itheima.person/person");
Cursor cursor = resolver.query(uri, new String[] { "name", "age",
"phone", "address" }, null, null, null);
while (cursor.moveToNext()) {
String name = cursor.getString(0);
int age = cursor.getInt(1);
String phone = cursor.getString(2);
String address = cursor.getString(3);
System.out.println(name + "/" + age + "/" + phone + "/" + address);
}
cursor.close();
}
URI
schema | 主机名authority | path | ID |
---|---|---|---|
content:// | com.itheima.provider/ | person/ | 10 |
schema:用来说明一个ContentProvider 控制这些数据。”content://”
authority:它定义了是哪个ContentProvider 提供这些数据,provider>节点中的authorites 属性
path:路径,URI 下的某一个Item。
ID:通常定义Uri 时使用”#”号占位符代替, 使用时替换成对应的数字
content://com.itheima.provider/person/#:#表示数据id(#代表任意数字)
content://com.itheima.provider/person/*:*来匹配任意文本。
示例 短信的备份与恢复
需要的权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
备份短信
public void backupSms(View view) {
// 如果当前正在备份,则返回。
if (backupFlag) {
Toast.makeText(this, "当前正在备份中。。。请稍后再操作", 0).show();
return;
}
// 开辟一个新的线程来处理业务,因为短信备份是耗时操作,如果不放在子线程中操作可能会导致ANR 异常
new Thread(new Runnable() {
@Override
public void run() {
// 将标记位设置为true
backupFlag = true;
// 子线程中创建一个Looper 对象
Looper.prepare();
// 获取ContentResolver 对象
ContentResolver resolver = getContentResolver();
// 编辑短信访问的uri
Uri uri = Uri.parse("content://sms");
// 执行查询语句
Cursor cursor = resolver.query(uri, new String[] { "address",
"date", "body", "type" }, null, null, null);
List<Sms> list = new ArrayList<Sms>();
// 获取短信的总共条数
int count = cursor.getCount();
// 设置进度条的最大值
pb.setMax(count);
int i = 0;
while (cursor.moveToNext()) {
Sms sms = new Sms();
String address = cursor.getString(0);
String date = cursor.getString(1);
String body = cursor.getString(2);
String type = cursor.getString(3);
sms.setAddress(address);
sms.setBody(body);
sms.setDate(date);
sms.setType(type);
list.add(sms);
// 更新进度条
pb.setProgress(++i);
// 这里是为了方便演示备份的效果加上线程等待
SystemClock.sleep(50);
}
try {
// 通过自定义的Sms2XmlUtil 工具类将短信保存到xml 中
Sms2XmlUtil.sms2Xml(list);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(MainActivity.this,
"备份短信失败。" + e.getLocalizedMessage(), 1).show();
}
// 关闭游标
cursor.close();
Toast.makeText(MainActivity.this, "短信备份成功!本次备份了" + i + "条短信。",
1).show();
// 循环消息
Looper.loop();
// 将标记设置为flase
backupFlag = false;
}
}).start();
}
恢复短信
代码缺陷:在插入数据库之前先判断该短信是否存在,如果存在则不插入。
public void reverseSms(View view) {
if (reverseFlag) {
Toast.makeText(this, "当前短信正在备份中,请稍后再操作。", 0).show();
return;
}
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
ContentResolver resolver = getContentResolver();
Uri url = Uri.parse("content://sms");
List<Sms> list = null;
try {
list = Sms2XmlUtil.xml2Sms();
pb.setMax(list.size());
pb.setProgress(0);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(MainActivity.this,
"短信恢复失败!" + e.getLocalizedMessage(), 1).show();
return;
}
int i = 0;
for (Sms s : list) {
ContentValues values = new ContentValues();
values.put(Sms2XmlUtil.SMS_TAG_ADDRESS, s.getAddress());
values.put(Sms2XmlUtil.SMS_TAG_BODY, s.getBody());
values.put(Sms2XmlUtil.SMS_TAG_DATE, s.getDate());
values.put(Sms2XmlUtil.SMS_TAG_TYPE, s.getType());
Uri uri = resolver.insert(url, values);
System.out.println("插入的id:" + ContentUris.parseId(uri));
pb.setProgress(++i);
SystemClock.sleep(50);
}
Toast.makeText(MainActivity.this, "短信恢复成功!本次恢复了" + i + "条短信。",
1).show();
reverseFlag = false;
Looper.loop();
}
}).start();
}
示例 操作系统联系人
查看数据库其中raw_contacts 表存放的是联系人条数信息,
data 表中存放的是raw_contacts 中的每一条id 对应的具体信息,
不同类型的信息由mimetype_id 来标识。
- raw_contacts 表:其中保存了联系人id 字段为:_id
- data 表:和raw_contacts 是多对一的关系,保存了联系人的各项数据
- mimetypes 表:数据类型
查询联系人信息思路:先查询raw_contacts 得到每个联系人的id,在使用id 从data表中查询对应数据,根据mimetype 分类数据
tips:
- 系统内部提供了根据电话号码获取data 表数据的功能, 路径为:data/phones/filter/*
- 用电话号码替换“*”部分就可以查到所需数据,获取“display_name”可以获取到
联系人显示名
需要的权限
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
获取联系人代码
public class MainActivity extends Activity {
private ListView lv;
private List<Contact> list;
private MyAdapter adapter;
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
Toast.makeText(MainActivity.this, "数据获取成功。", 1).show();
// 更新数据
adapter.notifyDataSetChanged();
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv = (ListView) findViewById(R.id.lv);
list = new ArrayList<Contact>();
adapter = new MyAdapter();
lv.setAdapter(adapter);
}
/**
* 查询所有的系统联系人,这里的业务放在子线程中操作
*/
public void queryContacts(View view) {
new Thread(new Runnable() {
@Override
public void run() {
ContentResolver contentResolver = getContentResolver();
Uri uri = Uri
.parse("content://com.android.contacts/raw_contacts");
Cursor cursor = contentResolver.query(uri,
new String[] { "contact_id" }, null, null, null);
list.clear();
int i = 0;
while (cursor.moveToNext()) {
i++;
// 先从raw_contacts 表中获取contact_id
String contact_id = cursor.getString(0);
if (TextUtils.isEmpty(contact_id)) {
continue;
}
// 根据contact_id 从data 表中查询具体的数据
Cursor cursor2 = contentResolver.query(
Uri.parse("content://com.android.contacts/data"),
new String[] { "data1", "mimetype" },
"contact_id=?", new String[] { contact_id }, null);
Contact contact = new Contact();
while (cursor2.moveToNext()) {
String data = cursor2.getString(0);
String mimetype = cursor2.getString(1);
if ("vnd.android.cursor.item/name".equals(mimetype)) {
contact.setName(data);
} else if ("vnd.android.cursor.item/phone_v2"
.equals(mimetype)) {
contact.setPhone(data);
} else if ("vnd.android.cursor.item/email_v2"
.equals(mimetype)) {
contact.setEmail(data);
} else {
contact.setOther(data);
}
}
list.add(contact);
cursor2.close();
}
cursor.close();
// 发送一个空消息,更新ListView
handler.sendEmptyMessage(RESULT_OK);
}
}).start();
}
class MyAdapter extends BaseAdapter {
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return position;
}
/**
* 这里面通过ViewHolder 类将其他子属性值绑定在View 上面,这里对 ListView 作了进一步的优化处理
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
ViewHolder holder;
if (convertView != null) {
view = convertView;
holder = (ViewHolder) view.getTag();
} else {
view = View
.inflate(MainActivity.this, R.layout.list_item, null);
holder = new ViewHolder();
holder.tv_name = (TextView) view.findViewById(R.id.tv_name);
holder.tv_phone = (TextView) view.findViewById(R.id.tv_phone);
holder.tv_email = (TextView) view.findViewById(R.id.tv_email);
view.setTag(holder);
}
Contact contact = list.get(position);
holder.tv_name.setText(contact.getName());
holder.tv_email.setText(contact.getEmail());
holder.tv_phone.setText(contact.getPhone());
return view;
}
}
class ViewHolder {
TextView tv_name;
TextView tv_email;
TextView tv_phone;
}
}
插入联系人
/**
* 往系统联系人表中插入一条数据,这里为了方便演示,我们直接插入一 条固定的数据
*/
public void insertContacts(View view) {
// 创建一个自定义的Contact 类,将要网系统联系人表中插入的字段封装起来
Contact contact = new Contact();
contact.setEmail("[email protected]");
contact.setName("王二麻子" + new Random().nextInt(1000));
contact.setPhone("9999999" + new Random().nextInt(100));
contact.setOther("北京市中关村软件园");
// 获取ContentResolver 对象
ContentResolver resolver = getContentResolver();
// 操作raw_contacts 表的uri
Uri raw_uri = Uri.parse("content://com.android.contacts/raw_contacts");
// 操作data 表的uri
Uri data_uri = Uri.parse("content://com.android.contacts/data");
// 在插入数据之前先查询出当前最大的id
Cursor cursor = resolver.query(raw_uri, new String[] { "contact_id" },
null, null, "contact_id desc limit 1");
int id = 1;
if (cursor != null) {
boolean moveToFirst = cursor.moveToFirst();
if (moveToFirst) {
id = cursor.getInt(0);
}
}
cursor.close();
// 要插入数据的contact_id 值
int newId = id + 1;
// 给raw_contact 表中添加一天记录
ContentValues values = new ContentValues();
values.put("contact_id", newId);
resolver.insert(raw_uri, values);
// 在data 表中添加数据
// 添加name
values = new ContentValues();
values.put("raw_contact_id", newId);
values.put("mimetype", "vnd.android.cursor.item/name");
values.put("data1", contact.getName());
resolver.insert(data_uri, values);
// 添加phone
values = new ContentValues();
values.put("raw_contact_id", newId);
values.put("mimetype", "vnd.android.cursor.item/phone_v2");
values.put("data1", contact.getPhone());
resolver.insert(data_uri, values);
// 添加地址
values = new ContentValues();
values.put("raw_contact_id", newId);
values.put("mimetype", "vnd.android.cursor.item/postal-address_v2");
values.put("data1", contact.getOther());
resolver.insert(data_uri, values);
// 添加email
values = new ContentValues();
values.put("raw_contact_id", newId);
values.put("mimetype", "vnd.android.cursor.item/email_v2");
values.put("data1", contact.getEmail());
resolver.insert(data_uri, values);
Toast.makeText(this, "成功插入联系人" + contact, 0).show();
}
ContentObserver 内容观察者
- 类似于数据库技术中的触发器(Trigger)
- 当ContentObserver所观察的Uri 发生变化时,便会触发它
- ContentObserver 分为:“表“ContentObserver、“行”ContentObserver,与的Uri MIME Type相关
- Uri Type可以分为:返回多条数据的Uri、返回单条数据的Uri。
示例 使用ContentObserver短信监听
发出的短信有这样几个过程:草稿箱->发件箱->已发送。所以只需查询发件箱中的信息即可,处于正在发送状态的短信放在发送箱中。
权限
<uses-permission android:name="android.permission.READ_SMS"/>
创建一个内容观察者
public class SmsContentObserver extends ContentObserver {
// 声明一个Content 对象,在构造函数中对其进行实例化
private Context context;
private Handler handler;
// 声明构造函数
public SmsContentObserver(Context context, Handler handler) {
super(handler);
this.context = context;
this.handler = handler;
}
// 覆写父类onChange 方法
@Override
public void onChange(boolean selfChange) {
// 通过查看发件箱中的短信即可观察到新短信的发送
Uri uri = Uri.parse("content://sms/outbox");
// 获取Context 对象的ContentResolver 对象
ContentResolver resolver = context.getContentResolver();
String[] projection = { "address", "body", "date" };
// 获取发件箱中的短信
Cursor cursor = resolver.query(uri, projection, null, null, null);
if (cursor != null && cursor.moveToNext()) {
String address = cursor.getString(0);
String body = cursor.getString(1);
Long date = cursor.getLong(2);
// 调用DateFormat 的方法将long 类型的时间转化为系统相关时间
String dateStr = DateFormat.getTimeFormat(context).format(date);
// 打印观察到正在发送的短信
System.out.println("address=" + address + " body=" + body + "date="
+ dateStr);
}
cursor.close();
}
}
在MainActivity 类中注册SmsContentObserver
public class MainActivity extends Activity {
public class MainActivity extends Activity {
private SmsContentObserver observer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 实例化一个自定义的SMSContentObserver 对象
observer = new SmsContentObserver(this, null);
// 操作系统短信的uri
Uri uri = Uri.parse("content://sms");
/**
* 获取ContentResolver,然后注册ContentObserver
*/
getContentResolver().registerContentObserver(uri, true, observer);
}
}
}
自定义 ContentObserver
通过ContentObserver 实现了对系统发送短信的监听
这是因为系统短信数据数据库中插入新的短信的时候通过ContentResolver 调用了notifyChange方法,
正是该方法对外发出了消息,这样我们的ContentObserver 才监听到了短信的发送
示例 发送消息
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = openHelper.getWritableDatabase();
// 新插入对象的id
long id = db.insert(TABLE_PERSON_NAME, null, values);
db.close();
// 声明一个uri,通过这个uri ContentObserver 可以进行监听
Uri notifyUri = Uri.parse("com.itheima.person");
/*
* 方法中第一个参数指定一个被监听的uri 第二个参数是指定ContentObserver,如果不为null 则代表只有指定
* 的ContentObserver 可以接收到消息 为null 则所有的ContentObserver 都有机会接收到该消息
*/
getContext().getContentResolver().notifyChange(notifyUri, null);
// 使用ContentUris 工具类将id 追加到uri 中,返回给客户
return ContentUris.withAppendedId(uri, id);
}
监听
contentResolver.registerContentObserver(uri,notifyForDescendents,contentObserver);
参数:
uri、监听哪个uri 的操作
notifyForDescendents 是否观察uri 的子集,如果是false,不观察他的子孙,就只能完全匹配
contentObserver,内容观察者