¿Cómo obtener ID de contacto, correo electrónico, número de teléfono en una consulta SQLite? Optimización para Android de los contactos

Quiero buscar todos los contactos al menos con un número de teléfono, también quiero todos los números de teléfono y todos los correos electrónicos para cada contacto.

Código actual:

// 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 } 

Si hay más de 1000 contactos en mi dispositivo, está tomando demasiado tiempo. ¿Cómo puedo obtener todos los datos en una sola consulta, en lugar de hacer dos consultas adicionales para cada contacto?

¿O hay alguna otra manera de optimizar?

Gracias de antemano.

ICS: Cuando consulta desde Data.CONTENT_URI tiene todas las filas del Contact asociado ya unido, es decir, esto funcionaría:

 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); } 

Si apunta a 2.3, debe tener en cuenta el hecho de que HAS_PHONE_NUMBER no está disponible a través de las combinaciones utilizadas al consultar Data .

Diversión .

Esto podría ser resuelto, por ejemplo, saltando su requisito de que el contacto debe tener un número de teléfono y en su lugar se conforman con "cualquier contacto con al menos un número de teléfono o una dirección de correo electrónico":

 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); 

Si eso no es una opción que siempre puede ir para un horrible hacky sub-seleccionar:

 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); 

O solucionarlo usando dos Cursor :

 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); } } } } 

Pasé por el mismo problema. Desde entonces construyo mi propia solución que está inspirada en este post pero un poco diferente. Ahora me gustaría compartirlo como mi primera respuesta StackOverFlow 🙂

Es bastante similar al enfoque de doble cursor sugerido por Jens. La idea es

1- busque el contacto relevante desde la tabla Contactos
2- buscar la información relevante de Contactos (correo, teléfono, …)
3- combinar estos resultados

El "relevante" depende de usted, por supuesto, pero el punto importante es el rendimiento! Además, estoy seguro de que otras soluciones que utilizan la consulta bien adaptada SQL también podría hacer el trabajo, pero aquí sólo quiero usar el Android ContentProvider Aquí está el código:

Algunas constantes

 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 Contacto

Aquí necesito que los contactos tengan DISPLAY_NAME libre de "@" y que sus datos coincidan con una cadena dada (estos requisitos pueden ser modificados, por supuesto). El resultado del siguiente método es el primer cursor:

 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; } 

Esta consulta es bastante performant como verás!

2 Datos de contacto

Ahora vamos a buscar información de contacto. En este punto, no hago ningún enlace entre el contacto ya buscado y la información recuperada: Sólo busco todas las informaciones de la tabla de datos … Sin embargo, para evitar información inútil todavía necesito DISPLAY_NAMES libre de "@" Estoy interesado en el correo electrónico y el teléfono que requieren que los datos MIMETYPE a ser MAIL_TYPE o PHONE_TYPE (ver Constantes). Aquí está el código:

 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; } 

Una vez más, verá que esta consulta es bastante rápida …

3 Combinación

Ahora vamos a combinar tanto el contacto como sus respectivas informaciones. La idea es usar HashMap (Key, String) donde Key es el identificador de contacto y String es lo que quieras (nombre, correo electrónico, …).

En primer lugar, corro a través del cursor de contacto (que está ordenado alfabéticamente) y almacenar los nombres y la imagen uri en dos diferentes HashMap. Tenga en cuenta también que almaceno todas las identificaciones de contacto en una lista en el mismo orden en que aparecen los contactos en el cursor. Llamamos a esta lista contactListId

Hago lo mismo para las informaciones de contacto (correo y correo electrónico). Pero ahora me encargo de la correlación entre los dos cursor: si el CONTACT_ID de un correo electrónico o teléfono no aparece en contactListId se deja a un lado. También compruebo si el correo electrónico ya se ha encontrado. Observe que esta selección adicional puede introducir asimetrías entre el contenido de Nombre / Imagen y el contenido de HashMap Email / Teléfono, pero no se preocupe.

Eventualmente, corro sobre la lista contactListId y construyo una lista de objeto Contact teniendo en cuenta el hecho de que: un contacto debe tener información (condición keySet) y que el contacto debe tener al menos un correo o un correo electrónico (el caso donde mail = = Null && phone == null puede aparecer si el contacto es un contacto de Skype por ejemplo). Y aquí está el código:

 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; } 

La definición del objeto de contacto depende de usted.

Espero que esto ayude y gracias por el post anterior.

Corrección / Mejora

Olvidé comprobar el sistema de la llave del teléfono: debe bastante parece

 !mailKeySet.contains(key) 

reemplazado por

  (!mailKeySet.contains(key) && !phoneKeySet.contains(key)) 

Con el teléfono keySet

 Set<Integer> phoneKeySet = phoneMap.keySet(); 

¿Por qué no añadir un cursor de contacto vacío como:

 if(contactCursor.getCount() == 0){ contactCursor.close(); return new ArrayList<>(); } 

Justo después de la llamada getContactCursor

  • La carpeta de "datos" del monitor de dispositivos Android está vacía
  • Java.lang.IllegalStateException: intento de volver a abrir un objeto ya cerrado (Tried closing)
  • Tabla normal vs tabla virtual SQLite DB
  • Problemas para obtener Calendar.WEEK_OF_YEAR
  • La base de datos SQLite de Android está bloqueada
  • Cómo contar el número de registros en sqlite en Android
  • Android - Observer DB cambia con GreenDao ORM
  • Problema de base de datos SQLite en TextView
  • Simple exportación e importación de una base de datos SQLite en Android
  • DB SQLite de Android Cuando cerrar
  • Limitar los registros obtenidos en Android (base de datos Sqlite)
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.