ListView滚动时收藏夹列表图片消失问题求助
ListView滚动时收藏夹列表图片消失问题求助
你好,我来帮你分析下这个问题的核心原因和对应的解决办法:
问题根源
你遇到的图片消失问题,核心原因是InputStream是一次性读取的流对象:
- 你在加载收藏夹时,把联系人头像的InputStream存在了
customListImg数组里,第一次调用BitmapFactory.decodeStream(imageId[position])时,流的读取指针会移动到末尾; - 当ListView滚动复用Item视图时,再次调用这个方法,流已经没有数据可以读取了,自然解码不出Bitmap,所以图片就消失了;
- 而通话记录的图片是用
setImageResource加载的本地资源,每次调用都会重新从资源文件读取,所以没有这个问题。
另外你的Adapter的getView方法没有实现正确的ViewHolder复用机制,每次都inflate新的View,不仅效率低,也会让这个流的问题更明显。
解决方案
这里给你两个可行的解决思路,推荐优先用第一种或者第二种优化后的方案:
方案1:提前将InputStream转成Bitmap缓存
直接在加载收藏夹数据时,把InputStream解码成Bitmap存储,避免复用View时流已经被读完的问题:
- 先修改收藏夹的图片列表类型:
// 把InputStream的列表改成Bitmap列表 ArrayList<Bitmap> customListImg = new ArrayList<>();
- 加载收藏夹时,直接解码并缓存Bitmap,同时关闭流避免资源泄漏:
InputStream thumbnailInputStream = openPhoto(ContactId); if (thumbnailInputStream != null) { Bitmap bitmap = BitmapFactory.decodeStream(thumbnailInputStream); customListImg.add(bitmap); // 记得关闭流 try { thumbnailInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } else { // 如果获取不到头像,添加默认头像 customListImg.add(BitmapFactory.decodeResource(getResources(), R.drawable.default_avatar)); }
- 修改Adapter的对应代码:
// 把InputStream[]改成Bitmap[] private final Bitmap[] imageId; public CustomList2 (Activity context, String[] web, Bitmap[] imageId) { super(context, R.layout.callog_layout, web); this.context = context; this.web = web; this.imageId = imageId; } @Override public View getView(int position, View view, ViewGroup parent) { // 实现ViewHolder复用,提升ListView性能 ViewHolder holder; if (view == null) { LayoutInflater inflater = context.getLayoutInflater(); view = inflater.inflate(R.layout.callog_layout, parent, false); holder = new ViewHolder(); holder.txtTitle = view.findViewById(R.id.txt); holder.imageView = view.findViewById(R.id.img); view.setTag(holder); } else { holder = (ViewHolder) view.getTag(); } holder.txtTitle.setText(web[position]); holder.imageView.setImageBitmap(imageId[position]); return view; } // 定义ViewHolder类缓存控件 static class ViewHolder { TextView txtTitle; ImageView imageView; }
方案2:缓存ContactId,每次getView时重新加载头像
如果担心Bitmap占用内存过多,可以只缓存联系人的ID和名称,每次复用View时根据ID重新获取头像流并解码,同时实现ViewHolder复用:
- 先定义一个数据类来存储收藏夹联系人的信息:
public class FavoriteContact { public String nameWithPhone; public long contactId; public FavoriteContact(String nameWithPhone, long contactId) { this.nameWithPhone = nameWithPhone; this.contactId = contactId; } }
- 加载收藏夹时,只存储必要的信息:
ArrayList<FavoriteContact> favoriteContacts = new ArrayList<>(); // 原有查询Cursor的代码... String name = cursor.getString(1) + phones; long contactId = Long.parseLong(id); favoriteContacts.add(new FavoriteContact(name, contactId)); // 不再需要存储InputStream或Bitmap
- 修改Adapter为基于
FavoriteContact的适配,同时实现ViewHolder:
public class CustomList2 extends ArrayAdapter<FavoriteContact> { private final Activity context; public CustomList2(Activity context, ArrayList<FavoriteContact> contacts) { super(context, R.layout.callog_layout, contacts); this.context = context; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { LayoutInflater inflater = context.getLayoutInflater(); convertView = inflater.inflate(R.layout.callog_layout, parent, false); holder = new ViewHolder(); holder.txtTitle = convertView.findViewById(R.id.txt); holder.imageView = convertView.findViewById(R.id.img); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } FavoriteContact contact = getItem(position); holder.txtTitle.setText(contact.nameWithPhone); // 每次复用View时,根据contactId重新获取头像 InputStream inputStream = ((MainActivity)context).openPhoto(contact.contactId); if (inputStream != null) { Bitmap bitmap = BitmapFactory.decodeStream(inputStream); holder.imageView.setImageBitmap(bitmap); try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } else { holder.imageView.setImageResource(R.drawable.default_avatar); } return convertView; } static class ViewHolder { TextView txtTitle; ImageView imageView; } }
- 最后设置Adapter的代码改成:
CustomList2 adapter = new CustomList2(MainActivity.this, favoriteContacts); lvFavourites.setAdapter(adapter);
额外注意事项
- 不管用哪种方案,都要记得关闭InputStream,避免出现资源泄漏的问题;
- 如果联系人头像比较大,建议在子线程中解码Bitmap,避免阻塞UI线程(可以用Coroutine、AsyncTask或者HandlerThread实现);
- 可以用
LruCache来缓存已经加载过的Bitmap,减少重复解码的开销,提升滚动流畅度。
内容来源于stack exchange
相关产品推荐
相关产品推荐

