安卓基础第九天(四大组件之ContentProvider,操作系统短信与联系人)

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,内容观察者

猜你喜欢

转载自blog.csdn.net/opopopwqwqwq/article/details/79365478
今日推荐