Android开发之内容解析者ContentResolver

在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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值