带有联系人照片的联系人列表会产生性能问题
Posted
技术标签:
【中文标题】带有联系人照片的联系人列表会产生性能问题【英文标题】:Contact List with Contact Photo creates performance issue 【发布时间】:2012-03-09 13:10:07 【问题描述】:我正在使用光标获取联系人数据并尝试将其加载到 ListView 中
我基于QuickContact APIDemos 示例(API 级别 8)开发了我的代码
这是我的代码,我做了一些修改,但我正面临性能问题 如果我删除联系人照片代码,那么性能会很好,否则它就像拖动列表一样太慢
适配器代码
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);
获取联系代码
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);
注意: 我经历了这个线程,但我们都使用不同的方法 Load contact photo in a listview performance
编辑
我已经把它写在下面的代码中,但是调用了
imageDownloader.download(contactUri,
(ImageView) cache.photoView ,
getContentResolver(),
contactId);
下载图片代码
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();
但我使用此代码的问题是几秒钟后我快速向下滚动 QuickBadgeContact 小部件正在加载,在此之前 QuickBadgeContact 的占位符中没有任何内容。
看图
有时图像会在很长一段时间后出现,请看这里
所以它的工作方式与android中的Contact ListView不一样,如果我们滚动得更快,那么默认情况下QuickBadgeContact将在那里并充当占位符,QuickBadgeContact中的图像稍后会加载,但在我的代码中,整个徽章出现在之后几秒
我只需要继续将图像加载到有照片的联系人,并让其他人保持不变,因为这是加载黑色位图,没有为用户找到图像,但我不知道该怎么做?
【问题讨论】:
【参考方案1】:保持应用响应的一个简单规则是:不要在 UI 线程上执行 I/O。您的 bindView() 实现违反了规则,因为它从数据库中读取位图。当慢速 I/O 操作完成时,主线程被阻塞。
您应该创建 AsyncTask 来加载图像。最佳实践 AFAIK 是在您的应用程序中拥有一个完全可重用的 ImageCache 类......使用这样的方法:
public static void getImage(Uri imageUri, ImageView imageView);
即你给它一个 URI 和一个 ImageView 引用,它会负责在后台线程上下载图像并在 ImageView 准备好时更新它。
除此之外还有更多...未完成的请求需要被取消,并且您应该将 ImageViews 保存在 WeakReferences 中以避免泄漏它所属的 Activity(有一篇关于此的 Android 开发者博客文章)。
【讨论】:
android-developers.blogspot.com/2010/07/… 我通过 Deriving BitmapDrawable 而不是 ColorDrawable 在一定程度上解决了这个问题,但是当我快速滚动时实际将图片加载到 QuickContactBadge 中时仍然会有一点延迟 我已经参考了您的代码,因此为此 +1,但仍在寻求明确的答案:)【参考方案2】:我认为延迟加载是你的答案。 这是使用 ListView 的 ScrollListener 实现的。
在 Activity 中它看起来像这样:
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
适配器将获得额外的代码:
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
通过采用此代码,您将获得仅填充当前可见视图的延迟加载,因此正如您所提到的,性能应该会提高。
另外一件有意义的事情是一种缓存在光标加载时检索到的照片的方法。
编辑:
上面提到的代码是从http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/view/List13.html的Android示例中借用和采用的->慢适配器
编辑 2:
我同意Reuben Scratton 将适配器的繁重工作部分(例如图像处理)放到另一个线程中。
【讨论】:
我也试过你的代码,但是当我快速滚动列表时,我可以看到它有一点延迟,所以不够流畅,你有什么解决方案吗? 要获得更流畅的解决方案,您必须在列表上实现缓存。然后延迟加载部分不会从资源或内容提供者中获取图像,而是从图像缓存中获取。【参考方案3】:我和你有同样的问题,我找到了这个解决方案。您可以从默认的Android Contacts
中查看ContactsPhotoLoader
。将mPhotoLoader.loadPhoto(contactAvatar, photoId);
放入getView
与contactAvatar
是ImageView
和photoID
是Contacts.PHOTO_ID
(根据联系方式)
【讨论】:
以上是关于带有联系人照片的联系人列表会产生性能问题的主要内容,如果未能解决你的问题,请参考以下文章