android通讯录开发及优化

联系人

首先需要说明的是,Android系统中的联系人的存储并不是仅仅是一张表。信息存储分为了不同的表,可以按表访问,同时其设计人员为应用开发人员提供了视图模式。

下图是通讯录的表结构:
通讯录表结构

查看联系人SQLITE表结构流程方法博客链接

在做通讯录相关开发之前,首先要添加联系人相关权限
< uses-permission android:name=”android.permission.READ_CONTACTS” />
< uses-permission android:name=”android.permission.WRITE_CONTACTS” />

数据库的存储结构:

  • 在联系人数据库中,保存的都是一些小的数据表,即与把所有数据保存成一个表不同,它会对联系人的资料模块化,然后分成多个表保存。
  • android已经替我们准备好了,它在数据库里面建了一些视图,视图就是虚拟表。
  • 联系人的数据库比较复杂,在联系人相关应用开发中,一般也不直接通过数据库字段来操作,主要用视图(指定的Uri)来操作。
  • android也提供了很多接口,通过ContentResolver().query方法,传入不同的URI即可访问相应的数据集。

一个联系人信息的存储

  • 在联系人数据库里面联系人和电话号码是分别存在两个表里面的,因为存在一个联系人拥有几个号码的情况,所以android为联系人和手机号码分别单独创建了相应的视图。
  • 联系人信息的视图里面只保存与联系人相关的资料,例如姓名,是否有手机号码等。
  • 手机号码资料则是每一个电话号码为一条记录,如果有一个联系人有3个号码,则里面会出现3个该联系人的记录,号码分别为他的三个号码。

不同视图URL

如果是需要读取联系人信息,使用的URI为:ContactsContract.Contacts.CONTENT_URI
如果是需要读取手机号码信息, 使用的URI为:ContactsContract.CommonDataKinds.Phone.CONTENT_URI

ContentResolver.query Method解析

ContentResolver.query函数的原型,query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

  • projection:是需要读取的字段
  • selection:是数据检索的条件
  • selectionArgs:是数据检索条件的参数
  • sortOrder:是排序的字段

解释一下:
假如一条sql语句如下:select * from anyTable where var=’const’
那么anyTable就是uri,*就是projection,selection是“var=?”,selectionArgs写成这样:new String[]{‘const‘}至于最后一个就简单了,就是排序方式。

只读取自己需要的信息,减少读取的信息,可以减少读取时间


public static List getContactsMultiPhoneNumber(Context context) {
//定义常量,节省重复引用的时间
Uri CONTENT_URI = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
String CONTACT_ID = ContactsContract.CommonDataKinds.Phone.CONTACT_ID;
String NUMBER = ContactsContract.CommonDataKinds.Phone.NUMBER;
String ID = ContactsContract.Contacts._ID;
String DISPLAY_NAME = ContactsContract.Contacts.DISPLAY_NAME;
String HAS_PHONE_NUMBER = ContactsContract.Contacts.HAS_PHONE_NUMBER;
//临时变量
String contactId;
String displayName;
Cursor phoneCursor;
//生成ContentResolver对象
ContentResolver contentResolver = context.getContentResolver();
// 获取手机联系人
Cursor cursor = contentResolver.query(Uri.parse("content://com.android.contacts/contacts"), null, null, null, null);
List friendList = new ArrayList<>();
// 无联系人直接返回
if (!cursor.moveToFirst()) {//moveToFirst定位到第一行
return null;
}
do {
// 获得联系人的ID:String类型 列名--》列数--》列内容
contactId = cursor.getString(cursor.getColumnIndex(ID));
// 获得联系人姓名:String类型
displayName = cursor.getString(cursor.getColumnIndex(DISPLAY_NAME));
// 查看联系人有多少个号码,如果没有号码,返回0
int phoneCount = cursor.getInt(cursor.getColumnIndex(HAS_PHONE_NUMBER));

FriendItem item;
List phoneList = new ArrayList<>();
if (phoneCount <= 0) {
continue;
} else if (phoneCount == 1) {
//仅有一个联系号码
phoneCursor = contentResolver.query(CONTENT_URI, null, CONTACT_ID + "=" + contactId, null, null);
if (!phoneCursor.moveToFirst()) {
continue;
} phoneList.add(StringUtil.removeBlank(phoneCursor.getString(phoneCursor.getColumnIndex(NUMBER))));
item = new FriendItem(displayName, phoneList, false);
} else {
// 有多个联系号码
phoneCursor = contentResolver.query(CONTENT_URI, null, CONTACT_ID + "=" + contactId, null, null);
if (!phoneCursor.moveToFirst()) {
continue;
}
do {
phoneList.add(StringUtil.removeBlank(
phoneCursor.getString(phoneCursor.getColumnIndex(NUMBER))
));
} while (phoneCursor.moveToNext());
item = new FriendItem(displayName, phoneList, false);
}
item.setMainPhoneNumber(phoneList.get(0));
friendList.add(item);
} while (cursor.moveToNext());
return friendList;
}

上述代码所实现的效果是读取联系人姓名,手机号(如果有多个手机号,将多个手机号都读出来)。因为联系人信息和手机号信息是在不同的数据库表中,所以先通过Contact视图读出用户信息,然后再在phoneNumber视图中读出手机号。思路很自然。该代码经过实际测试,1000个联系人耗时约为20秒左右,实际使用中效率无法满足需求。
对上述代码的优化过程:

流程拆分和优化

将上面的读取信息的两个过程拆分开,与用户界面结合。初始显示所有用户时不显示手机号,只显示用户名,当用户列表中某个用户名被选中时,再去寻找该用户对应的手机号。这样就节省了大量无用用户手机号的读取时间。经过实际测试,经过这样处理之后,1000个联系人的预读取时间在300ms左右,单独获取手机号的时间可以不在用户能够感知到的范围。

获取简略用户列表

public static List getBriefContactInfor(Context context) {
//定义常量,节省重复引用的时间
Uri CONTENT_URI = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
String CONTACT_ID = ContactsContract.CommonDataKinds.Phone.CONTACT_ID;
String NUMBER = ContactsContract.CommonDataKinds.Phone.NUMBER;
String ID = ContactsContract.Contacts._ID;
String DISPLAY_NAME = ContactsContract.Contacts.DISPLAY_NAME;
String HAS_PHONE_NUMBER = ContactsContract.Contacts.HAS_PHONE_NUMBER;
//临时变量
String contactId;
String displayName;
//生成ContentResolver对象
ContentResolver contentResolver = context.getContentResolver();
// 获取手机联系人
Cursor cursor = contentResolver.query(Uri.parse("content://com.android.contacts/contacts"), null, null, null, null);
List friendList = new ArrayList<>();
// 无联系人直接返回
if (!cursor.moveToFirst()) {//moveToFirst定位到第一行
return null;
}
do {
// 获得联系人的ID:String类型 列名--》列数--》列内容
contactId = cursor.getString(cursor.getColumnIndex(ID));
// 获得联系人姓名:String类型
displayName = cursor.getString(cursor.getColumnIndex(DISPLAY_NAME));
// 查看联系人有多少个号码,如果没有号码,返回0
int phoneCount = cursor.getInt(cursor.getColumnIndex(HAS_PHONE_NUMBER));
FriendItem item;
item = new FriendItem(displayName, null, false);
item.setContactId(contactId);
item.setPhoneCount(phoneCount);
friendList.add(item);
} while (cursor.moveToNext());

for (int i = 0; i < friendList.size(); i++) {
friendList.get(i).setPinyin(PinYin.getPinYin(friendList.get(i).getName()).toLowerCase());
}
Collections.sort(friendList);
return friendList;
}

获取当个用户的手机号

//根据cotact_id来获取该联系人的手机号
public static FriendItem getDetailFromContactID(Context context, FriendItem item) {
Uri CONTENT_URI = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
String CONTACT_ID = ContactsContract.CommonDataKinds.Phone.CONTACT_ID;
String NUMBER = ContactsContract.CommonDataKinds.Phone.NUMBER;

Cursor phoneCursor;
ContentResolver contentResolver = context.getContentResolver();
List phoneList = new ArrayList<>();
if (item.getContactId() == null) {
return item;
}
phoneCursor = contentResolver.query(CONTENT_URI, null, CONTACT_ID + "=" + item.getContactId(), null, null);
if (!phoneCursor.moveToFirst()) {
return item;
}
do {
String temp = StringUtil.normalizePhone(phoneCursor.getString(phoneCursor.getColumnIndex(NUMBER)));
if (temp != null) {
phoneList.add(temp);
}
} while (phoneCursor.moveToNext());

item.setPhoneNumber(phoneList);
return item;
}

预读取和修改监控

考虑到通讯录的实际变动比较少,可以先对通讯录的信息进行预先读取之后,自行保存下来,或者在app登录时预先读取(有一定用户隐私的争议,这里是个思路)。在每次实际的使用前,先检查,当前的通讯录与保存下来的通讯录之间有没有信息改动。检测的过程是,在raw_contacts表中有version字段。在预读取时将通讯录的所有用户以及version保存下来,然后使用通论录信息时,获取当前用户以及version字段,相互对比,即可获取新增,删除和修改信息。然后更新修改信息。
相关资料:

附录:

raw_contacts表结构:

data表结构:

posted @ 2015-10-02 07:19  CC同学哈  阅读(6794)  评论(0)    收藏  举报