0

I'm working in an application that read phone contacts and use them in my application (Call history, Favorite contacts and All contacts).

My UI consist of tab host control and user can swap between them, as I want my data to be shared across all my activities and also to be saved in only one place.

So I have created a singleton class called data controller, and when I open the application I show loading screen until all data loaded.

The problem now that user is complaining because of waiting a lot of time about (1 minute) every time they open the application when he has a very large amount of contacts, so how can I optimize my code in a good way?

EDIT

This is the method that I'm using to get all contacts:

public static ArrayList<ContactInfo> getAllContactWithNumberAndNameAndPhoto(
            Context context, boolean starred) {

        ArrayList<ContactInfo> retList = new ArrayList<ContactInfo>();

        ContentResolver cr = context.getContentResolver();

        Cursor cur = null;
        if (starred == true) {
            cur = cr.query(ContactsContract.Contacts.CONTENT_URI, null,
                    "starred=?", new String[] { "1" }, null);
        } else {

            cur = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null,
                    null, null);
        }
        if (cur.getCount() > 0) {
            while (cur.moveToNext()) {

                ContactInfo item = new ContactInfo();
                String id = cur.getString(cur
                        .getColumnIndex(ContactsContract.Contacts._ID));
                String name = cur
                        .getString(cur
                                .getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
                Uri photo = PhoneUtils.getPhotoUriFromID(context, id);
                String starredValue = cur.getString(cur
                        .getColumnIndex(ContactsContract.Contacts.STARRED));
                boolean isFav = false;
                if (starredValue.equals("1"))
                    isFav = true;

                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);
                    while (pCur.moveToNext()) {

                        String phoneNo = pCur
                                .getString(pCur
                                        .getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                        item.addPhone(removeCharactersFromPhoneNumber(phoneNo));
                    }
                    pCur.close();

                    if (photo != null) {

                        item.setPhoto(photo.toString());
                    }

                    item.setName(name);
                    item.setFavorite(isFav);
                    item.setRecent(false);

                    retList.add(item);
                }
            }
            cur.close();
        }

        return retList;
    }

Please let me know if I can optimize this method.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Amira Elsayed Ismail
  • 9,216
  • 30
  • 92
  • 175
  • 1 minute!!? maybe your query is not efficient ( just get columns that you want), but if it is normal ( I don't think so ) you can get limit data (10 - 20) and in background get more and more. – Shayan Pourvatan Jun 22 '14 at 10:10
  • @shayanpourvatan : I have updated the post with the method that i use to get contact data, please review it and let me know if there is any problem. – Amira Elsayed Ismail Jun 22 '14 at 10:15
  • all thing is good instead of `PhoneUtils.getPhotoUriFromID(context, id);`, this takes too long time, move this to one thread and get image in thread, show default pic to user and call `notifyDataSetChanged` for refreshing picture – Shayan Pourvatan Jun 22 '14 at 10:18
  • I see you have posted code not and profiled / found the src of the bottleneck. @shayanpourvatan is right, you should load the views asyncrounously - you could use something like https://github.com/square/picasso in your view hierarchy – Dori Jun 22 '14 at 11:08
  • @shayanpourvatan : OK this is a good point, but I'm not loading the photo itself here, I just get the URI, so is that make all this problem ??? and how can I solve it. – Amira Elsayed Ismail Jun 22 '14 at 11:26
  • @shayanpourvatan : when I remove the image part from the code and calculate the time spent for loading data it was (17929) milliseconds which mean (18) seconds. – Amira Elsayed Ismail Jun 22 '14 at 11:31
  • get limit data, like paging, get 100 data and in scroll listener get more data. – Shayan Pourvatan Jun 22 '14 at 11:43
  • set image form URI is a heavy operation, this take too long, use `asyncTask` and save Bitmap image on one list or anywhere else ( see http://stackoverflow.com/questions/3879992/get-bitmap-from-an-uri-android ) – Shayan Pourvatan Jun 22 '14 at 11:46
  • one thing else, you just want Name and id from `ContactsContract.Contacts.CONTENT_URI` but you get all columns ( you set null as selection ) so just get two columns. – Shayan Pourvatan Jun 22 '14 at 11:48
  • @shayanpourvatan : do you know how can I do the paging and update my UI while I'm loading, which means that, I will load 50 record by 50 record in background thread and update my list, can you provide me with a simple tutorial ? – Amira Elsayed Ismail Jun 22 '14 at 11:53
  • and the last thing that i know I right one app that involved with contact data, I have tried to get data in background thread, I've used `asyncTask` and `Handler`, and `Handler` is faster than `asyncTask`, – Shayan Pourvatan Jun 22 '14 at 11:53
  • I'm using asyncTask to load data now – Amira Elsayed Ismail Jun 22 '14 at 11:55
  • While I'm searching I found something called CursorLoader, so do you think this class can help me improving my contacts loading ? – Amira Elsayed Ismail Jun 22 '14 at 11:56
  • unfortunately no, I can't, but scenarios is get data in background (50) show to user, in `getView` of adapter check position if position equal (length - 30 that in first case is equal to 20) then get more data in back thread then in UI thread add to your list and call notifyDatasetChanged();. be careful you can't add to your list in back thread, in some phone this get exception – Shayan Pourvatan Jun 22 '14 at 11:57
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/56061/discussion-between-shayan-pourvatan-and-amira-elsayed-ismail). – Shayan Pourvatan Jun 22 '14 at 11:57

2 Answers2

0

Im surprised it takes that long to load the contacts from the device.

Have you profiled the app to see where the time is actually spent? Something seems wrong here.

If it truly takes that long to load from the system providers (due to the OS) you could cache the results (i.e. put in your own SQL db) so you can load quickly on each app visit (< 1 sec) and refresh from device in the background.

Dori
  • 18,283
  • 17
  • 74
  • 116
  • good idea I will try to profile the data and know the time that exactly taken to load all data, and get back to you – Amira Elsayed Ismail Jun 22 '14 at 10:16
  • @AmiraElsayedIsmail it's not good way, because you must sync contact data with your `sql` table every time, maybe one contact has been deleted or one phone number added or image changed or ...., – Shayan Pourvatan Jun 22 '14 at 10:20
  • ...this is a worst case scenario where the system contact providers are introducing the long delay - which is unlikely IMHO. Its worth noting this is the approach you would likely use when accessing certain kinds remote data i.e. data which has a long loading time, so the user can still interact with the data while its being updated in the background - this is actually similar to how the system contacts works on Android as the main data src is on google servers - the differnce is that this will be updated periodically in the background as opposed to due to a user prompt i.e. not lazy-loading – Dori Jun 22 '14 at 11:05
  • I have added a method to insert bulk of contacts to my testing device, now I have 950 contact and in my application I have added a timer to calculate the exact time that taken while loading my contacts and it was (39601) millisecond about 40 second, it is very long period, I don't want to read data in my own database, as I want to be always sych to contact for any change that may happen in my original contact list. so can any one please advise ?? – Amira Elsayed Ismail Jun 22 '14 at 11:22
  • It would be much more useful to profile the methods being called as opposed to the complete time taken - as you already know the total time is too long - profiling the method calls will tell you _WHY_ the complete time is long. See [TraceView](http://developer.android.com/tools/debugging/debugging-tracing.html). Its likely its due to you performing an extra query inside your while loop. Beware that some devices have a min time ~800ms for a single query (im thinking of the S1 here)... – Dori Jun 22 '14 at 14:57
0

I guess that the bottleneck of your method is photo loading. Try to load everything except photos, and then show your activity, concurrently loading photos.

Also you can try to create your own app table that contains just the data you need. So you'll do less selects while loading contacts. But you would have to synchronize your table. You can do so concurrently.

Pavel
  • 4,912
  • 7
  • 49
  • 69