如何用Picasso实现图片缓存解决RecyclerView重复加载问题?
嘿,你碰到的这个图片重复加载、返回页面重新加载的糟心问题,其实Picasso本身就自带完善的两级缓存机制,完全能搞定这类性能痛点,不用额外造轮子!下面给你拆解清楚怎么用:
一、Picasso默认就支持缓存,先搞懂它的逻辑
Picasso默认会自动做两级缓存:
- 内存缓存:用LruCache实现,优先从内存取图,速度最快,适合当前会话内的重复加载;
- 磁盘缓存:默认依托OkHttp的缓存实现(如果你的Picasso依赖了OkHttp的话),会把图片存在应用的缓存目录,就算退出应用再进来,只要缓存没过期/被清理,就能直接从磁盘读,不用重新下载。
那为什么你会遇到重复加载?大概率是缓存被意外禁用,或者RecyclerView的复用没配合好,先排查这两点!
二、确保缓存正常工作的关键配置
1. 别误禁用缓存
检查你的代码里有没有加这些会跳过缓存的配置,正常加载时千万别用:
// 跳过内存缓存,调试场景偶尔用,正式环境禁用 .memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE) // 跳过磁盘缓存,同样只适合调试 .networkPolicy(NetworkPolicy.NO_CACHE)
正常加载图片就用最基础的写法,Picasso会自动走缓存逻辑:
Picasso.get().load(imageUrl).into(yourImageView);
2. 自定义缓存大小(按需调整)
默认的内存缓存是根据应用可用内存自动分配的,磁盘缓存也有默认大小,但你可以根据自己的需求改:
自定义内存缓存
// 设置50MB的内存缓存 LruCache memoryCache = new LruCache(50 * 1024 * 1024); Picasso customPicasso = new Picasso.Builder(context) .memoryCache(memoryCache) .build(); // 设置为全局单例,整个应用用同一个Picasso实例 Picasso.setSingletonInstance(customPicasso);
自定义磁盘缓存(需要依赖OkHttp)
// 设置200MB的磁盘缓存,存在应用专属缓存目录 Cache okHttpCache = new Cache(new File(context.getCacheDir(), "picasso-disk-cache"), 200 * 1024 * 1024); OkHttpClient okHttpClient = new OkHttpClient.Builder() .cache(okHttpCache) .build(); Picasso customPicasso = new Picasso.Builder(context) .downloader(new OkHttp3Downloader(okHttpClient)) .build(); Picasso.setSingletonInstance(customPicasso);
三、RecyclerView配合Picasso的优化技巧
光有缓存还不够,RecyclerView的复用特性如果没处理好,也会出现重复加载、图片错位的问题:
绑定ViewHolder时先取消旧请求:
在onBindViewHolder里,先给ImageView取消之前的加载请求,避免复用的View加载旧图片:@Override public void onBindViewHolder(MyViewHolder holder, int position) { String imageUrl = imageList.get(position); // 先取消当前ImageView的所有未完成请求 Picasso.get().cancelRequest(holder.imageView); // 再加载新图片 Picasso.get().load(imageUrl).into(holder.imageView); }滚动时暂停加载,停止后恢复:
快速滚动列表时,暂停图片加载能减少不必要的网络请求和内存消耗,滚动停止后再恢复:recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); Picasso picasso = Picasso.get(); if (newState == RecyclerView.SCROLL_STATE_IDLE) { // 停止滚动,恢复加载 picasso.resumeTag(context); } else { // 正在滚动,暂停加载 picasso.pauseTag(context); } } });记得加载图片时给请求打上Tag:
Picasso.get().load(imageUrl).tag(context).into(holder.imageView);
四、模拟Instagram式的缓存体验
Instagram的缓存核心就是充分利用两级缓存+合理的预加载/渐进式加载,用Picasso也能实现:
先加载缩略图,再加载原图:提升首屏加载速度,同时缓存原图
Picasso.get() .load(imageUrl) .resize(200, 200) // 先加载小尺寸缩略图 .onlyScaleDown() // 只缩小不放大,避免失真 .into(holder.imageView, new Callback() { @Override public void onSuccess() { // 缩略图加载成功后,再加载原图 Picasso.get().load(imageUrl).into(holder.imageView); } @Override public void onError(Exception e) {} });后台预加载:比如在列表初始化时,提前加载即将显示的几张图片,让用户滑动时直接从缓存取。
最后说下返回页面重新加载的问题
如果返回页面后图片重新加载,大概率是内存缓存被系统回收了(比如应用后台时系统杀了内存),这时候Picasso会自动从磁盘缓存读取,速度比网络快很多。如果还是觉得慢,可以考虑增大内存缓存,或者通过OkHttp配置更长的磁盘缓存有效期。
内容的提问来源于stack exchange,提问作者ico3d

