You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

图片下载完成后如何更新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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 19:10:38