Как получить идентификатор контакта, адрес электронной почты, номер телефона в одном запросе SQLite? Контакты Оптимизация Android
Я хочу получить все контакты по крайней мере с одним номером телефона. Также мне нужны все номера телефонов и все письма для каждого контакта.
Текущий код:
// To get All Contacts having atleast one phone number.
Uri uri = ContactsContract.Contacts.CONTENT_URI;
String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + " > ?";
String[] selectionArgs = new String[] {"0"};
Cursor cu = applicationContext.getContentResolver().query(uri,
null, selection, selectionArgs, null);
// For getting All Phone Numbers and Emails further queries :
while(cu.moveToNext()){
String id = cu.getString(cu.getColumnIndex(ContactsContract.Contacts._ID));
// To get Phone Numbers of Contact
Cursor pCur = context.getContentResolver().query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=?",
new String[]{id}, null);
// To get Email ids of Contact
Cursor emailCur = context.getContentResolver().query(
ContactsContract.CommonDataKinds.Email.CONTENT_URI, null,
ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = ?",
new String[]{id}, null);
// Iterate through these cursors to get Phone numbers and Emails
}
Если на моем устройстве более 1000 контактов, это занимает слишком много времени. Как я могу получить все данные в одном запросе, а не делать два дополнительных запроса для каждого контакта?
Или есть ли другой способ оптимизации?
Благодарим вас в Advance.
Ответы
Ответ 1
ICS: при запросе из Data.CONTENT_URI
у вас есть все строки из связанного Contact
, уже подключенного - то есть это будет работать:
ContentResolver resolver = getContentResolver();
Cursor c = resolver.query(
Data.CONTENT_URI,
null,
Data.HAS_PHONE_NUMBER + "!=0 AND (" + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=?)",
new String[]{Email.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE},
Data.CONTACT_ID);
while (c.moveToNext()) {
long id = c.getLong(c.getColumnIndex(Data.CONTACT_ID));
String name = c.getString(c.getColumnIndex(Data.DISPLAY_NAME));
String data1 = c.getString(c.getColumnIndex(Data.DATA1));
System.out.println(id + ", name=" + name + ", data1=" + data1);
}
Если вы используете таргетинг на 2.3, вам необходимо учитывать тот факт, что HAS_PHONE_NUMBER
недоступен через соединения, используемые при запросе Data
.
<я > Fun.
Это можно решить, например, путем отказа от вашего требования о том, что контакт должен иметь номер телефона и вместо этого разрешить "любой контакт с хотя бы номером телефона или адресом электронной почты":
Cursor c = resolver.query(
Data.CONTENT_URI,
null,
Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=?",
new String[]{Email.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE},
Data.CONTACT_ID);
Если это не вариант, вы всегда можете пойти на жуткий hacky sub-select:
Cursor c = resolver.query(
Data.CONTENT_URI,
null,
"(" + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=?) AND " +
Data.CONTACT_ID + " IN (SELECT " + Contacts._ID + " FROM contacts WHERE " + Contacts.HAS_PHONE_NUMBER + "!=0)",
new String[]{Email.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE}, Data.CONTACT_ID);
или решить его, используя two Cursor
s:
Cursor contacts = resolver.query(Contacts.CONTENT_URI,
null, Contacts.HAS_PHONE_NUMBER + " != 0", null, Contacts._ID + " ASC");
Cursor data = resolver.query(Data.CONTENT_URI, null,
Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=?",
new String[]{Email.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE},
Data.CONTACT_ID + " ASC");
int idIndex = contacts.getColumnIndexOrThrow(Contacts._ID);
int nameIndex = contacts.getColumnIndexOrThrow(Contacts.DISPLAY_NAME);
int cidIndex = data.getColumnIndexOrThrow(Data.CONTACT_ID);
int data1Index = data.getColumnIndexOrThrow(Data.DATA1);
boolean hasData = data.moveToNext();
while (contacts.moveToNext()) {
long id = contacts.getLong(idIndex);
System.out.println("Contact(" + id + "): " + contacts.getString(nameIndex));
if (hasData) {
long cid = data.getLong(cidIndex);
while (cid <= id && hasData) {
if (cid == id) {
System.out.println("\t(" + cid + "/" + id + ").data1:" +
data.getString(data1Index));
}
hasData = data.moveToNext();
if (hasData) {
cid = data.getLong(cidIndex);
}
}
}
}
Ответ 2
Я рассмотрел ту же проблему. С тех пор я создаю собственное решение, которое вдохновлено этим сообщением, но немного другим. Теперь я хотел бы поделиться им как с первым ответом StackOverFlow: -)
Его очень похоже на подход с двойным курсором, предложенный Йенсом. Идея состоит в том, чтобы
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 без "@", m интересуется электронной почтой и телефоном. Мне требуется, чтобы данные 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 - это идентификатор контакта, а String - все, что вам нравится (имя, адрес электронной почты,...).
Во-первых, я запускаю курсор "Контакт" (который упорядочен по алфавиту) и сохраняет имена и рисунки uri в двух разных HashMap. Также обратите внимание, что я сохраняю все Contact id в списке в том же порядке, что и контакты в курсоре. Позволяет называть этот список contactListId
Я делаю то же самое для информации о контакте (почта и электронная почта). Но теперь я позабочусь о корреляции между двумя курсорами: если CONTACT_ID электронной почты или телефона не отображается в contactListId, он откладывается. Я также проверяю, было ли электронное письмо уже встречено. Обратите внимание, что этот дополнительный выбор может вводить асимметрию между содержимым Name/Picture и содержимым HashMap Email/Phone, но не беспокойтесь.
В конце концов я запускаю список contactListId и строю список объекта Contact, заботясь о том, что: контакт должен иметь информацию (условие KeySet) и что у контакта должно быть как минимум почта или электронное письмо (случай где mail == null && phone == null может появиться, если контакт является контактом Skype, например).
И вот код:
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