Search code examples
androidabaddressbookandroid-contentresolver

Android fetch all contact list (name, email, phone) takes more then a minute for about 700 contacts


Is there any way to shorten this time? I'm running with the cursor and takes the name, phone numbers and emails

if I remove the phone numbers query from the query loop it ends in 3 seconds

any idea how can I improve that query?

Maybe I'm doing something wrong in my query?

(Obviously I'm doing it async but still... it's a very long time that a user can't wait)

Hope someone can share his thoughts about this

this is my code

ContentResolver cr = getContentResolver();
            Cursor cur = cr.query(ContactsContract.Contacts.CONTENT_URI,
                    null, null, null, null);
            if (cur.getCount() > 0) {
                while (cur.moveToNext()) {
                    AddressBookEntity adr = new AddressBookEntity();
                    String id = cur.getString(cur
                            .getColumnIndex(ContactsContract.Contacts._ID));
                    String name = cur
                            .getString(cur
                                    .getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
                    adr.fullName = name;

                    Cursor emailCur = cr
                            .query(ContactsContract.CommonDataKinds.Email.CONTENT_URI,
                                    null,
                                    ContactsContract.CommonDataKinds.Email.CONTACT_ID
                                            + " = ?", new String[] { id },
                                    null);
                    while (emailCur.moveToNext()) {
                        // This would allow you get several email addresses
                        // if the email addresses were stored in an array
                        String email = emailCur
                                .getString(emailCur
                                        .getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA));
                        if (!Utils.IsNullOrEmptyString(email)) {
                            adr.email = email;
                        }

                    }
                    emailCur.close();

                    if (Integer
                            .parseInt(cur.getString(cur
                                    .getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))) > 0) {
                        Cursor pCur = cr
                                .query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                                        null,
                                        ContactsContract.CommonDataKinds.Phone.CONTACT_ID
                                                + " = ?",
                                        new String[] { id }, null);
                        int phoneIndex = 0;
                        while (pCur.moveToNext()) {
                            String number = pCur.getString(pCur
                                    .getColumnIndex(Phone.NUMBER));

                            String country = Utils.GetCountryFromNumber(
                                    number, app);
                            number = Utils.GetFullPhoneNumber(number, app);
                            if (phoneIndex == 0) {
                                if (!Utils.IsNullOrEmptyString(number)) {
                                    adr.contactAdressBookId = id;
                                    adr.phoneNumber = number;
                                    adr.userInsertedId = app.userCred.userId;
                                    adr.country = country;
                                    myContacts.add(adr);
                                }
                            } else {
                                if (!Utils.IsNullOrEmptyString(number)) {
                                    AddressBookEntity adrMore = new AddressBookEntity();
                                    adrMore.fullName = adrMore.fullName;
                                    adrMore.country = adrMore.country;
                                    adrMore.email = adrMore.email;
                                    adrMore.phoneNumber = number;
                                    adrMore.contactAdressBookId = id;
                                    adrMore.country = country;
                                    myContacts.add(adrMore);
                                }
                            }
                        }
                        pCur.close();
                    }
                }
                cur.close();

Solution

  • with the following code for 59 contacts i got the following results on the emulator:

          D  ╔══════ query execution stats ═══════
          D  ║    got 59 contacts
          D  ║    query took 0.012 s (12 ms)
          D  ╚════════════════════════════════════
    

    ok, that was the best time, but the average is 25-35 ms (for 59 contacts), add the following code in some onClick callback and run in several times in order to get the average time, in your case you should get 30 * 700 / 59 = ~300-400 ms, not 3 seconds, let alone one minute ;)

    it uses Uri set to Contactables.CONTENT_URI added in API level 18 but you can use ContactsContract.Data.CONTENT_URI when building for pre 18 API devices

    List<AddressBookContact> list = new LinkedList<AddressBookContact>();
    LongSparseArray<AddressBookContact> array = new LongSparseArray<AddressBookContact>();
    long start = System.currentTimeMillis();
    
    String[] projection = {
            ContactsContract.Data.MIMETYPE,
            ContactsContract.Data.CONTACT_ID,
            ContactsContract.Contacts.DISPLAY_NAME,
            ContactsContract.CommonDataKinds.Contactables.DATA,
            ContactsContract.CommonDataKinds.Contactables.TYPE,
    };
    String selection = ContactsContract.Data.MIMETYPE + " in (?, ?)";
    String[] selectionArgs = {
            ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE,
            ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
    };
    String sortOrder = ContactsContract.Contacts.SORT_KEY_ALTERNATIVE;
    
    Uri uri = ContactsContract.CommonDataKinds.Contactables.CONTENT_URI;
    // we could also use Uri uri = ContactsContract.Data.CONTENT_URI;
    
    // ok, let's work...
    Cursor cursor = getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
    
    final int mimeTypeIdx = cursor.getColumnIndex(ContactsContract.Data.MIMETYPE);
    final int idIdx = cursor.getColumnIndex(ContactsContract.Data.CONTACT_ID);
    final int nameIdx = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
    final int dataIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Contactables.DATA);
    final int typeIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Contactables.TYPE);
    
    while (cursor.moveToNext()) {
        long id = cursor.getLong(idIdx);
        AddressBookContact addressBookContact = array.get(id);
        if (addressBookContact == null) {
            addressBookContact = new AddressBookContact(id, cursor.getString(nameIdx), getResources());
            array.put(id, addressBookContact);
            list.add(addressBookContact);
        }
        int type = cursor.getInt(typeIdx);
        String data = cursor.getString(dataIdx);
        String mimeType = cursor.getString(mimeTypeIdx);
        if (mimeType.equals(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
            // mimeType == ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
            addressBookContact.addEmail(type, data);
        } else {
            // mimeType == ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
            addressBookContact.addPhone(type, data);
        }
    }
    long ms = System.currentTimeMillis() - start;
    cursor.close();
    
    // done!!! show the results...
    int i = 1;
    for (AddressBookContact addressBookContact : list) {
        Log.d(TAG, "AddressBookContact #" + i++ + ": " + addressBookContact.toString(true));
    }
    final String cOn = "<b><font color='#ff9900'>";
    final String cOff = "</font></b>";
    Spanned l1 = Html.fromHtml("got " + cOn + array.size() + cOff + " contacts<br/>");
    Spanned l2 = Html.fromHtml("query took " + cOn + ms / 1000f + cOff + " s (" + cOn + ms + cOff + " ms)");
    
    Log.d(TAG, "\n\n╔══════ query execution stats ═══════" );
    Log.d(TAG, "║    " + l1);
    Log.d(TAG, "║    " + l2);
    Log.d(TAG, "╚════════════════════════════════════" );
    SpannableStringBuilder msg = new SpannableStringBuilder().append(l1).append(l2);
    
    LinearLayout ll = new LinearLayout(this);
    ll.setOrientation(LinearLayout.VERTICAL);
    TextView tv = new TextView(this);
    tv.setTextSize(20);
    tv.setBackgroundColor(0xff000033);
    tv.setPadding(24, 8, 24, 24);
    tv.setText(msg);
    ll.addView(tv);
    ListView lv = new ListView(this);
    lv.setAdapter(new ArrayAdapter<AddressBookContact>(this, android.R.layout.simple_list_item_1, list));
    ll.addView(lv);
    new AlertDialog.Builder(this).setView(ll).setPositiveButton("close", null).create().show();
    

    the helper AddressBookContact class:

    class AddressBookContact {
        private long id;
        private Resources res;
        private String name;
        private LongSparseArray<String> emails;
        private LongSparseArray<String> phones;
    
        AddressBookContact(long id, String name, Resources res) {
            this.id = id;
            this.name = name;
            this.res = res;
        }
    
        @Override
        public String toString() {
            return toString(false);
        }
    
        public String toString(boolean rich) {
            SpannableStringBuilder builder = new SpannableStringBuilder();
            if (rich) {
                builder.append("id: ").append(Long.toString(id))
                        .append(", name: ").append("\u001b[1m").append(name).append("\u001b[0m");
            } else {
                builder.append(name);
            }
    
            if (phones != null) {
                builder.append("\n\tphones: ");
                for (int i = 0; i < phones.size(); i++) {
                    int type = (int) phones.keyAt(i);
                    builder.append(ContactsContract.CommonDataKinds.Phone.getTypeLabel(res, type, ""))
                            .append(": ")
                            .append(phones.valueAt(i));
                    if (i + 1 < phones.size()) {
                        builder.append(", ");
                    }
                }
            }
    
            if (emails != null) {
                builder.append("\n\temails: ");
                for (int i = 0; i < emails.size(); i++) {
                    int type = (int) emails.keyAt(i);
                    builder.append(ContactsContract.CommonDataKinds.Email.getTypeLabel(res, type, ""))
                            .append(": ")
                            .append(emails.valueAt(i));
                    if (i + 1 < emails.size()) {
                        builder.append(", ");
                    }
                }
            }
            return builder.toString();
        }
    
        public void addEmail(int type, String address) {
            if (emails == null) {
                emails = new LongSparseArray<String>();
            }
            emails.put(type, address);
        }
    
        public void addPhone(int type, String number) {
            if (phones == null) {
                phones = new LongSparseArray<String>();
            }
            phones.put(type, number);
        }
    }