如何在 Picasso 中使用磁盘缓存?
Posted
技术标签:
【中文标题】如何在 Picasso 中使用磁盘缓存?【英文标题】:How do I use disk caching in Picasso? 【发布时间】:2014-07-21 15:27:11 【问题描述】:我正在使用 Picasso 在我的 android 应用中显示图像:
/**
* load image.This is within a activity so this context is activity
*/
public void loadImage ()
Picasso picasso = Picasso.with(this);
picasso.setDebugging(true);
picasso.load(quiz.getImageUrl()).into(quizImage);
我启用了调试,它总是显示红色和绿色。但从不显示黄色
现在如果我下次加载相同的图像并且互联网不可用,则图像不会加载。
问题:
-
没有本地磁盘缓存吗?
如何启用磁盘缓存,因为我将多次使用同一个图像。
我需要在 android manifest 文件中添加一些磁盘权限吗?
【问题讨论】:
我也有同样的问题。它不会缓存! 伙计们,你应该看看 facebook 的 Fresco lib。它的缓存管理很棒。 【参考方案1】:这就是我所做的。效果很好。
首先将OkHttp添加到app模块的gradle构建文件中:
compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.squareup.okhttp3:okhttp:3.10.0'
compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'
然后做一个扩展Application
的类
import android.app.Application;
import com.jakewharton.picasso.OkHttp3Downloader;
import com.squareup.picasso.Picasso;
public class Global extends Application
@Override
public void onCreate()
super.onCreate();
Picasso.Builder builder = new Picasso.Builder(this);
builder.downloader(new OkHttp3Downloader(this,Integer.MAX_VALUE));
Picasso built = builder.build();
built.setIndicatorsEnabled(true);
built.setLoggingEnabled(true);
Picasso.setSingletonInstance(built);
将其添加到 Manifest 文件中,如下所示:
<application
android:name=".Global"
.. >
</application>
现在像往常一样使用毕加索。没有变化。
编辑:
如果您只想使用缓存图像。像这样调用图书馆。我注意到,如果我们不添加 networkPolicy,图像将不会显示在完全脱机启动时即使它们被缓存。下面的代码解决了这个问题。
Picasso.with(this)
.load(url)
.networkPolicy(NetworkPolicy.OFFLINE)
.into(imageView);
编辑#2
上面代码的问题是,如果你清除缓存,Picasso会一直在缓存中离线寻找它并失败,下面的代码示例查看本地缓存,如果离线没有找到,它会在线并补充缓存.
Picasso.with(getActivity())
.load(imageUrl)
.networkPolicy(NetworkPolicy.OFFLINE)
.into(imageView, new Callback()
@Override
public void onSuccess()
@Override
public void onError()
//Try again online if cache failed
Picasso.with(getActivity())
.load(posts.get(position).getImageUrl())
.error(R.drawable.header)
.into(imageView, new Callback()
@Override
public void onSuccess()
@Override
public void onError()
Log.v("Picasso","Could not fetch image");
);
);
【讨论】:
@ArtjomB。 ,我确实回答了这个问题。解决方案确实有效。但是我可以使用这个小小的澄清。我浏览了 OkHttp 文档,他们没有提到“缓存”的单位。所以如果有人想分享一些智慧......这是一个很好的机会。 @ArtjomB。是的,这是有道理的。已编辑! @SanketBerde:感谢您的快速说明,但我发现只有当应用程序在后台运行时(离线时),图像才会从内存中出现。如果我关闭应用程序,则清除正在运行的应用程序,然后再次打开我的应用程序,图像不会从缓存加载。我已经设置了即将出现的错误默认加载图像。这里有什么问题? 也许毕加索改变了事情的运作方式,因为对我来说,没有 okhttp 和网络策略也能正常工作。重新启动时,立即从磁盘中获取图像,当网络离线时,它仍然可以正常显示。 使用okhttp3.OkHttpClient
库你必须使用OkHttp3Downloader
类形式compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'
【参考方案2】:
1) 第一个问题的答案: 根据Picasso Doc for With() method
从 with() 返回的全局默认 Picasso 实例会自动使用适用于大多数实现的默认值进行初始化。
15% 的可用应用程序 RAM 的 LRU 内存缓存 2% 存储空间的磁盘缓存最大为 50MB 但不少于 5MB。
但是 Disk Cache
全局默认毕加索操作仅在 API 14+ 上可用
2)第二个问题的答案:Picasso
使用HTTP
客户端请求到Disk Cache
操作所以你可以让你自己的http request header
有属性Cache-Control
与 max-age
并创建您自己的静态毕加索实例而不是默认毕加索通过使用
1] HttpResponseCache(注意:仅适用于 API 13+) 2] OkHttpClient(适用于所有 API)
示例使用OkHttpClient
创建您自己的静态毕加索类:
首先新建一个类,获取自己的单例picasso
对象
import android.content.Context;
import com.squareup.picasso.Downloader;
import com.squareup.picasso.OkHttpDownloader;
import com.squareup.picasso.Picasso;
public class PicassoCache
/**
* Static Picasso Instance
*/
private static Picasso picassoInstance = null;
/**
* PicassoCache Constructor
*
* @param context application Context
*/
private PicassoCache (Context context)
Downloader downloader = new OkHttpDownloader(context, Integer.MAX_VALUE);
Picasso.Builder builder = new Picasso.Builder(context);
builder.downloader(downloader);
picassoInstance = builder.build();
/**
* Get Singleton Picasso Instance
*
* @param context application Context
* @return Picasso instance
*/
public static Picasso getPicassoInstance (Context context)
if (picassoInstance == null)
new PicassoCache(context);
return picassoInstance;
return picassoInstance;
使用你自己的单例 picasso
对象而不是 Picasso.With()
PicassoCache.getPicassoInstance(getContext()).load(imagePath).into(imageView)
3) 第三个问题的答案:磁盘缓存操作不需要任何磁盘权限
参考文献:Github issue about disk cache,@jake-wharton 已回答了两个问题 -> Question1 和 Question2
【讨论】:
不,如果应用程序关闭,这将不起作用。应用程序被强制停止后,所有图像都消失了。 这给了我这个错误:FATAL EXCEPTION: main java.lang.NoClassDefFoundError: com.squareup.okhttp.OkHttpClient
@CIRCLE 抱歉来晚了,要使用示例,您需要先下载 okhttp
使用的 [okhttp] (square.github.io/okhttp) 包和 [okio] (github.com/square/okio) 包跨度>
@CIRCLE 可能你也需要下载 [okhttp-urlconnection] (mvnrepository.com/artifact/com.squareup.okhttp/…) 包
您的解决方案有问题Do not place Android context classes in static fields (static reference to Picasso which has field context pointing to Context); this is a memory leak (and also breaks Instant Run)
【参考方案3】:
对于缓存,我会使用 OkHttp 拦截器 来控制缓存策略。查看 OkHttp 库中包含的这个示例。
RewriteResponseCacheControl.java
这就是我如何将它与毕加索一起使用 -
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.networkInterceptors().add(new Interceptor()
@Override
public Response intercept(Chain chain) throws IOException
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder().header("Cache-Control", "max-age=" + (60 * 60 * 24 * 365)).build();
);
okHttpClient.setCache(new Cache(mainActivity.getCacheDir(), Integer.MAX_VALUE));
OkHttpDownloader okHttpDownloader = new OkHttpDownloader(okHttpClient);
Picasso picasso = new Picasso.Builder(mainActivity).downloader(okHttpDownloader).build();
picasso.load(imageURL).into(viewHolder.image);
【讨论】:
不再起作用 networkInterceptors() 返回一个不可变列表。 @noev 在 OkHttp 3.x 中你可以使用 builder 模式(见github.com/square/okhttp/wiki/Interceptors)来添加拦截器。【参考方案4】:对于最新版本 2.71828 这些就是你的答案。
Q1:没有本地磁盘缓存吗?
A1:Picasso内部有默认缓存,请求流程就是这样
App -> Memory -> Disk -> Server
无论他们首先在哪里遇到他们的图片,他们都会使用该图片,然后停止请求流。 响应流呢?不用担心,就在这里。
Server -> Disk -> Memory -> App
默认情况下,它们将首先存储到本地磁盘中以用于扩展保留缓存。然后是内存,用于缓存的实例使用。
启用此功能,您可以使用 Picasso 中的内置指示器查看图像形成的位置。
Picasso.get().setIndicatorEnabled(true);
它会在图片的左上角显示一个标志。
红色标志表示图片来自服务器。 (首次加载时无缓存) 蓝色标志表示照片来自本地磁盘。 (缓存) 绿色标志表示图像来自内存。 (实例缓存)Q2:如何启用磁盘缓存,因为我将多次使用同一个图像?
A2:您不必启用它。这是默认设置。
当您希望您的图像始终保持新鲜时,您需要做的是禁用它。有两种禁用缓存方式。
-
将
.memoryPolicy()
设置为NO_CACHE 和/或NO_STORE,流程将如下所示。
NO_CACHE 将跳过从内存中查找图像。
App -> Disk -> Server
NO_STORE 将在第一次加载图像时跳过将图像存储在内存中。
Server -> Disk -> App
-
将
.networkPolicy()
设置为NO_CACHE 和/或NO_STORE,流程将如下所示。
NO_CACHE 将跳过从磁盘查找图像。
App -> Memory -> Server
NO_STORE 将在第一次加载图像时跳过将图像存储在磁盘中。
Server -> Memory -> App
您可以禁用完全不缓存图像。这是一个例子。
Picasso.get().load(imageUrl)
.memoryPolicy(MemoryPolicy.NO_CACHE,MemoryPolicy.NO_STORE)
.networkPolicy(NetworkPolicy.NO_CACHE, NetworkPolicy.NO_STORE)
.fit().into(banner);
完全没有缓存和没有存储的流程是这样的。
App -> Server //Request
Server -> App //Response
因此,您可能还需要它来减少您的应用存储使用量。
Q3:我是否需要在 android manifest 文件中添加一些磁盘权限?
A3:不,但不要忘记为您的 HTTP 请求添加 INTERNET 权限。
【讨论】:
【参考方案5】:1) Picasso 默认有缓存(见 ahmed hamdy 答案)
2) 如果您真的必须从磁盘缓存中获取图像,然后再从网络中获取图像,我建议您编写自己的下载器:
public class OkHttpDownloaderDiskCacheFirst extends OkHttpDownloader
public OkHttpDownloaderDiskCacheFirst(OkHttpClient client)
super(client);
@Override
public Response load(Uri uri, int networkPolicy) throws IOException
Response responseDiskCache = null;
try
responseDiskCache = super.load(uri, 1 << 2); //NetworkPolicy.OFFLINE
catch (Exception ignored) // ignore, handle null later
if (responseDiskCache == null || responseDiskCache.getContentLength()<=0)
return super.load(uri, networkPolicy); //user normal policy
else
return responseDiskCache;
并且在 OnCreate 方法中的 Application 单例中将其与 picasso 一起使用:
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setCache(new Cache(getCacheDir(), 100 * 1024 * 1024)); //100 MB cache, use Integer.MAX_VALUE if it is too low
OkHttpDownloader downloader = new OkHttpDownloaderDiskCacheFirst(okHttpClient);
Picasso.Builder builder = new Picasso.Builder(this);
builder.downloader(downloader);
Picasso built = builder.build();
Picasso.setSingletonInstance(built);
3) 默认应用缓存文件夹不需要权限
【讨论】:
【参考方案6】:我不知道这个解决方案有多好,但它绝对是 简单的 一个我刚刚在我的应用程序中使用,它工作正常
你像这样加载图像
public void loadImage ()
Picasso picasso = Picasso.get();
picasso.setIndicatorsEnabled(true);
picasso.load(quiz.getImageUrl()).into(quizImage);
你可以像这样得到bimap
Bitmap bitmap = Picasso.get().load(quiz.getImageUrl()).get();
现在将Bitmap
转换为JPG
文件并存储在缓存中,下面是获取bimap 并对其进行缓存的完整代码
Thread thread = new Thread()
public void run()
File file = new File(getCacheDir() + "/" +member.getMemberId() + ".jpg");
try
Bitmap bitmap = Picasso.get().load(uri).get();
FileOutputStream fOut = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100,new FileOutputStream(file));
fOut.flush();
fOut.close();
catch (Exception e)
e.printStackTrace();
;
thread.start();
)
Piccasso
的 get()
方法需要在单独的线程上调用,我也在同一个线程上保存该图像。
图像保存后,您可以获取所有类似的文件
List<File> files = new LinkedList<>(Arrays.asList(context.getExternalCacheDir().listFiles()));
现在您可以在下面找到您要查找的文件
for(File file : files)
if(file.getName().equals("fileyouarelookingfor" + ".jpg")) // you need the name of the file, for example you are storing user image and the his image name is same as his id , you can call getId() on user to get the file name
Picasso.get() // if file found then load it
.load(file)
.into(mThumbnailImage);
return; // return
// fetch it over the internet here because the file is not found
【讨论】:
毕加索的版本已经改变,需要更新。我建议编辑!不过很好的答案。做了一些改变后它对我有用。 @ArpitAnand 谢谢,请随时改进答案,我会接受更改【参考方案7】:在Application.onCreate
中添加如下代码即可正常使用
Picasso picasso = new Picasso.Builder(context)
.downloader(new OkHttp3Downloader(this,Integer.MAX_VALUE))
.build();
picasso.setIndicatorsEnabled(true);
picasso.setLoggingEnabled(true);
Picasso.setSingletonInstance(picasso);
如果你先缓存图片,然后在 ProductImageDownloader.doBackground
中执行类似的操作
final Callback callback = new Callback()
@Override
public void onSuccess()
downLatch.countDown();
updateProgress();
@Override
public void onError()
errorCount++;
downLatch.countDown();
updateProgress();
;
Picasso.with(context).load(Constants.imagesUrl+productModel.getGalleryImage())
.memoryPolicy(MemoryPolicy.NO_CACHE).fetch(callback);
Picasso.with(context).load(Constants.imagesUrl+productModel.getLeftImage())
.memoryPolicy(MemoryPolicy.NO_CACHE).fetch(callback);
Picasso.with(context).load(Constants.imagesUrl+productModel.getRightImage())
.memoryPolicy(MemoryPolicy.NO_CACHE).fetch(callback);
try
downLatch.await();
catch (InterruptedException e)
e.printStackTrace();
if(errorCount == 0)
products.remove(productModel);
productModel.isDownloaded = true;
productsDatasource.updateElseInsert(productModel);
else
//error occurred while downloading images for this product
//ignore error for now
// FIXME: 9/27/2017 handle error
products.remove(productModel);
errorCount = 0;
downLatch = new CountDownLatch(3);
if(!products.isEmpty() /*&& testCount++ < 30*/)
startDownloading(products.get(0));
else
//all products with images are downloaded
publishProgress(100);
并像正常或使用磁盘缓存一样加载您的图像
Picasso.with(this).load(Constants.imagesUrl+batterProduct.getGalleryImage())
.networkPolicy(NetworkPolicy.OFFLINE)
.placeholder(R.drawable.GalleryDefaultImage)
.error(R.drawable.GalleryDefaultImage)
.into(viewGallery);
注意:
红色颜色表示图片是从网络获取的。
绿色颜色表示图像是从缓存内存中获取的。
蓝色颜色表示图像是从磁盘内存获取的。
在发布应用之前删除或设置它false
picasso.setLoggingEnabled(true);
,picasso.setIndicatorsEnabled(true);
(如果不需要)。谢谢x
【讨论】:
【参考方案8】:我使用这个代码并且工作了,也许对你有用:
public static void makeImageRequest(final View parentView,final int id, final String imageUrl)
final int defaultImageResId = R.mipmap.user;
final ImageView imageView = (ImageView) parentView.findViewById(id);
Picasso.with(context)
.load(imageUrl)
.networkPolicy(NetworkPolicy.OFFLINE)
.into(imageView, new Callback()
@Override
public void onSuccess()
Log.v("Picasso","fetch image success in first time.");
@Override
public void onError()
//Try again online if cache failed
Log.v("Picasso","Could not fetch image in first time...");
Picasso.with(context).load(imageUrl).networkPolicy(NetworkPolicy.NO_CACHE)
.memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE).error(defaultImageResId)
.into(imageView, new Callback()
@Override
public void onSuccess()
Log.v("Picasso","fetch image success in try again.");
@Override
public void onError()
Log.v("Picasso","Could not fetch image again...");
);
);
【讨论】:
【参考方案9】:我遇到了同样的问题并改用 Glide 库。缓存在那里是开箱即用的。 https://github.com/bumptech/glide
【讨论】:
Glide 的代码比 Picasso 多 5 倍。已经在使用 okhttp 时,最好使用 Picasso 两个库都使用缓存但缓存不同:medium.com/@multidots/glide-vs-picasso-930eed42b81d以上是关于如何在 Picasso 中使用磁盘缓存?的主要内容,如果未能解决你的问题,请参考以下文章