Reputation: 8435
I am fetching Contact Data using cursor and trying to load it in a ListView
I have developed my code based on QuickContact sample of APIDemos (API Level 8)
and here is my code which i have modified a bit , but i am facing the performance issue if i remove the contact photo code then performance will be fine otherwise its too slow like dragging a list
Adapter Code
private class ContactListAdapter extends ResourceCursorAdapter{
Cursor cur;
public ContactListAdapter(Context context, int layout, Cursor c) {
super(context, layout, c);
cur = c;
// TODO Auto-generated constructor stub
}
@Override
public void bindView(View view, Context arg1, Cursor arg2) {
// TODO Auto-generated method stub
final ContactListItemCache cache = (ContactListItemCache) view.getTag();
TextView nameView = cache.nameView;
QuickContactBadge photoView = cache.photoView;
cur.copyStringToBuffer(cur.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME), cache.nameBuffer);
int size = cache.nameBuffer.sizeCopied;
cache.nameView.setText(cache.nameBuffer.data, 0, size);
long contactId = cur.getLong(cur.getColumnIndex(ContactsContract.Contacts._ID));
Uri contactUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, String.valueOf(contactId));
InputStream input = ContactsContract.Contacts.openContactPhotoInputStream(getContentResolver(), contactUri);
cache.photoView.setImageBitmap(BitmapFactory.decodeStream(input));
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = super.newView(context, cursor, parent);
ContactListItemCache cache = new ContactListItemCache();
cache.nameView = (TextView) view.findViewById(R.id.name);
cache.photoView = (QuickContactBadge) view.findViewById(R.id.badge);
view.setTag(cache);
return view;
}
}
final static class ContactListItemCache {
public TextView nameView;
public QuickContactBadge photoView;
public CharArrayBuffer nameBuffer = new CharArrayBuffer(128);
}
}
Fetch Contact Code
private Cursor getContacts()
{
// Run query
Uri uri = ContactsContract.Contacts.CONTENT_URI;
String[] projection = new String[] {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.LOOKUP_KEY
};
String selection = ContactsContract.Contacts.IN_VISIBLE_GROUP + " = '" +
(mShowInvisible ? "0" : "1") + "'";
String[] selectionArgs = null;
String sortOrder = ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
return managedQuery(uri, projection, selection, selectionArgs, sortOrder);
}
Note: i went through this thread but we both are using different approach Load contact photo in a listview performance
EDIT
I have write it down following code but calling
imageDownloader.download(contactUri,
(ImageView) cache.photoView ,
getContentResolver(),
contactId);
Download Image code
Bitmap downloadBitmap(Uri url) {
final int IO_BUFFER_SIZE = 4 * 1024;
InputStream inputStream = null;
try {
inputStream = ContactsContract.Contacts.openContactPhotoInputStream(resolver, url);
if(inputStream ==null){
return BitmapFactory.decodeStream(inputStream);
}else
{
return BitmapFactory.decodeStream(new FlushedInputStream(inputStream));
}
// return BitmapFactory.decodeStream(inputStream);
// Bug on slow connections, fixed in future release.
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
But the problem that i am using this code and i scroll down fast , after few second the QuickBadgeContact widget is loading , before that nothing appears in that placeholder of QuickBadgeContact.
see the image
sometimes images appears after quite long , look here
so its not working the way Contact ListView in android work , if we scroll much faster then aslo QuickBadgeContact will there by default and act as a placeholder , image inside the QuickBadgeContact loads later on but in my code the whole badge appears after a few seconds
I just need to keep loading images to the contact which has a photo , and let the other untouched as this is loading BLACK bitmap where no image is found for user but i dont know how would i do tht ?
Upvotes: 3
Views: 2603
Reputation: 7532
I had same problem with you and i found this solution. You can have a look at ContactsPhotoLoader
from default Android Contacts
. Put mPhotoLoader.loadPhoto(contactAvatar, photoId);
in getView
with contactAvatar
is ImageView
and photoID
is Contacts.PHOTO_ID
(according with contact)
Upvotes: 0
Reputation: 161
I think lazy loading is the answer for you. This is implemented using a ScrollListener for your ListView.
In the Activity it looks something like that:
public class ListViewActivity extends Activity {
public boolean mBusy = false;
// even more initializing
@Override
public void onCreate(Bundle savedInstanceState) {
// The code to build the activity
ListView listView = findViewById(R.id.ListView);
listView.setOnScrollListener(listViewOnScrollListener);
listView.setAdapter(new ContactListAdapter(this, R.layout.whatsoever, null)
// it is important to give the Adapter 'this' as context because we need the mBusy boolean
// we should populate the adapter later in onCreate...
// even more code
}
private AbsListView.OnScrollListener listViewOnScrollListener = new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView absListView, int scrollState) {
if (absListView instanceof ListView) {
ListView listView = (ListView)absListView;
switch (scrollState) {
case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
mBusy = false;
// Find out which elements of the ListView are visible to the user
int first = listView.getFirstVisiblePosition();
int childMargin = 0;
if (first < listView.getHeaderViewsCount()) {
first = listView.getHeaderViewsCount();
childMargin = listView.getHeaderViewsCount();
}
int last = listView.getLastVisiblePosition();
if (last > (listView.getCount() - listView.getFooterViewsCount() - 1)) {
last = listView.getCount() - listView.getFooterViewsCount() - 1;
}
for (int i = first; i <= last; i++) {
// This is for only populating the visible items of the list
// Action to perform the loading of your contactPhoto
// or displaying it
// maybe you call a method from the adapter to populate the views
adapter.loadImageBadges(listView.getChildAt(i - first + childMargin)),i);
}
break;
case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
mBusy = true;
break;
case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
mBusy = true;
break;
}
}
}
@Override
public void onScroll(AbsListView absListView, int i, int i1, int i2) {
// Do something when the list is scrolling
}
};
// The rest of the activity code
}
The adapter will get additional code:
private class ContactListAdapter extends ResourceCursorAdapter {
public ContactListAdapter(Context context, int layout, Cursor c) {
super(context, layout, c);
cur = c;
}
@Override
public void bindView(View view, Context arg1, Cursor arg2) {
// TODO Auto-generated method stub
final ContactListItemCache cache = (ContactListItemCache) view.getTag();
TextView nameView = cache.nameView;
cur.copyStringToBuffer(cur.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME), cache.nameBuffer);
int size = cache.nameBuffer.sizeCopied;
cache.nameView.setText(cache.nameBuffer.data, 0, size);
if (!context.mBusy) {
// Do the expensive things only if the list is not scrolling...
loadImageBadges(convertView, arg2.getPosition())
} else {
// initialize with dummy data
}
}
public void ImageBadges(View view, int position) {
final ContactListItemCache cache = (ContactListItemCache) view.getTag();
QuickContactBadge photoView = cache.photoView;
long contactId = cur.getLong(cur.getColumnIndex(ContactsContract.Contacts._ID));
Uri contactUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, String.valueOf(contactId));
InputStream input = ContactsContract.Contacts.openContactPhotoInputStream(getContentResolver(), contactUri);
cache.photoView.setImageBitmap(BitmapFactory.decodeStream(input));
}
// The rest of the adapter code
}
By adopting this code you get lazy loading which only populates the currently visible views, so performance should increase just as you mentioned.
One additional thing which would make also sense is a way to cache the photos retrieved when the cursor loads it.
Edit:
The code mentioned above is borrowed and adopted out of the Android Samples at http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/view/List13.html -> Slow adapter
Edit 2:
I agree to Reuben Scratton for putting the hard working part of the adapter (such as Image processing) into another thread.
Upvotes: 3
Reputation: 38717
There's a simple rule to keeping apps responsive: Don't do I/O on the UI thread. Your bindView() implementation breaks the rule because it reads bitmaps from a database. The main thread is blocked while slow I/O operations complete.
You should be creating AsyncTasks to load the images. Best practice AFAIK is to have a totally reusable ImageCache class in your app... something with a method like this:
public static void getImage(Uri imageUri, ImageView imageView);
I.e. you give it a URI and an ImageView reference, and it will take care of downloading the image on a background thread and updating the ImageView when it's ready.
There's slightly more to it than that... outstanding requests need to be cancelable, and you should hold the ImageViews in WeakReferences to avoid leaking the Activity it belongs to (there's a Android Developer's blog article about this).
Upvotes: 3