¿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:
- Cómo eliminar todos los elementos de SQLite en Android
- Prueba de Android JUnit para SQLiteOpenHelper
- Android, manejar SQLiteConstraintException
- Notificaciones de la base de datos SQLite de Android
- Vista de lista con la opción de actualización y supresión mediante contextmenu
// 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.
- Android: transacciones SQLite al usar ContentResolver
- Evitar duplicar la entrada en la base de datos sqllite android
- Compartir y persistir datos entre varias aplicaciones de Android
- Cómo diferenciar dos tablas
- No se puede usar como cláusula en la aplicación android
- ¿Cómo puedo anotar correctamente las clases de herencia usando ORMLite?
- Cómo implementar una base de datos de objetos de uno a muchos en sqlite para android
- ¿Tengo que usar _ID como una clave primaria de SQlite? Y tiene que ser un INT? (Android Dev)
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
- ¿Cómo hacer la llamada entrante en el emulador de Genymotion para Android?
- System.ObjectModel Advertencia Xamarin no se ejecuta en Android