在Android开发中有所谓四大组件之说即Activity界面 BroadcastRsolver广播 Service服务 ContentProvider内容提供者
/****
* ContentProvider内容提供者
* ContentObserver内容观察者
* ContentResolver就是来取ContentProvider提供的数据的。
* ContentProvider使你数据库中数据能够被其他程序访问,但能访问不能任意方式都能访问,
* 只能通过规定的方式,这种方式就是通过ContentResolver来实现
*内容观察者,观察内容提供者数据的变化。如果内容提供者数据变化了,那么发送信息给观察者。
*原理:在resolver身上注册一个观察者observer,当数据改变时,调用观察者的onChange方法
*在provider的数据会发生改变的方法中调用resolver的notifyChange方法。说白了ContentObserver就是一个能够动态监听数据的监听器
*/
以下是为什么要使用ContentObserver而不是自己写监听去监测数据变化
我们知道,在db 做insert、delete等操作的时候,db会改变,这个时候UI 可能是需要更新的,那怎么才能知道db 是有了变化呢?不能做个监听一直查询db是否变化吧?这样就太废精力了,Android 中提供了ContentObserver来作为db 数据变化后的callback。
部分内容取材于私房菜的博客,下边是私房菜的博客地址
http://blog.csdn.net/shift_wwx/article/details/48782367
这哥们讲的很专业,我也没必要在这里装逼了,直接看他的,,哈哈哈哈
在android 中的 ContentObserver (一) 中,提到如果一个db 发生了变化,用户需要最快的知晓。可以做个监听器一直查询db的变化状态,这里需要更正一下,这个不是个好方法,也最好不要想,对于数据表小一点的话,还是可以考虑,可是对于大型的数据表或者字段很多的情况下,这个监听器是没办法做的。
所以,剩下有两种选择,一是选择ContentObserver,另一个选择是广播。
对于两者的区别,大概可以列出下面几点:
一、前者是通过URI来把通知的发送者和接收者关联在一起的,而后者是通过Intent来关联的
二、前者的通知注册中心是由ContentService服务来扮演的,而后者是由ActivityManagerService服务来扮演的
三、前者负责接收数据更新通知的类必须要继承ContentObserver类,而后者要继承BroadcastReceiver类。
从上面这些区别看,两者完全可以做成一个,目前也就是实现的地方不一样而已,其实之所以会有这些区别,主要是因为第四点。
四、Content Proivder组件的数据共享功能本身就是建立在URI的基础之上的,因此专门针对URI来设计另外一套通知机制会更实用和方便,而Android系统的广播机制是一种更加通用的事件通知机制,它的适用范围会更广泛一些。
当然,如果不愿意用 ContentObserver,用广播也是可以,可以将uri 以 param 的形式传递上来。(通过intent传递参数)
本片博文主要讲述如何通过ContentResolver拿到内容提供者的数据并予以展示,访问的都是系统的数据库,拿到通话记录,短息,,联系人
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.CallLog;
import android.support.v7.app.AppCompatActivity;
import android.widget.CursorAdapter;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
public class MainActivity extends AppCompatActivity {
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.listView);
// 获得ContentResolver对象
ContentResolver resolver = getContentResolver();
// 构造访问通话记录的Uri
// 方式一:直接通过字符串创建
//Uri callLogUri = Uri.parse("content://call_log/calls");
// 方式二:使用对应合约类中的常量
Uri callLogUri = CallLog.Calls.CONTENT_URI;
// 查询获得结果 参数为查询条件
// public final Cursor query(Uri uri, String[] projection,
// String selection, String[] selectionArgs, String sortOrder) {
// return query(uri, projection, selection, selectionArgs, sortOrder, null);
// }
Cursor cursor = resolver.query(
callLogUri, // Uri
new String[] { "_id", "number", "date", "type" }, // 返回结果所要包含的列明数组
null, null, // 共同指定了查询条件,即WHERE子句
null); // 排序条件,即ORDER BY子句
SimpleCursorAdapter adapter = new SimpleCursorAdapter(
this,
R.layout.item_listview_main,
cursor,
new String[] { "number", "date", "type" },
new int[] {R.id.text_item_number, R.id.text_item_date, R.id.text_item_type},
CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
listView.setAdapter(adapter);
}
}
item布局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" >
<TextView android:id="@+id/text_item_number" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:textSize="26sp" android:textColor="#00f" android:text="TextView" />
<TextView android:id="@+id/text_item_type" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:text="TextView" />
<TextView android:id="@+id/text_item_date" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/text_item_number" android:textColor="#ddd" android:text="TextView" />
</RelativeLayout>
//别忘了权限
<uses-permission android:name="android.permission.READ_CALL_LOG" />
你会发现好多数据显示的格式都看不懂 ,那是系统数据库保存的格式,我们可以自定义一个BaseAdapter对数据进行一些转换
package com.longlian.contentobserverdemo;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class MainActivity extends AppCompatActivity {
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.listView_main_smslist);
// 获得ContentResolver
ContentResolver resolver = getContentResolver();
// 构建访问消息的Uri
Uri smsUri = Uri.parse("content://sms");
// 查询获得结果
Cursor cursor = resolver.query(
smsUri,
new String[] { "_id", "address", "date", "body", "type" },
null, null,
null);
SmsAdapter adapter = new SmsAdapter(this, cursor);
listView.setAdapter(adapter);
}
class SmsAdapter extends BaseAdapter {
private Cursor cursor;
private LayoutInflater inflater;
public SmsAdapter(Context context, Cursor cursor) {
this.cursor = cursor;
inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return cursor.getCount();
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return null;
}
@Override
public long getItemId(int position) {
cursor.moveToPosition(position);
return cursor.getLong(cursor.getColumnIndex("_id"));
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder h = null;
if (convertView == null) {
convertView = inflater.inflate(R.layout.item_listview_main, null);
h = new ViewHolder();
h.addrText = (TextView) convertView.findViewById(R.id.text_item_address);
h.bodyText = (TextView) convertView.findViewById(R.id.text_item_body);
h.dateText = (TextView) convertView.findViewById(R.id.text_item_date);
h.typeText = (TextView) convertView.findViewById(R.id.text_item_type);
convertView.setTag(h);
} else {
h = (ViewHolder) convertView.getTag();
}
// 绑定数据
cursor.moveToPosition(position);
h.addrText.setText(cursor.getString(cursor.getColumnIndex("address")));
h.bodyText.setText(cursor.getString(cursor.getColumnIndex("body")));
h.dateText.setText(getDateString(cursor.getLong(cursor.getColumnIndex("date"))));
h.typeText.setText(getSmsType(cursor.getInt(cursor.getColumnIndex("type"))));
return convertView;
}
/** * 将13位时间戳代表的日期转换为常见的可读格式 * @param time * @return */
private String getDateString(long time) {
Date date = new Date(time);
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA);
return format.format(date);
}
/** * 将整数代表的消息类型转换为可读字符串 * @param type * @return */
private String getSmsType(int type) {
String str;
switch(type) {
case 1:
str = "收到";
break;
case 2:
str = "发出";
break;
case 3:
str = "草稿";
break;
default:
str = "其他";
break;
}
return str;
}
class ViewHolder {
TextView addrText;
TextView bodyText;
TextView dateText;
TextView typeText;
}
}
}
item布局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" >
<TextView android:id="@+id/text_item_address" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:textSize="26sp" android:textColor="#00f" android:text="TextView" />
<TextView android:id="@+id/text_item_type" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:text="TextView" />
<TextView android:id="@+id/text_item_body" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/text_item_address" android:layout_marginTop="5dp" android:text="TextView" />
<TextView android:id="@+id/text_item_date" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/text_item_body" android:layout_alignBottom="@+id/text_item_body" android:layout_alignParentRight="true" android:textColor="#f00" android:text="TextView" />
</RelativeLayout>
//还有读取手机短信的权限
<uses-permission android:name="android.permission.READ_SMS" />
//上边说ContentObserver可以监听数据的delete insert操作,现在让我们以一个例子来说一下如何办到
import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.ContextMenu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MainActivity extends AppCompatActivity {
private String uri_raw_contacts = "content://com.android.contacts/raw_contacts";
private String uri_data_phones = "content://com.android.contacts/data/phones";
private String uri_data_emails ="content://com.android.contacts/data/emails";
private ListView listView;
private ContentResolver resolver;
private List<Map<String, Object>> list;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.listView);
resolver = getContentResolver();
list = queryContactsList();
SimpleAdapter adapter = new SimpleAdapter(
this,
list,
R.layout.item_listview_main,
new String[] { "_id", "display_name", "emails", "phones"},
new int[] {R.id.text_item_id, R.id.text_item_username, R.id.text_item_emails, R.id.text_item_phones});
listView.setAdapter(adapter);
// 为ListView注册上下文菜单
registerForContextMenu(listView);
}
/** * 查询联系人信息 * @return */
private List<Map<String, Object>> queryContactsList() {
List<Map<String, Object>> list = new ArrayList<>();
Cursor cursor = null;
try{
cursor = resolver.query(Uri.parse(uri_raw_contacts),
new String[] { "_id", "display_name" }, null, null, null);
while(cursor.moveToNext()) {
Map<String, Object> map = new HashMap<>();
int id = cursor.getInt(cursor.getColumnIndex("_id"));
String name = cursor.getString(cursor.getColumnIndex("display_name"));
map.put("_id", id);
map.put("display_name", name);
Cursor phoneCursor = null;
try {
// 根据_id查询联系人电话
phoneCursor = resolver.query(Uri.parse(uri_data_phones),
new String[] { "raw_contact_id", "data1" }, "raw_contact_id=?", new String[] { id+"" }, null);
StringBuilder builder1 = new StringBuilder();
while(phoneCursor.moveToNext()) {
builder1.append(phoneCursor.getString(phoneCursor.getColumnIndex("data1")));
builder1.append("|");
}
map.put("phones", builder1.toString());
Cursor emailCursor = null;
try {
// 根据_id查询联系人Emails
emailCursor = resolver.query(Uri.parse(uri_data_emails),
new String[] { "raw_contact_id", "data1" }, "raw_contact_id=?", new String[] { id+"" }, null);
StringBuilder builder2 = new StringBuilder();
while(emailCursor.moveToNext()) {
builder2.append(emailCursor.getString(emailCursor.getColumnIndex("data1")));
builder2.append("|");
}
map.put("emails", builder2.toString());
}catch (Exception e){
e.printStackTrace();
}finally {
if(emailCursor != null){
emailCursor.close();
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(phoneCursor != null){
phoneCursor.close();
}
}
// 添加到list
list.add(map);
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(cursor != null){
cursor.close();
}
}
return list;
}
@Override
protected void onDestroy() {
super.onDestroy();
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenu.ContextMenuInfo menuInfo) {
getMenuInflater().inflate(R.menu.contacts, menu);
// 填充菜单
AdapterView.AdapterContextMenuInfo aMenuInfo = (AdapterView.AdapterContextMenuInfo) menuInfo;
// 显示菜单Header图表和标题
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) listView.getAdapter().getItem(aMenuInfo.position);
String name = (String) map.get("display_name");
menu.setHeaderIcon(R.mipmap.ic_launcher);
menu.setHeaderTitle(name);
super.onCreateContextMenu(menu, v, menuInfo);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo aMenuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
Map<String, Object> map = (Map<String, Object>) listView.getAdapter().getItem(aMenuInfo.position);
int id = (Integer) map.get("_id");
String name = (String) map.get("display_name");
switch(item.getItemId()) {
case R.id.delete_item:
// 删除数据库中数据
resolver.delete(Uri.parse(uri_raw_contacts), "_id=?", new String[] { id+"" });
// 删除内存中数据以更新界面
list.remove(aMenuInfo.position);
((SimpleAdapter) listView.getAdapter()).notifyDataSetChanged();
break;
case R.id.update_item:
// 携带必要数据跳转到UpdateActivity
Intent intent = new Intent(this, UpdateActivity.class);
intent.putExtra("id", id);
intent.putExtra("name", name);
intent.putExtra("position", aMenuInfo.position);
startActivityForResult(intent, 0);
break;
}
return super.onContextItemSelected(item);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
// 获得从UpdateActivity传回的数据
int position = data.getIntExtra("position", 0);
String newName = data.getStringExtra("newName");
// 删除内存中数据以更新界面
list.get(position).put("display_name", newName);
((SimpleAdapter) listView.getAdapter()).notifyDataSetChanged();
}
super.onActivityResult(requestCode, resultCode, data);
}
}
//更新数据的Activity
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
public class UpdateActivity extends AppCompatActivity {
private String uri_raw_contacts = "content://com.android.contacts/raw_contacts";
private EditText displayname;
private int id;
private int position;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_update);
// 获取传入的值
Intent intent = getIntent();
String name = intent.getStringExtra("name");
id = intent.getIntExtra("id", 0);
position = intent.getIntExtra("position", 0);
displayname = (EditText) findViewById(R.id.editText_update_displayname);
displayname.setText(name);
}
/** * 响应按钮点击 * @param view */
public void clickButton(View view) {
// 修改数据库中的值
ContentResolver resolver = getContentResolver();
ContentValues values = new ContentValues();
String newName = displayname.getText().toString();
values.put("display_name", newName);
int result = resolver.update(Uri.parse(uri_raw_contacts), values, "_id=" + id, null);
if (result == 1) {
Toast.makeText(this, "修改成功", Toast.LENGTH_SHORT).show();
// 向MainActivity中回传数据,以使ListView更新界面
Intent intent = new Intent();
intent.putExtra("position", position);
intent.putExtra("newName", newName);
setResult(RESULT_OK, intent);
finish();
} else {
Toast.makeText(this, "修改失败", Toast.LENGTH_SHORT).show();
}
}
}
//贴上布局代码
listView的item选项
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:id="@+id/text_item_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:textSize="24sp"
android:textColor="#00f"
android:text="TextView" />
<TextView
android:id="@+id/text_item_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:text="TextView" />
<TextView
android:id="@+id/text_item_phones"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/text_item_username"
android:text="TextView" />
<TextView
android:id="@+id/text_item_emails"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/text_item_phones"
android:textSize="20sp"
android:textColor="#f00"
android:text="TextView" />
</RelativeLayout>
//UpdateActivity的布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" >
<EditText android:id="@+id/editText_update_displayname" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" >
<requestFocus />
</EditText>
<Button android:id="@+id/button_update_submit" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="clickButton" android:layout_gravity="center" android:text="修改联系人姓名" />
</LinearLayout>
在这些方法中我们反复用到一个东西Cursor游标,
Cursor是Android查询数据后得到的一个管理数据集合的类,正常情况下,如果查询得到的数据量较小时不会有内存问题,而且虚拟机能够保证Cusor最终会被释放掉。然而如果Cursor的数据量特表大,特别是如果里面有Blob信息时,应该保证Cursor占用的内存被及时的释放掉,而不是等待GC来处理。并且Android明显是倾向于编程者手动的将Cursor close掉,因为在源代码中我们发现,如果等到垃圾回收器来回收时,也就是如果不手动关闭,系统会报错,会给用户以错误提示。
所以我们使用Cursor的方式一般如下:
Cursor cursor = null;
try{
cursor = mContext.getContentResolver().query(uri,null,null,null,null);
if(cursor != null){
cursor.moveToFirst();
//do something
}
}catch(Exception e){
e.printStatckTrace();
}finally{
if(cursor != null){
cursor.close();
}
}
//有一种情况下,我们不能直接将Cursor关闭掉,这就是在CursorAdapter中应用的情况,但是注意,CursorAdapter在Acivity结束时并没有自动的将Cursor关闭掉,因此,你需要在onDestroy函数中,手动关闭。
@Override
protected void onDestroy() {
if (mAdapter != null && mAdapter.getCurosr() != null){
mAdapter.getCursor().close();
}
super.onDestroy();
}
// CursorAdapter中的changeCursor函数,会将原来的Cursor释放掉,并替换为新的Cursor,所以你不用担心原来的Cursor没有被关闭。 你可能会想到使用Activity的managedQuery来生成Cursor,这样Cursor就会与Acitivity的生命周期一致了,多么完美的解决方法!然而事实上managedQuery也有很大的局限性。managedQuery生成的Cursor必须确保不会被替换,因为可能很多程序事实上查询条件都是不确定的,因此我们经常会用新查询的Cursor来替换掉原先的Cursor。因此这种方法适用范围也是很小。
我在查询联系人时用了两层游标,如果我只关闭了外层游标,没有关闭里层的,导致内存资源没有被释放。多次运行后,系统资源被耗尽。
会抛出如下异常信息
主要抛出的异常信息为:
ERROR/JavaBinder(3438): * Uncaught remote exception! (Exceptions are not yet supported across processes.)
ERROR/JavaBinder(3438): java.lang.RuntimeException: No memory in memObj