我遇到了完全相同的问题。从那以后,我构建了自己的解决方案,该解决方案的灵感来自这篇文章,但有点不同。现在我想分享它作为我的第一个 StackOverFlow 答案:-)
它与 Jens 建议的双光标方法非常相似。这个想法是
1- 从联系人表中获取相关联系人
2- 获取相关的联系人信息(邮件、电话、...)
3- 结合这些结果
“相关”当然取决于您,但重要的是性能!
此外,我确信使用非常适合 SQL 查询的其他解决方案也可以完成这项工作,但在这里我只想使用 Android ContentProvider
这是代码:
一些常量
public static String CONTACT_ID_URI = ContactsContract.Contacts._ID;
public static String DATA_CONTACT_ID_URI = ContactsContract.Data.CONTACT_ID;
public static String MIMETYPE_URI = ContactsContract.Data.MIMETYPE;
public static String EMAIL_URI = ContactsContract.CommonDataKinds.Email.DATA;
public static String PHONE_URI = ContactsContract.CommonDataKinds.Phone.DATA;
public static String NAME_URI = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) ? ContactsContract.Data.DISPLAY_NAME_PRIMARY : ContactsContract.Data.DISPLAY_NAME;
public static String PICTURE_URI = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) ? ContactsContract.Contacts.PHOTO_THUMBNAIL_URI : ContactsContract.Contacts.PHOTO_ID;
public static String MAIL_TYPE = ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE;
public static String PHONE_TYPE = ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE;
1 个联系人
这里我要求联系人的 DISPLAY_NAME 必须不含“@”,并且他们的信息必须与给定的字符串匹配(这些要求当然可以修改)。以下方法的结果是第一个游标:
public Cursor getContactCursor(String stringQuery, String sortOrder) {
Logger.i(TAG, "+++++++++++++++++++++++++++++++++++++++++++++++++++");
Logger.e(TAG, "ContactCursor search has started...");
Long t0 = System.currentTimeMillis();
Uri CONTENT_URI;
if (stringQuery == null)
CONTENT_URI = ContactsContract.Contacts.CONTENT_URI;
else
CONTENT_URI = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI, Uri.encode(stringQuery));
String[] PROJECTION = new String[]{
CONTACT_ID_URI,
NAME_URI,
PICTURE_URI
};
String SELECTION = NAME_URI + " NOT LIKE ?";
String[] SELECTION_ARGS = new String[]{"%" + "@" + "%"};
Cursor cursor = sContext.getContentResolver().query(CONTENT_URI, PROJECTION, SELECTION, SELECTION_ARGS, sortOrder);
Long t1 = System.currentTimeMillis();
Logger.e(TAG, "ContactCursor finished in " + (t1 - t0) / 1000 + " secs");
Logger.e(TAG, "ContactCursor found " + cursor.getCount() + " contacts");
Logger.i(TAG, "+++++++++++++++++++++++++++++++++++++++++++++++++++");
return cursor;
}
如您所见,此查询非常高效!
2 联系方式
现在让我们获取联系信息。在这一点上,我没有在已经获取的联系人和检索到的信息之间建立任何链接:我只是从数据表中获取所有信息......然而,为了避免无用的信息,我仍然需要没有“@”的 DISPLAY_NAMES,因为我我对电子邮件和电话感兴趣,我要求数据 MIMETYPE 为 MAIL_TYPE 或 PHONE_TYPE(请参阅常量)。这是代码:
public Cursor getContactDetailsCursor() {
Logger.i(TAG, "+++++++++++++++++++++++++++++++++++++++++++++++++++");
Logger.e(TAG, "ContactDetailsCursor search has started...");
Long t0 = System.currentTimeMillis();
String[] PROJECTION = new String[]{
DATA_CONTACT_ID_URI,
MIMETYPE_URI,
EMAIL_URI,
PHONE_URI
};
String SELECTION = ContactManager.NAME_URI + " NOT LIKE ?" + " AND " + "(" + MIMETYPE_URI + "=? " + " OR " + MIMETYPE_URI + "=? " + ")";
String[] SELECTION_ARGS = new String[]{"%" + "@" + "%", ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE};
Cursor cursor = sContext.getContentResolver().query(
ContactsContract.Data.CONTENT_URI,
PROJECTION,
SELECTION,
SELECTION_ARGS,
null);
Long t1 = System.currentTimeMillis();
Logger.e(TAG, "ContactDetailsCursor finished in " + (t1 - t0) / 1000 + " secs");
Logger.e(TAG, "ContactDetailsCursor found " + cursor.getCount() + " contacts");
Logger.i(TAG, "+++++++++++++++++++++++++++++++++++++++++++++++++++");
return cursor;
}
你会再次看到这个查询相当快...
3 组合
现在让我们结合联系人及其各自的信息。我们的想法是使用 HashMap(Key, String),其中 Key 是联系人 ID,String 是您喜欢的任何内容(姓名、电子邮件、...)。
首先,我遍历联系人光标(按字母顺序排列)并将名称和图片 uri 存储在两个不同的 HashMap 中。另请注意,我将所有联系人 ID 存储在列表中的顺序与联系人出现在光标中的顺序完全相同。让我们将此列表称为contactListId
我对联系信息(邮件和电子邮件)也是如此。但是现在我处理了两个光标之间的相关性:如果电子邮件或电话的 CONTACT_ID 没有出现在 contactListId 中,则将其放在一边。我还检查是否已经遇到过电子邮件。请注意,此进一步选择可能会在名称/图片内容和电子邮件/电话 HashMap 内容之间引入不对称,但不要担心。
最后,我遍历了 contactListId 列表并构建了一个 Contact 对象列表,考虑到以下事实:联系人必须有信息(keySet 条件)并且联系人必须至少有一封邮件或电子邮件(案例例如,如果联系人是 Skype 联系人,则可能会出现 mail == null && phone == null)。
这是代码:
public List<Contact> getDetailedContactList(String queryString) {
/**
* First we fetch the contacts name and picture uri in alphabetical order for
* display purpose and store these data in HashMap.
*/
Cursor contactCursor = getContactCursor(queryString, NAME_URI);
List<Integer> contactIds = new ArrayList<>();
if (contactCursor.moveToFirst()) {
do {
contactIds.add(contactCursor.getInt(contactCursor.getColumnIndex(CONTACT_ID_URI)));
} while (contactCursor.moveToNext());
}
HashMap<Integer, String> nameMap = new HashMap<>();
HashMap<Integer, String> pictureMap = new HashMap<>();
int idIdx = contactCursor.getColumnIndex(CONTACT_ID_URI);
int nameIdx = contactCursor.getColumnIndex(NAME_URI);
int pictureIdx = contactCursor.getColumnIndex(PICTURE_URI);
if (contactCursor.moveToFirst()) {
do {
nameMap.put(contactCursor.getInt(idIdx), contactCursor.getString(nameIdx));
pictureMap.put(contactCursor.getInt(idIdx), contactCursor.getString(pictureIdx));
} while (contactCursor.moveToNext());
}
/**
* Then we get the remaining contact information. Here email and phone
*/
Cursor detailsCursor = getContactDetailsCursor();
HashMap<Integer, String> emailMap = new HashMap<>();
HashMap<Integer, String> phoneMap = new HashMap<>();
idIdx = detailsCursor.getColumnIndex(DATA_CONTACT_ID_URI);
int mimeIdx = detailsCursor.getColumnIndex(MIMETYPE_URI);
int mailIdx = detailsCursor.getColumnIndex(EMAIL_URI);
int phoneIdx = detailsCursor.getColumnIndex(PHONE_URI);
String mailString;
String phoneString;
if (detailsCursor.moveToFirst()) {
do {
/**
* We forget all details which are not correlated with the contact list
*/
if (!contactIds.contains(detailsCursor.getInt(idIdx))) {
continue;
}
if(detailsCursor.getString(mimeIdx).equals(MAIL_TYPE)){
mailString = detailsCursor.getString(mailIdx);
/**
* We remove all double contact having the same email address
*/
if(!emailMap.containsValue(mailString.toLowerCase()))
emailMap.put(detailsCursor.getInt(idIdx), mailString.toLowerCase());
} else {
phoneString = detailsCursor.getString(phoneIdx);
phoneMap.put(detailsCursor.getInt(idIdx), phoneString);
}
} while (detailsCursor.moveToNext());
}
contactCursor.close();
detailsCursor.close();
/**
* Finally the contact list is build up
*/
List<Contact> contacts = new ArrayList<>();
Set<Integer> detailsKeySet = emailMap.keySet();
for (Integer key : contactIds) {
if(!detailsKeySet.contains(key) || (emailMap.get(key) == null && phoneMap.get(key) == null))
continue;
contacts.add(new Contact(String.valueOf(key), pictureMap.get(key), nameMap.get(key), emailMap.get(key), phoneMap.get(key)));
}
return contacts;
}
联系人对象定义由您决定。
希望这会有所帮助,并感谢之前的帖子。
更正/改进
我忘了检查电话键设置:它应该看起来像
!mailKeySet.contains(key)
替换为
(!mailKeySet.contains(key) && !phoneKeySet.contains(key))
用手机按键设置
Set<Integer> phoneKeySet = phoneMap.keySet();
我为什么不添加一个空的联系人光标检查,例如:
if(contactCursor.getCount() == 0){
contactCursor.close();
return new ArrayList<>();
}
在 getContactCursor 调用之后