如何在 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-Controlmax-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();
  )

Piccassoget() 方法需要在单独的线程上调用,我也在同一个线程上保存该图像。

图像保存后,您可以获取所有类似的文件

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);

注意:

红色颜色表示图片是从网络获取的。

绿色颜色表示图像是从缓存内存中获取的。

蓝色颜色表示图像是从磁盘内存获取的。

在发布应用之前删除或设置它falsepicasso.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 中使用磁盘缓存?的主要内容,如果未能解决你的问题,请参考以下文章

Picasso中的缓存无效

如何使用 Picasso 使用动态 URL 缓存来自 S3 存储桶的图像?

使用 Picasso 和 OkHttp 更新/更改缓存位图

使毕加索中的缓存无效

Android通用流行框架大全

15 个 Android 通用流行框架大全