联系人
首先需要说明的是,Android系统中的联系人的存储并不是仅仅是一张表。信息存储分为了不同的表,可以按表访问,同时其设计人员为应用开发人员提供了视图模式。
下图是通讯录的表结构:
在做通讯录相关开发之前,首先要添加联系人相关权限:
< 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<FriendItem> 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<FriendItem> 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<String> 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<FriendItem> 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<FriendItem> 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<String> 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表结构: