【问题标题】:How do I use disk caching in Picasso?如何在 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);
}

我启用了调试,它总是显示红色和绿色。但从不显示黄色

现在如果我下次加载相同的图像并且互联网不可用,则图像不会加载。

问题:

  1. 没有本地磁盘缓存吗?
  2. 如何启用磁盘缓存,因为我将多次使用同一个图像。
  3. 我需要在 android manifest 文件中添加一些磁盘权限吗?

【问题讨论】:

  • 我也有同样的问题。它不会缓存!
  • 伙计们,你应该看看 facebook 的 Fresco lib。它的缓存管理很棒。

标签: android image caching picasso


【解决方案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 已回答了两个问题 -> Question1Question2

【讨论】:

  • 不,如果应用程序关闭,这将不起作用。应用程序被强制停止后,所有图像都消失了。
  • 这给了我这个错误: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);

【讨论】:

【解决方案4】:

对于最新版本 2.71828 这些就是你的答案。

Q1:没有本地磁盘缓存吗?

A1:Picasso内部有默认缓存,请求流程就是这样

App -> Memory -> Disk -> Server

无论他们首先在哪里遇到他们的图片,他们都会使用该图片,然后停止请求流。 响应流呢?不用担心,就在这里。

Server -> Disk -> Memory -> App

默认情况下,它们将首先存储到本地磁盘中以用于扩展保留缓存。然后是内存,用于缓存的实例使用。

启用此功能,您可以使用 Picasso 中的内置指示器查看图像形成的位置。

Picasso.get().setIndicatorEnabled(true);

它会在图片的左上角显示一个标志。

  • 红色标志表示图片来自服务器。 (首次加载时无缓存)
  • 蓝色标志表示照片来自本地磁盘。 (缓存)
  • 绿色标志表示图像来自内存。 (实例缓存)

Q2:如何启用磁盘缓存,因为我将多次使用同一个图像?

A2:您不必启用它。这是默认设置。

当您希望您的图像始终保持新鲜时,您需要做的是禁用它。有两种禁用缓存方式。

  1. .memoryPolicy() 设置为NO_CACHE 和/或NO_STORE,流程将如下所示。

NO_CACHE 将跳过从内存中查找图像。

App -> Disk -> Server

NO_STORE 将在第一次加载图像时跳过将图像存储在内存中。

Server -> Disk -> App
  1. .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

          【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-11-15
          • 2015-10-01
          • 2018-08-22
          • 1970-01-01
          • 2016-03-16
          • 2014-06-16
          • 2016-06-25
          • 2015-06-13
          相关资源
          最近更新 更多