图片下载完成后如何更新RecyclerView指定位置的图片?
一、解决notifyItemChanged(position)无效的问题
1. 先更新数据源,再触发刷新
调用notifyItemChanged前,必须确保对应位置的数据源已经更新,否则刷新后还是旧数据:
imageLoader.loadImage(imageUrl, new ImageLoader.Callback() { @Override public void onSuccess(Bitmap bitmap) { // 第一步:更新数据源中的图片数据 dataList.get(position).setBitmap(bitmap); // 第二步:通知RecyclerView刷新对应位置 recyclerView.post(() -> adapter.notifyItemChanged(position)); } @Override public void onFailure() { // 失败逻辑处理 } });
2. 确保在主线程执行通知操作
ImageLoader的下载回调通常在子线程运行,RecyclerView的刷新方法必须在主线程调用,可通过以下方式切换:
// 方式1:利用RecyclerView的post方法(推荐,自动关联主线程) recyclerView.post(() -> adapter.notifyItemChanged(position)); // 方式2:用Handler切换到主线程 new Handler(Looper.getMainLooper()).post(() -> adapter.notifyItemChanged(position));
3. 绑定稳定ID避免position失效
如果列表存在增删操作,外部保存的position可能过期。给Adapter设置稳定ID,让RecyclerView通过唯一标识定位item:
public class ImageAdapter extends RecyclerView.Adapter<ImageAdapter.ViewHolder> { @Override public long getItemId(int position) { // 返回item的唯一标识,比如图片URL的哈希值或数据库ID return dataList.get(position).getImageUrl().hashCode(); } @Override public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); setHasStableIds(true); // 启用稳定ID功能 } }
4. 使用ViewHolder的实时位置
如果在ViewHolder内部触发下载,直接用getAdapterPosition()获取当前有效位置,避免外部position变量过期:
public class ViewHolder extends RecyclerView.ViewHolder { ImageView imageView; public ViewHolder(@NonNull View itemView) { super(itemView); imageView = itemView.findViewById(R.id.image_view); } public void bind(ImageItem item) { imageLoader.loadImage(item.getImageUrl(), new ImageLoader.Callback() { @Override public void onSuccess(Bitmap bitmap) { item.setBitmap(bitmap); int currentPos = getAdapterPosition(); // 确保item未被移除再刷新 if (currentPos != RecyclerView.NO_POSITION) { recyclerView.post(() -> adapter.notifyItemChanged(currentPos)); } } @Override public void onFailure() {} }); } }
二、ImageLoader优化建议
1. 内存缓存优化
用LruCache实现内存缓存,优先从内存读取图片,避免重复下载:
private LruCache<String, Bitmap> memoryCache; public ImageLoader() { // 取应用最大可用内存的1/8作为缓存上限 int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); int cacheSize = maxMemory / 8; memoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { // 返回Bitmap占用的内存大小(单位KB) return bitmap.getByteCount() / 1024; } }; } // 缓存Bitmap public void addToMemoryCache(String key, Bitmap bitmap) { if (getFromMemoryCache(key) == null) { memoryCache.put(key, bitmap); } } // 读取内存缓存 public Bitmap getFromMemoryCache(String key) { return memoryCache.get(key); }
2. 磁盘缓存优化
用DiskLruCache实现磁盘缓存,将下载的图片保存到本地,下次加载直接读取:
private DiskLruCache diskLruCache; public void initDiskCache(Context context) { try { File cacheDir = new File(context.getCacheDir(), "image_cache"); if (!cacheDir.exists()) cacheDir.mkdirs(); // 设置磁盘缓存大小为10MB diskLruCache = DiskLruCache.open(cacheDir, 1, 1, 10 * 1024 * 1024); } catch (IOException e) { e.printStackTrace(); } } // 读取磁盘缓存 public Bitmap getFromDiskCache(String key) { try { DiskLruCache.Snapshot snapshot = diskLruCache.get(key); if (snapshot != null) { return BitmapFactory.decodeStream(snapshot.getInputStream(0)); } } catch (IOException e) { e.printStackTrace(); } return null; } // 写入磁盘缓存 public void putToDiskCache(String key, Bitmap bitmap) { try { DiskLruCache.Editor editor = diskLruCache.edit(key); if (editor != null) { bitmap.compress(Bitmap.CompressFormat.JPEG, 80, editor.newOutputStream(0)); editor.commit(); diskLruCache.flush(); } } catch (IOException e) { e.printStackTrace(); } }
3. 线程池控制并发
用自定义线程池限制下载并发数,避免过多线程占用资源:
private ExecutorService executorService; public ImageLoader() { int corePoolSize = Runtime.getRuntime().availableProcessors(); executorService = new ThreadPoolExecutor( corePoolSize, corePoolSize * 2, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>() ); } // 提交下载任务时,按「内存缓存→磁盘缓存→网络下载」的顺序加载 public void loadImage(String url, Callback callback) { executorService.submit(() -> { Bitmap bitmap = getFromMemoryCache(url); if (bitmap == null) { bitmap = getFromDiskCache(url); if (bitmap == null) { bitmap = downloadBitmap(url); if (bitmap != null) { addToMemoryCache(url, bitmap); putToDiskCache(url, bitmap); } } else { addToMemoryCache(url, bitmap); // 磁盘缓存命中后同步到内存 } } // 回调切换到主线程 new Handler(Looper.getMainLooper()).post(() -> { if (bitmap != null) callback.onSuccess(bitmap); else callback.onFailure(); }); }); }
4. 按ImageView尺寸压缩图片
根据目标ImageView的宽高压缩图片,避免加载过大Bitmap导致内存溢出:
public Bitmap downloadBitmap(String url, int reqWidth, int reqHeight) { try { HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); conn.connect(); // 先获取图片原始尺寸,不加载完整Bitmap InputStream is = conn.getInputStream(); BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeStream(is, null, options); is.close(); // 计算缩放比例 options.inSampleSize = calculateSampleSize(options, reqWidth, reqHeight); options.inJustDecodeBounds = false; // 加载压缩后的Bitmap is = conn.getInputStream(); Bitmap bitmap = BitmapFactory.decodeStream(is, null, options); is.close(); conn.disconnect(); return bitmap; } catch (IOException e) { e.printStackTrace(); } return null; } private int calculateSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { final int width = options.outWidth; final int height = options.outHeight; int sampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; while ((halfHeight / sampleSize) >= reqHeight && (halfWidth / sampleSize) >= reqWidth) { sampleSize *= 2; } } return sampleSize; }
5. 取消无效下载任务
当item滑出屏幕时,取消对应的下载任务,避免资源浪费:
private Map<String, Future<?>> taskMap = new HashMap<>(); public void loadImage(String url, Callback callback) { // 先取消同名的未完成任务 if (taskMap.containsKey(url)) { Future<?> future = taskMap.get(url); if (!future.isDone()) future.cancel(true); taskMap.remove(url); } Future<?> future = executorService.submit(() -> { // 下载逻辑... taskMap.remove(url); // 任务完成后移除记录 }); taskMap.put(url, future); } // 在Adapter中重写该方法,回收item时取消任务 @Override public void onViewRecycled(@NonNull ViewHolder holder) { super.onViewRecycled(holder); String url = dataList.get(holder.getAdapterPosition()).getImageUrl(); if (taskMap.containsKey(url)) { Future<?> future = taskMap.get(url); if (!future.isDone()) future.cancel(true); taskMap.remove(url); } }
内容的提问来源于stack exchange,提问作者Code9
相关产品推荐
相关产品推荐

