开始之前,我先记录一下针对小米手机系统权限严格的处理。

情景复现一下先:当程序对系统通讯录数据库做删除操作时,小米手机会弹出警告中止此次操作,具体提示内容是“ ***正在删除小米账户上的联系人,为了保护信息安全,本次删除已被禁止。您可以使用系统通讯录删除联系人。”,正下图这种:

先不说造成这样提示具体代码操作是什么,这里先给出解决方案,相信能搜索到本小破站这篇文章的你一定像我一样饥渴地想知道答案:

ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI)
                                            .withSelection(ContactsContract.RawContacts.CONTACT_ID + "=" + getContactID("通讯录联系人姓名"), null)
                                            .build());

                                    try {
                                        mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
                                        Log.d(TAG, "delete contact success");
                                    } catch (Exception e) {
                                        Log.d(TAG, "delete contact failed");
                                        Log.e(TAG, e.getMessage());
                                    }

// ... ...


    private String getContactID(String name) {
        String id = "0";
        ContentResolver resolver = mContext.getContentResolver();
        Cursor cursor = resolver.query(
                android.provider.ContactsContract.Contacts.CONTENT_URI,
                new String[]{android.provider.ContactsContract.Contacts._ID},
                android.provider.ContactsContract.Contacts.DISPLAY_NAME +
                        "='" + name + "'", null, null);
        if (cursor.moveToNext()) {
            id = cursor.getString(cursor.getColumnIndex(
                    android.provider.ContactsContract.Contacts._ID));
        }
        return id;
    }

好了,现在有耐心的可以听我哔哔了: )


首先我拿到这个问题,当然像你们一样去查,查之前大概捋了一下逻辑,小米系统不可以直接整个删除联系人,需要先判断本地有没有这个联系人,如果没有那没事,如果有那就清除此联系人下的电话、邮件等信息。

于是百度到了这一片谷歌官方文档:https://developer.android.com/reference/android/provider/ContactsContract.Data.html

让我用ContactsContract.api进行查询id后对结果的单独删除操作,这里也贴一下谷歌的示例代码:

//Just as with insert and update, deletion can be done either using the ContentResolver#delete method or using a ContentProviderOperation:  

ArrayList<ContentProviderOperation> ops =
          new ArrayList<ContentProviderOperation>();

 ops.add(ContentProviderOperation.newDelete(Data.CONTENT_URI)
          .withSelection(Data._ID + "=?", new String[]{String.valueOf(dataId)})
          .build());
 getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);

可是实际代码运行中并不能正常工作,详细差异自行比较最上方的代码与谷歌给出的实例差别。


这里讲一下传统常规Android对通讯录 增、删、改、查的方法以及一些概念解释。

重要数据

URI

对raw_contacts表添加、删除、更新操作:

URI = content://com.android.contacts/raw_contacts;

Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;

对data表添加、删除、更新操作:

URI = content://com.android.contacts/data;

根据email对data表查询

URI =content://com.android.contacts/data/emails/filter/*

根据电话号码对data表查询

URI =content://com.android.contacts/data/phone/filter/*

如果要根据ID查询电话,可以

URI = content://com.android.contacts/data;

然后where条件为:raw_contact_id=? and mimetype = ?

MIMETYPE

电话:vnd.android.cursor.item/phone_v2

姓名:vnd.android.cursor.item/name

邮件:vnd.android.cursor.item/email_v2

通信地址:vnd.android.cursor.item/postal-address_v2

组织:vnd.android.cursor.item/organization

照片:vnd.android.cursor.item/photo

Data中的常量

Data._ID: "_id"

Data.DISPLAY_NAME:“display_name”

Data.DATA1:“data1”

Data.DATA2:“data2”

Data.RAW_CONTACT_ID:“raw_contact_id”

Data.MIMETYPE:“mimetype”

通讯录常用的数据库表

使用有关接口前,首先了解一下通讯录数据库中常用的数据库表:

表名 表用途
contacts 联系人表,存储了实际的联系人姓名,头像,最后通话时间等信息。 会对实际的联系人数据进行一定去重。
raw_contacts 实际的联系人数据表,每一行是一个单独的联系人。 会存在多行对应同一个contacts表中条目的情况。
data 所有联系人信息数据。通过raw_contact_id外键与raw_contacts建立联系。

contacts与raw_contacts的区分

一个raw_contacts对应一个联系人,程序中或用户操作生成新的联系人,就是直接在这个表中插入新条目。

contacts是实际通讯录中显示的联系人——当raw_contacts中存在相同名称的联系人时,系统会将这几个联系人合并。

(例如通过通讯录添加两个名字相同的名片,这时系统会提示是否要对这两个名片进行合并。)

data表

1.data表每一行都是一项数据(姓名,电话,Email,网址,生日等)。并通过外键raw_contacts_idraw_contacts表关联起来。

2.由1所述,一个联系人根据情况会有多条data数据。数据存储在data1-15这15列中。

例如某一行存储电话号码,那么在表中data1列存储电话号码,data2列存储号码类型(单位/家庭/组织等)。

又例如某一行存储的联系人姓名,那么data1列存储显示在界面上的名称,data2存储名,data3存储姓。

3.依数据类型不同,data1-14的含义会不同;data15默认存储blob二进制形式的数据。

4.那么又如何区分不同行数据的真实类型呢?是通过data表中mimetype_id列的值(整形)来进行区分。根据这一列的取值,对data1-14进行不同的解析。mimetype_id中数值与类型的对应关系在mimetypes表中定义。例如:

_id mimetypes 含义
1 vnd.android.cursor.item/email_v2 电子邮件
2 vnd.android.cursor.item/im 即时通讯
3 vnd.android.cursor.item/nickname 昵称

在编写代码时,实际传入的是mimetypes中的字符串参数,而不是ID值。

以上数据库中所有表及字段的定义,都可在android.provider.ContactsContract中找到。

通讯录存储的数据文件在/data/data/com.android.providers.contacts/databases/目录下,需要手机获取Root权限。

增删改查

1.query

(1)查询所有的联系人

//读取通讯录的全部的联系人 
//需要先在raw_contact表中遍历id,并根据id到data表中获取数据 
    public void testReadAll(){
        //uri = content://com.android.contacts/contacts 
        Uri uri = Uri.parse("content://com.android.contacts/contacts"); //访问raw_contacts表 
        ContentResolver resolver = this.getContext().getContentResolver();
        //获得_id属性
        Cursor cursor = resolver.query(uri, new String[]{Data._ID}, null, null, null);
        while(cursor.moveToNext()){
            StringBuilder buf = new StringBuilder();
            //获得id并且在data中寻找数据  
            int id = cursor.getInt(0);
            buf.append("id="+id);
            uri = Uri.parse("content://com.android.contacts/contacts/"+id+"/data");
            //data1存储各个记录的总数据,mimetype存放记录的类型,如电话、email等
            Cursor cursor2 = resolver.query(uri, new String[]{Data.DATA1,Data.MIMETYPE}, null,null, null);
            while(cursor2.moveToNext()){
                String data = cursor2.getString(cursor2.getColumnIndex("data1"));
                if(cursor2.getString(cursor2.getColumnIndex("mimetype")).equals("vnd.android.cursor.item/name")){       //如果是名字 
                    buf.append(",name="+data);
                }
                else if(cursor2.getString(cursor2.getColumnIndex("mimetype")).equals("vnd.android.cursor.item/phone_v2")){  //如果是电话 
                    buf.append(",phone="+data);
                }
                else if(cursor2.getString(cursor2.getColumnIndex("mimetype")).equals("vnd.android.cursor.item/email_v2")){  //如果是email 
                    buf.append(",email="+data);
                }
                else if(cursor2.getString(cursor2.getColumnIndex("mimetype")).equals("vnd.android.cursor.item/postal-address_v2")){ //如果是地址 
                    buf.append(",address="+data);
                }
                else if(cursor2.getString(cursor2.getColumnIndex("mimetype")).equals("vnd.android.cursor.item/organization")){  //如果是组织 
                    buf.append(",organization="+data);
                }
            }
            String str = buf.toString();
            Log.i("Contacts", str);
        }
    }

(2)根据电话号码查询姓名

//根据电话号码查询姓名(在一个电话打过来时,如果此电话在通讯录中,则显示姓名) 
    public void testReadNameByPhone(){
        String phone = "12345678";
        //uri=  content://com.android.contacts/data/phones/filter/# 
        Uri uri = Uri.parse("content://com.android.contacts/data/phones/filter/"+phone);
        ContentResolver resolver = this.getContext().getContentResolver();
        Cursor cursor = resolver.query(uri, new String[]{Data.DISPLAY_NAME}, null, null, null); //从raw_contact表中返回display_name 
        if(cursor.moveToFirst()){
            Log.i("Contacts", "name="+cursor.getString(0));
        }

    }

2.Insert

注意:对某个联系人插入姓名、电话等记录时必须要插入Data.MIMETYPE(或者是"mimetype")属性,而不是插入"mimetype_id"!

比如:values.put(Data.MIMETYPE,"vnd.android.cursor.item/phone_v2")

//一步一步添加数据 
    public void testAddContacts(){
        //插入raw_contacts表,并获取_id属性 
        Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
        ContentResolver resolver = this.getContext().getContentResolver();
        ContentValues values = new ContentValues();
        long contact_id = ContentUris.parseId(resolver.insert(uri, values));
        //插入data表 
        uri = Uri.parse("content://com.android.contacts/data");
        //add Name 
        values.put("raw_contact_id", contact_id);
        values.put(Data.MIMETYPE,"vnd.android.cursor.item/name");
        values.put("data2", "zdong");
        values.put("data1", "xzdong");
        resolver.insert(uri, values);
        values.clear();
        //add Phone 
        values.put("raw_contact_id", contact_id);
        values.put(Data.MIMETYPE,"vnd.android.cursor.item/phone_v2");
        values.put("data2", "2");   //手机 
        values.put("data1", "87654321");
        resolver.insert(uri, values);
        values.clear();
        //add email 
        values.put("raw_contact_id", contact_id);
        values.put(Data.MIMETYPE,"vnd.android.cursor.item/email_v2");
        values.put("data2", "2");   //单位 
        values.put("data1", "[email protected]");
        resolver.insert(uri, values);
    }

批量添加数据

核心代码:

(1)ContentProviderOperation operation = ContentProviderOperation.newInsert(uri).withValue("key","value").build();

(2)resolver.applyBatch("authorities",operations);//批量提交

public void testAddContactsInTransaction() throws Exception {
        Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
        ContentResolver resolver = this.getContext().getContentResolver();
        ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
        // 向raw_contact表添加一条记录 
        //此处.withValue("account_name", null)一定要加,不然会抛NullPointerException 
        ContentProviderOperation operation1 = ContentProviderOperation
                .newInsert(uri).withValue("account_name", null).build();
        operations.add(operation1);
        // 向data添加数据 
        uri = Uri.parse("content://com.android.contacts/data");
        //添加姓名 
        ContentProviderOperation operation2 = ContentProviderOperation
                .newInsert(uri).withValueBackReference("raw_contact_id", 0)
                //withValueBackReference的第二个参数表示引用operations[0]的操作的返回id作为此值 
                .withValue("mimetype", "vnd.android.cursor.item/name")
                .withValue("data2", "xzdong").build();
        operations.add(operation2);
        //添加手机数据 
        ContentProviderOperation operation3 = ContentProviderOperation
                .newInsert(uri).withValueBackReference("raw_contact_id", 0)
                .withValue("mimetype", "vnd.android.cursor.item/phone_v2")
                .withValue("data2", "2").withValue("data1", "0000000").build();
        operations.add(operation3);
        resolver.applyBatch("com.android.contacts", operations);

3.Delete

核心思想:
(1)先在raw_contacts表根据姓名(此处的姓名为name记录的data2的数据而不是data1的数据)查出id;

(2)在data表中只要raw_contact_id匹配的都删除;

public void testDelete()throws Exception{
        String name = "xzdong";
        //根据姓名求id 
        Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
        ContentResolver resolver = this.getContext().getContentResolver();
        Cursor cursor = resolver.query(uri, new String[]{Data._ID},"display_name=?", new String[]{name}, null);
        if(cursor.moveToFirst()){
            int id = cursor.getInt(0);
            //根据id删除data中的相应数据 
            resolver.delete(uri, "display_name=?", new String[]{name});
            uri = Uri.parse("content://com.android.contacts/data");
            resolver.delete(uri, "raw_contact_id=?", new String[]{id+""});
        }
    }

4.Update

核心思想:
(1)不需要更新raw_contacts,只需要更新data表;

(2)uri=content://com.android.contacts/data 表示对data表进行操作;

public void testUpdate()throws Exception{
        int id = 1;
        String phone = "999999";
        Uri uri = Uri.parse("content://com.android.contacts/data");//对data表的所有数据操作 
        ContentResolver resolver = this.getContext().getContentResolver();
        ContentValues values = new ContentValues();
        values.put("data1", phone);
        resolver.update(uri, values, "mimetype=? and raw_contact_id=?", new String[]{"vnd.android.cursor.item/phone_v2",id+""})
    }

另外附一个简明的删除联系人方法

/**
     * 删除联系人
     * */
    public void deleteContact(Contact contact) {
        Log.w(TAG, "**delete start**");
        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();

        String id = getContactID(contact.getName());
        //delete contact
        ops.add(ContentProviderOperation.newDelete(ContactsContract.RawContacts.CONTENT_URI)
                .withSelection(ContactsContract.RawContacts.CONTACT_ID+"="+id, null)
                .build());
        //delete contact information such as phone number,email
        ops.add(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI)
                .withSelection(COLUMN_CONTACT_ID + "=" + id, null)
                .build());
        Log.d(TAG, "delete contact: " + contact.getName());

        try {
            contentResolver.applyBatch(ContactsContract.AUTHORITY, ops);
            Log.d(TAG, "delete contact success");
        } catch (Exception e) {
            Log.d(TAG, "delete contact failed");
            Log.e(TAG, e.getMessage());
        }
        Log.w(TAG, "**delete end**");
    }
最后修改:2020 年 08 月 28 日 04 : 02 PM
如果觉得我的文章对你有用,请随意赞赏