0

I'm tring to get all the contact data at one. I need : * First name * Last name * Phone numberS * Version etc.

for that i'm doing a query on the whole contacts and then, for each contact id, i get the relevant data.

the problem is that it takes a lot of time, more than 10 sec for ~400 contacts. (Running on Samsung i9300 with 4.4.2)

I DONT need to show anything to the user, i'm just syncing the contacts to with my server in the background, so i dont have any UI issues... just iterate over the contacts, see how needs an update and add him to my ArrayList, then POST it to the server.

public static void getAllContactsFromDevice() {

        long startTime = System.currentTimeMillis();
        Log.d(TAG, "startTime: " + startTime);

        //  Get all contacts by cursor
        ContentResolver cr = getContext().getContentResolver();
        Cursor cursor = cr.query(ContactsContract.Contacts.CONTENT_URI, null,
                null, null, null);
        if (cursor.moveToFirst() && cursor.getCount() > 0) {
            while (cursor.isAfterLast() == false) {
                // get contact id on the device DB.
                String contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));

                String contactNumber = null;
                //  Get all phone numbers.
                Cursor phones = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
                        ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + contactId, null, null);
                // if the user has numbers
                // get just the first one for now
                if (phones.moveToNext())
                    contactNumber = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                // close the cursor to prevent leak
                phones.close();

                // if we dont have a phone number, continue to next contact.
                if (contactNumber == null) {
                    cursor.moveToNext();
                    continue;
                }

                // get first and last name by contact id
                String[] projection = new String[]{ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME};
                String where = ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?";
                String[] whereParameters = new String[]{contactId, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE};
                Cursor nameCur = cr.query(ContactsContract.Data.CONTENT_URI, projection, where, whereParameters, null);

                String given = null;
                String family = null;
                try {
                    if (nameCur.getCount() > 0) {
                        nameCur.moveToFirst();
                        given = nameCur.getString(nameCur.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME));
                        family = nameCur.getString(nameCur.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME));
                        nameCur.close();
                    }
                    // if there is no name, continue to next contact.
                    else {
                        nameCur.close();
                        cursor.moveToNext();
                        continue;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    nameCur.close();
                }


                if (given == null || given == "null")
                    given = "";
                if (family == null || family == "null")
                    family = "";

                String[] mProjection = new String[]{ContactsContract.RawContacts.VERSION};
                String[] mWhereParameters = new String[]{contactId};
                String mWhere = ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID + " = ? ";
                Cursor mCursor = cr.query(ContactsContract.RawContacts.CONTENT_URI, mProjection, mWhere, mWhereParameters, null);

                int contactVersion = 0;
                try {
                    if (mCursor.getCount() > 0) {
                        mCursor.moveToFirst();
                        contactVersion = mCursor.getInt(mCursor.getColumnIndex(ContactsContract.RawContacts.VERSION));
                        mCursor.close();
                    } else {
                        mCursor.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    mCursor.close();
                }
                cursor.moveToNext();

            }

        }
        cursor.close();
        long endTime = System.currentTimeMillis();
        Log.d(TAG, "endTime: " + endTime);
        Log.d(TAG, "total time: " + (endTime - startTime));
    }

total time: 18280 ms

i looked HERE developer.android.com but didnt find anything useful.

also this answer did not helped much.

Community
  • 1
  • 1
Shahar
  • 3,692
  • 4
  • 25
  • 47
  • check this link http://stackoverflow.com/questions/11991743/fetching-contact-detail-take-a-lot-of-time-in-android – Deniz Jan 27 '14 at 12:11
  • Nope, does not help, i use it to check which contacts should be sync with my server contacts. i do it on another thread in background... – Shahar Jan 27 '14 at 12:23
  • Is pagination is possible? sync a section of contacts first then on scrolling sync next..... It will reduce the time – Deniz Jan 27 '14 at 12:29
  • I'm not showing anything, just taking all the contact and sync them to the server in background. – Shahar Jan 27 '14 at 12:49

1 Answers1

0

The problem is that for every contact you perform few queries: to phone numbers table, to structured names table, etc. You want to batch them all and perform single query to main contact list, single query to phone numbers list, etc.

On Genymotion with ~400 contacts with full data this approach gives 30x better performance (from 2.8s to 100ms). Here is the code I used:

private static <TLeft, TRight> Function<Pair<TLeft, TRight>, TLeft> getExtractLeftFunction() {
  return new Function<Pair<TLeft, TRight>, TLeft>() {
    @Override
    public TLeft apply(Pair<TLeft, TRight> input) {
      return input.first;
    }
  };
}

private static <TLeft, TRight> Map<TLeft, TRight> toMap(Iterable<Pair<TLeft, TRight>> pairs) {
  Map<TLeft, TRight> result = Maps.newHashMap();

  for (Pair<TLeft, TRight> pair : pairs) {
    result.put(pair.first, pair.second);
  }

  return result;
}

private static class Name {
  public final String mGivenName;
  public final String mFamilyName;

  private Name(String givenName, String familyName) {
    mGivenName = givenName;
    mFamilyName = familyName;
  }
}

public void getAllContactsFromDevice() {

  long startTime = System.currentTimeMillis();
  Log.d(TAG, "startTime: " + startTime);

  //  Get all contacts by cursor
  ContentResolver cr = getContentResolver();

  ImmutableList<Long> contactIds = ProviderAction
      .query(Contacts.CONTENT_URI)
      .projection(Contacts._ID)
      .perform(getContentResolver())
      .toFluentIterable(SingleRowTransforms.getColumn(Contacts._ID).asLong())
      .toImmutableList();

  Map<Long, String> phonesMap = toMap(
      ProviderAction.query(Phone.CONTENT_URI)
          .projection(
              Phone.CONTACT_ID,
              Phone.NUMBER
          )
          .whereIn(Phone.CONTACT_ID, contactIds)
          .perform(getContentResolver())
          .toFluentIterable(getToPairFunction(
              SingleRowTransforms.getColumn(Phone.CONTACT_ID).asLong(),
              SingleRowTransforms.getColumn(Phone.NUMBER).asString())
          )
  );

  Map<Long, String> contactsWithPhones = Maps.filterValues(phonesMap, Predicates.notNull());

  Map<Long, Name> contactNamesMap = toMap(ProviderAction
      .query(Data.CONTENT_URI)
      .projection(
          StructuredName.CONTACT_ID,
          StructuredName.FAMILY_NAME,
          StructuredName.GIVEN_NAME
      )
      .whereIn(StructuredName.CONTACT_ID, contactsWithPhones.keySet())
      .perform(getContentResolver())
      .toFluentIterable(getToPairFunction(
          SingleRowTransforms.getColumn(Phone.CONTACT_ID).asLong(),
          new Function<Cursor, Name>() {

            private Function<Cursor, String> mFamilyNameGetter = SingleRowTransforms.getColumn(StructuredName.FAMILY_NAME).asString();
            private Function<Cursor, String> mGivenNameGetter = SingleRowTransforms.getColumn(StructuredName.GIVEN_NAME).asString();

            @Override
            public Name apply(Cursor input) {
              return new Name(
                  mGivenNameGetter.apply(input),
                  mFamilyNameGetter.apply(input)
              );
            }
          }
      ))
  );

  Map<Long, Integer> versions = toMap(ProviderAction.query(RawContacts.CONTENT_URI)
      .projection(
          StructuredName.CONTACT_ID,
          RawContacts.VERSION
      )
      .whereIn(StructuredName.CONTACT_ID, contactNamesMap.keySet())
      .perform(getContentResolver())
      .toFluentIterable(getToPairFunction(
          SingleRowTransforms.getColumn(StructuredName.CONTACT_ID).asLong(),
          SingleRowTransforms.getColumn(RawContacts.VERSION).asInteger())
      )
  );

  long endTime = System.currentTimeMillis();
  Log.d(TAG, "endTime: " + endTime);
  Log.d(TAG, "total time: " + (endTime - startTime));
}

I'm using android-db-commons (disclaimer: I'm co-author) to perform all the queries and process the output. The library doesn't support putting the query results into Map (yet!), which is what you want to do here, so the above code is a bit messy, but it still beats juggling multiple cursors IMO.

chalup
  • 8,358
  • 3
  • 33
  • 38