博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android通讯录数据库操作
阅读量:7011 次
发布时间:2019-06-28

本文共 16255 字,大约阅读时间需要 54 分钟。

1. ContactsProvider2.java (实现了ContentProvider)

2. ContactsContract.java 所有的联系人Uri,与联系人相关数据库字段均在此定义。这里面包含以下要讲的contacts表,raw_contacts表,data表,phone_lookup表的字段及Uri描述。

 

Table contacts

_id

starred

lookup

photo_id

 

 

 

 

1

0

 省略

<null>

 

 

 

 

2

0

 省略

<null>

 

 

 

 

3

1

 省略

2366

 

 

 

 

 

starred:标识该账户是否为VIP账户

lookup: 没具体看,也是一个非常重要的字段

photo_id: 引用data表的 _id,

 

Table raw_contacts

_id

contact_id

account_name

account_type

sync1

 

 

 

1

1

Yulei0619@gmail.com

com.google

http://www.google.com/m8/feeds/contacts/yulei0619@gmail.com/base2_property-android/1ac2a39e89c441cc

 

 

 

2

2

Yulei0619@gmail.com

com.google

 省略

 

 

 

3

3

pcsc

com.htc.android.pcsc

 省略

 

 

 

 

contact_id:引用contacts表的_id字段

account_name:指明该联系人是从哪个账户上同步下载下来的。

account_type:邮件帐户的类型

sync1:说明该联系人信息是与哪个网址进行交互(同步联系人信息)

 

Table data.

_id

raw_contact_id

mimetype_id

data1

data2

data15

 

1

1

6

南京用友

南京用友

<null>

 

2

1

5

1-318-296-4252

1

<null>

 

3

2

6

司道鹏

司道鹏

<null>

 

4

2

5

1-516-920-8116

2

<null>

 

5

3

6

子江

子江

<null>

 

6

3

5

1795113734017562

1

<null>

 

7

3

5

+8613734017562

2

<null>

 

2366

3

4

<null>

<null>

‰PNG

 

 

raw_contact_id: 引用  raw_contacts表的_id字段mimetype_id:vnd.android.cursor.item/photo -> 4 该行 data15代表照片vnd.android.cursor.item/phone_v2 -> 5 该行data1代表电话号码vnd.android.cursor.item/name -> 6说明该行data1代表联系人名称

 

data15: 存储照片 (Type: Blob)

 

 

Table phone_lookup

_id

data_id

raw_contact_id

normalized_number

1

2

1

25246928131

2

4

2

61180296151

3

6

3

2657104373115971

4

7

3

2657104373168+

 

data_id: 引用 data 表的_id字段

raw_contact_id: 引用raw_contacts表的 _id字段

normalized_number:联系人电话号码的倒序排列

 

如何将smsmms.db数据库与contacts2.db数据结合起来成为了非常关键的部分

依据联系人的number,查询该联系人的contact_id

 

public static String getContactId(Context context, String number) {        Cursor c = null;        try {               c = context.getContentResolver().query(Phone.CONTENT_URI,              new String[] { Phone.CONTACT_ID, Phone.NUMBER }, null,, null);               if (c != null && c.moveToFirst()) {                     while (!c.isAfterLast()) {                         if (PhoneNumberUtils.compare(number, c.getString(1))) {                                   return c.getString(0);                         }                                c.moveToNext();                        }                     }              } catch (Exception e) {                     Log.e(TAG, "getContactId error:", e);              } finally {                     if (c != null) {c.close();                     }              }              return null;       }

 

 

Phone.CONTENT_URI, = “content:// com.android.contacts/data/phones”

通过查看源代码发现:该URI主要对应着contacts表,raw_contacts表,data表。这段源码对于刚了解该contact2数据库的人说比较费劲,

“qb.setProjectionMap(distinct ? sDistinctDataProjectionMap : sDataProjectionMap);”是非常重要的线索,它告诉我们会查询哪些字段

 

在这里尤其需要说明的是:PhoneNumberUtils.compare(String a, String b) 方法

加入我们在手机中把“13466739143”存储为联系人“张三”(存储的联系手机号仅仅是13466739143),如果张三使用飞信给你发短信(我们知道用飞信,电话号码前会自动加12593),用户肯定希望在手机上的未接短信来自张三,而不是有“1259313466739143”被当作一个陌生号码出现

如何将“1259313466739143”和“13466739143”以及可能的“1795113466739143”,“+8613466739143”等等认为是同一个电话号码,这里的PhoneNumberUtils.compare(String a, String b)就起到了非常关键的作用。

  

有了contact_id之后,我们就可以做很多事情。

1.依据contact_id,去查询该联系人的照片:

  

public static Bitmap getContactsPhoto(Context context, String contactId) {        Cursor c = null;        // load icon        byte[] icon = null;           try {                  // get contact photo URI                  Uri refUri = Uri.withAppendedPath(Contacts.CONTENT_URI, contactId);                  refUri = Uri.withAppendedPath(refUri,                  ContactsContract.Contacts.Photo.CONTENT_DIRECTORY);                  // get cursor                  c = context.getContentResolver().query(refUri,                                   new String[] { Photo.PHOTO }, null, null, null);                 if (c != null && c.moveToFirst()) {         icon = c.getBlob(0);                 }              } catch (Exception e) {                     e.printStackTrace();              } finally {                     if (c != null) {  c.close(); }                }              if (icon != null) {                 try {                         return BitmapFactory.decodeByteArray(icon, 0, icon.length);                    } catch (OutOfMemoryError e) {                            e.printStackTrace();                            System.gc();                            return null;                   }              }              return null;       }

 

 

2. 依据该联系人的contact_id, 去查询该联系人的名字(比如“张三”)

      

private static String getDisplayName(Context context, String contacts_id) {              Cursor c = null;              try {                    c = context.getContentResolver().query(Contacts.CONTENT_URI,                                  new String[] { Contacts.DISPLAY_NAME },                                   Contacts._ID + "=" + contacts_id, null, null);                     if (c != null && c.moveToFirst()) {                            return c.getString(0);                     }              } catch (Exception e) {                     e.printStackTrace();              } finally {                     if (c != null) {  c.close(); }              }              return null;       }

 

 

3. 依据该联系人的contact_id ,查询同一个contact_id有多少个电话号码。

  (有可能手机上存了张三2个号码)

 

public static int getContactNumberCount(Context context, String contactId) {        int count = 0;        Cursor c = null;        try {             c = context.getContentResolver().query(                                                          ContactsContract.CommonDataKinds.Phone.CONTENT_URI,                       new String[] { ContactsContract.CommonDataKinds.Phone.NUMBER},                                                     ContactsContract.CommonDataKinds.Phone.CONTACT_ID  + " = " + contactId, null, null);                                                                  if (c != null && c.moveToFirst()) {                            count = c.getCount();                    }              } catch (Exception e) {                    e.printStackTrace();              } finally {                     if (c != null) {  c.close(); }             }              return count;       }

 

 

------------------------------------------------------------------------------

如果需要读取一个联系人的信息用CONTENT_LOOKUP_RUI代替CONTENT_URI

如果需要通过电话号码查找一个联系人,用PhoneLookup.CONTENT_FIILTER_URI,这个URI为这个目的进行了优化;

如果需要通过部分名字的匹配查找,用CONTENT_FILTER_URI;

 

Android中的通信录操作给人的第一感觉就是晕,不知道怎么去用。可以看下这张我画的图:(比较丑,但是地球人都能看懂)

data1.png
这幅图的意思是: 操作通信录本质是去操作合适的ContentProvider,通过合适的ContentProvider去操作通讯录 。
1: 要想熟练的操作合适的ContentProvider,就必须要掌握一个类ContactsContract(2.0开始使用)。
现在可以思考一个新问题: Android中的联系人信息是如何存储的?
Android 中的联系人信息都是存储在一个叫contacts2.db的数据库中。数据库的路径是:/data/data /com.android.provider.contacts/databases/contacts2.db(在这里推荐一个sqlite的查看工 具:  个人版是免费的)
继续思考一个新问题: 这个数据库是如何来存储联系人信息的?
根据官方的文档:通信录是一个3层的数据存储模型(初看挺牛的,说穿了就是3张表)
我又画了一张图比较形象的反应这个“3层模型”。
data.png
第一层:Data层,每种独立的数据类型占一行。具体哪些独立的数据可以占一行,可以在mimetypes这张表中找到, 原生Android的系统 一共12种,例如name,phone,email ect..
第二层:RawContracts层,由Data层的多条数据组合成一个完整的联系人信息。
第 三层:Contracts层,这一层主要注意与第二层的区别。大部分情况下这两层的数据时指同一个联系人的信息,即他们俩是一一对应的关系,但是有些特殊 情况,这个我是查了一些老外的论坛加上自己的理解,例如 我做一个本地通信录和网络上的通信录同步的时候,可能有一个人他在本地存在,他在网络上也存在,这个时候Android就可以识别他们,认为他们两个其实 是指同一个人。 (这种情况我没有试出来,我感觉这个其实是Android创造了这个概念之后,留给我们开发自己去实现的。)
上面说过“要想熟练的操作合适的ContentProvider,就必须要掌握一个类ContactsContract”,
那么这个ContactsContract类是干什么的呢 ?
说穿了这个类就是去解释和翻译这个contacts2.db数据库的。 
这个类超大6000+行代码, 但是确没有什么操作代码,几乎都是来解释contacts2.db数据库的。
这个类中有很多的内部接口和内部类,用来翻译一些表。 例如Data内部类,RawContacts内部类,等等。
下面我以一个实际的例子讲解一下操作过程:
问题: 我有一个电话号码,现在想去查找这个电话号码主人的姓名。
第一步:我要确定用哪一个ContentProvider去查询,这个时候肯定想到去用含有电话号码的ContentProvider去查询电话号码所在的Contacts_ID.
第二部:通过Contacts_ID找到对应的Raw_Contacts_ID.
第三部:通过Raw_Contacts_ID找到对应的联系人姓名.
确定了操作步骤之后,肯定是去看文档了,谁也不能猜出来含有电话号码ContentProvider_URI.

通过查询文档: If you need to look up a contact by the phone number, usePhoneLookup.CONTENT_FILTER_URI, which is optimized for this purpose.

我们确定了URI之后就可以开始编写代码了。

public String lookupNameByPhoneNumber(Context context,String phoneNumber) {Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber));    Cursor cursor = context.getContentResolver().query(uri,            new String[] {ContactsContract.PhoneLookup.DISPLAY_NAME}, null, null, null);    String name = null;    if (cursor == null) {        name = null;    }    try {       if (cursor.moveToFirst()) {           name = cursor.getColumnName(0);       }    } finally {       cursor.close();    }    return name;}
看完这段代码,你可能想去问,第二步跟第三步跑哪里去了。这两步其实已经被封装在Resoler的query方法中了。如果真要我们编写这两部的话,那我们还不如不用ContentProvider来的轻松。
最后再来说下真机上的通信录模型,真机上的通信录其实就是原生通信录的扩展。增加一些东西。
最重要的一点,原生数据库里有的表,表字段,触发器,视图或索引,在真机上肯定也有。
如果想自己做一个通信录,也肯定要在原生的通信录上扩展,只能增加,不能减少。
想问为什么?
如果你少两张表的话,可能一些系统功能就崩溃了。

这就不符合google的目的了,我提供了一些功能,你可以使用修改,但是你别删除,所以google火了。

添加一条数据

 

ArrayList
ops = new ArrayList
(); ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType()) .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName()) .build()); ops.add(ContentProviderOperation .newInsert(ContactsContract.Data.CONTENT_URI) .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name) .build()); ops.add(ContentProviderOperation .newInsert(ContactsContract.Data.CONTENT_URI) .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone) .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType).build()); ops.add(ContentProviderOperation .newInsert(ContactsContract.Data.CONTENT_URI) .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) .withValue(ContactsContract.CommonDataKinds.Email.DATA, email) .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType).build()); // Ask the Contact provider to create a new contact Log.i(TAG, "Selected account: " + mSelectedAccount.getName() + " (" + mSelectedAccount.getType() + ")"); Log.i(TAG, "Creating contact: " + name); try { getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); } catch (Exception e) { // Display warning }

 

 

 

---------------------------------------------------------

 

Android系统中的联系人也是通过ContentProvider来对外提供数据的,我们这里实现获取所有联系人、通过电话号码获取联系人、添加联系人、使用事务添加联系人。

获取所有联系人

1.   Android系统中的联系人也是通过ContentProvider来对外提供数据的

2.   数据库路径为:/data/data/com.android.providers.contacts/database/contacts2.db

3.   我们需要关注的有3张表

     raw_contacts:其中保存了联系人id

     data:和raw_contacts是多对一的关系,保存了联系人的各项数据

     mimetypes:为数据类型

4.   Provider的authorites为com.android.contacts

5.   查询raw_contacts表的路径为:contacts

6.   查询data表的路径为:contacts/#/data

     这个路径为连接查询,要查询“mimetype”字段可以根据“mimetype_id”查询到mimetypes表中的数据

7.   先查询raw_contacts得到每个联系人的id,在使用id从data表中查询对应数据,根据mimetype分类数据

示例:

//查询所有联系人  public void testGetAll() {      ContentResolver resolver = getContext().getContentResolver();      Uri uri = Uri.parse("content://com.android.contacts/contacts");      Cursor idCursor = resolver.query(uri, new String[] { "_id" }, null, null, null);      while (idCursor.moveToNext()) {          //获取到raw_contacts表中的id          int id = idCursor.getInt(0);           //根据获取到的ID查询data表中的数据          uri = Uri.parse("content://com.android.contacts/contacts/" + id + "/data");          Cursor dataCursor = resolver.query(uri, new String[] { "data1", "mimetype" }, null, null, null);          StringBuilder sb = new StringBuilder();          sb.append("id=" + id);          //查询联系人表中的          while (dataCursor.moveToNext()) {              String data = dataCursor.getString(0);              String type = dataCursor.getString(1);              if ("vnd.android.cursor.item/name".equals(type))                  sb.append(", name=" + data);              else if ("vnd.android.cursor.item/phone_v2".equals(type))                  sb.append(", phone=" + data);              else if ("vnd.android.cursor.item/email_v2".equals(type))                  sb.append(", email=" + data);          }          System.out.println(sb);      }  }

 

通过电话号码获取联系人

1.   系统内部提供了根据电话号码获取data表数据的功能,路径为:data/phones/filter/*

2.   用电话号码替换“*”部分就可以查到所需数据,获取“display_name”可以获取到联系人显示名

示例:

//根据电话号码查询联系人名称  public void testGetName() {      ContentResolver resolver = getContext().getContentResolver();      Uri uri = Uri.parse("content://com.android.contacts/data/phones/filter/1111");      Cursor c = resolver.query(uri, new String[] { "display_name" }, null, null, null);      while (c.moveToNext()) {          System.out.println(c.getString(0));      }  }

 

添加联系人

1.   先向raw_contacts表插入id,路径为:raw_contacts

2.   得到id之后再向data表插入数据,路径为:data

示例:

//添加联系人  ublic void testInsert() {  ContentResolver resolver = getContext().getContentResolver();  Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");  ContentValues values = new ContentValues();  // 向raw_contacts插入一条除了ID之外, 其他全部为NULL的记录, ID是自动生成的  long id = ContentUris.parseId(resolver.insert(uri, values));   //添加联系人姓名  uri = Uri.parse("content://com.android.contacts/data");  values.put("raw_contact_id", id);  values.put("data2", "FHM");  values.put("mimetype", "vnd.android.cursor.item/name");  resolver.insert(uri, values);                  //添加联系人电话  values.clear(); // 清空上次的数据  values.put("raw_contact_id", id);  values.put("data1", "18600000000");  values.put("data2", "2");  values.put("mimetype", "vnd.android.cursor.item/phone_v2");  resolver.insert(uri, values);  //添加联系人邮箱  values.clear();  values.put("raw_contact_id", id);  values.put("data1", "zxx@itcast.cn");  values.put("data2", "1");  values.put("mimetype", "vnd.android.cursor.item/email_v2");  resolver.insert(uri, values);

 

使用事务添加联系人

1.   在添加联系人得时候是分多次访问Provider,如果在过程中出现异常,会出现数据不完整的情况,这些操作应该放在一次事务中

2.   使用ContentResolver的applyBatch(String authority,ArrayList<ContentProviderOperation> operations) 方法可以将多个操作在一个事务中执行

3.   文档位置:

      

示例:

 

//使用事务添加联系人  public void testInsertBatch() throws Exception {      ContentResolver resolver = getContext().getContentResolver();        ArrayList
operations = new ArrayList
(); ContentProviderOperation operation1 = ContentProviderOperation // .newInsert(Uri.parse("content://com.android.contacts/raw_contacts")) // .withValue("_id", null) // .build(); operations.add(operation1); ContentProviderOperation operation2 = ContentProviderOperation // .newInsert(Uri.parse("content://com.android.contacts/data")) // .withValueBackReference("raw_contact_id", 0) // .withValue("data2", "ZZH") // .withValue("mimetype", "vnd.android.cursor.item/name") // .build(); operations.add(operation2); ContentProviderOperation operation3 = ContentProviderOperation // .newInsert(Uri.parse("content://com.android.contacts/data")) // .withValueBackReference("raw_contact_id", 0) // .withValue("data1", "18612312312") // .withValue("data2", "2") // .withValue("mimetype", "vnd.android.cursor.item/phone_v2") // .build(); operations.add(operation3); ContentProviderOperation operation4 = ContentProviderOperation // .newInsert(Uri.parse("content://com.android.contacts/data")) // .withValueBackReference("raw_contact_id", 0) // .withValue("data1", "zq@itcast.cn") // .withValue("data2", "2") // .withValue("mimetype", "vnd.android.cursor.item/email_v2") // .build(); operations.add(operation4); // 在事务中对多个操作批量执行 resolver.applyBatch("com.android.contacts", operations); }

转载于:https://www.cnblogs.com/chaoyu/p/6436978.html

你可能感兴趣的文章
关于滚动条
查看>>
软件门外汉的入门进阶
查看>>
一文把samba相关的都说清楚
查看>>
批处理用WINRAR只压缩某类型的文件
查看>>
40.配置完善爬虫代码文件及图片下载文件重命名问题-1
查看>>
python学习日记 1
查看>>
C++基础之泛型算法
查看>>
BZOJ 4756 [Usaco2017 Jan]Promotion Counting
查看>>
QC托盘没有图标,运行QCTrayIcon.exe出错解决方案
查看>>
Windows核心编程02-记事本写代码深入理解cl.exe和link.exe
查看>>
HTML协议
查看>>
Win7、Ubuntu双系统正确卸载Ubuntu系统
查看>>
cocos2d-x JsonBox 读写
查看>>
服务器搭建3 安装libevent2.0.20
查看>>
jchdl-GSL-实例 - 使用Intellij IDEA创建Mux
查看>>
去掉‘为帮助保护您的安全,internet explorer已经限制此文件显示可能访问您的计算机的活动内容’提示...
查看>>
项目管理过程
查看>>
Access数据库中“所有记录中均未找到搜索关键字”的解决方法
查看>>
jQuery贪吃蛇--jQuery学习
查看>>
linux下开启ssh服务
查看>>