Picaso完美兼容OkHttp3.3,缓存优化两不误 – Tamic Developer”s Blog

为何在Fresco,Glide这么强大的背景下,我又想起了当初的Picasso,又为何写这篇文章?是因为最近项目采用了square公司的RxAndroid,Retrfit和OKhttp, 不得不联想到这个公司曾经还有款图片加载Picasso,所以采用了square公司的全家桶来进行项目开发,为了减少开发成本和也防止Apk增大,毕竟一个公司的框架之前兼容性不用担心,那么请让我们回顾一下Picass之路

首先先让我们看看主流图片加载库

  • Picasso,Square公司的开源项目 ,和Square的网络库一起能发挥最大作用。占用内存小,自身不带缓存,需依赖OKhttps实现缓存,不支持gif图片

  • Fresco,FB的明星项目,也是2015最火的项目之一,匿名共享缓存等机制保证低端机表现极佳,但是源代码基于C/C++,阅读困难度提升。效率高,sdk库占用包体积比较大

  • Glide,Google员工私人项目,但是Google很多项目在用,占用内存小,减低oom更靠谱,相对Picasso在Gif方面有优势,并自带缓存功能!

我做了一个实验对比 用一个普通listview加载50张图片,并快速滑动列表,下面分别是glide和picasso消耗内存图

  • 分析后得出 一个占用内存大 一个占用cpu资源大, 这种区别是由于picasso只缓存一张大图,每次加载根据imagview的大小裁剪,因此消耗的cpu资源高,glide是分别存储不同尺寸的小图,每次不用计算,因此消耗内存比较多,加载速度相对Picasso也快,但也很耗流量.

  • 为了避免OOM, 我毫不犹豫选择了消耗内存较小的picasso, Fresco不用说都是加载速度第一的框架,采用c库 ,我没做集成测试,具体消耗多少cpu资源我无法给出数据,据说业界第一,但是对apk大小要求的项目很可能不太合适,这里对Apk包体积要求不高的项目,Fresco是优先的首选。

喜欢glide的朋友可以看看这篇文章 :http://mrfu.me/2016/02/27/Glide_Getting_Started/

实验测试并做了简单比较后,为何还要继续说Picasso,不是说他有多快多流畅,只是当你使用了square公司其他的开源项目,会发现他们都会依赖okhttp,okhttp的强大不言而喻,一个网络库可以无缝隙的对接Retrofit和Picasso.今天只介绍piacsso相关的,说说picasso(官方:https://github.com/square/picasso) 的一些常用技巧!


#使用方式:

配置gradle

dependencies {
c
compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.squareup.okhttp3:okhttp:3.3.1'
compile 'com.squareup.okhttp3:logging-interceptor:3.3.1'
}

据说目前的2.5.3已修复了2.52无法兼容okhttp3的问题,但我还是选择了2.52版本。

基本加载用法

Picasso.with(getApplication())
.load(url)
.into(imageView);

以上用法很简单,加载图片时提供url插入到imageview即可,picasso其他强大功还没有太多的理解的同学请Follow Me!

#裁剪图片

      
       1
      
      
       Picasso.with(getApplication()).resize(
       width, 
       height);
      

这句方法会出现bug,误用!

请用Transformation来进行转义实现:

      
       1
      
      
       2
      
      
       3
      
      
       4
      
      
      
       Picasso.with(getApplication())
      
      
        .
       load(url)
      
      
        .
       transform(
       new PaTransformation(
       width, 
       height)).into(imageView);
      

Transformation可以拦截到picasoo返回的bitmap,拿着bitmap随心所欲!

      
       1
      
      
       2
      
      
       3
      
      
       4
      
      
       5
      
      
       6
      
      
       7
      
      
       8
      
      
       9
      
      
       10
      
      
       11
      
      
       12
      
      
       13
      
      
       14
      
      
       15
      
      
       16
      
      
       17
      
      
       18
      
      
       19
      
      
       20
      
      
       21
      
      
       22
      
      
       23
      
      
       24
      
      
       25
      
      
       26
      
      
       27
      
      
       28
      
      
       29
      
      
       30
      
      
       31
      
      
       32
      
      
       33
      
      
       34
      
      
       35
      
         
       public class TamicTransformation implements Transformation {
      
      
         
       private 
       int 
       width;
      
         
       private 
       int 
       height;
      
         
       private 
       String 
       key;
      
      
         
       public PaTransformation(
       int 
       width, 
       int 
       height) {
      
             
       this(
       width, 
       height,  
       width + 
       "*" + 
       height);
      
      
          }
      
      
         
       public PaTransformation(
       int 
       width, 
       int 
       height, 
       String 
       key) {
      
             
       this.
       width = 
       width;
      
             
       this.
       height = 
       height;
      
             
       this.
       key = 
       key;
      
      
          }
      
      
      
          @Override
      
         
       public Bitmap transform(Bitmap source) {
      
      
      
             略 拿着source进行裁剪缩放即可
      
      
      
             
       if (result != source) {
      
                 
      
      
                  source.recycle();
      
      
              }
      
             
       return result;
      
      
          }
      
      
      
          @Override
      
         
       public 
       String 
       key() {
      
      
             
       return 
       key;
      
      
          }
      
      
       }
      

列如处理圆形头像

      
       1
      
      
       2
      
      
       3
      
      
       4
      
      
       5
      
      
       6
      
      
       7
      
      
       8
      
      
       9
      
      
       10
      
      
       11
      
      
       12
      
      
       13
      
      
       14
      
      
       15
      
      
       16
      
      
       17
      
      
       18
      
      
       19
      
      
       20
      
      
       21
      
      
       22
      
      
       23
      
      
       24
      
      
       25
      
      
       26
      
      
       27
      
      
       28
      
      
       29
      
      
       30
      
      
       31
      
      
       32
      
      
       33
      
      
       34
      
      
       35
      
      
       36
      
      
       37
      
      
       38
      
      
       
       public class CircleTransformation implements Transformation {   
      
       
       private 
       static 
       final 
       int STROKE_WIDTH = 
       5;  
      
        
      
      
        @Override   
      
       
       public Bitmap transform(Bitmap source) {   
      
         
       int 
       size = Math.
       min(source.getWidth(), source.getHeight());   
      
         
       int x = (source.getWidth() - 
       size) / 
       2; 
      
         
       int y = (source.getHeight() - 
       size) / 
       2;  
      
       
      
      
          Bitmap squaredBitmap = Bitmap.createBitmap(source, x, y, 
       size, 
       size);  
      
         
       if (squaredBitmap != source) { 
      
      
               source.recycle();   
      
      
          } 
      
      
          Bitmap bitmap = Bitmap.createBitmap(
       size, 
       size,source.getConfig()); 
      
      
          Canvas canvas = 
       new Canvas(bitmap); 
      
      
          Paint avatarPaint = 
       new Paint();  
      
      
          BitmapShader 
       shader = 
       new BitmapShader(squaredBitmap, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP);avatarPaint.setShader(
       shader);  
      
         
      
      
          Paint outlinePaint = 
       new Paint();
      
      
          outlinePaint.setColor(Color.WHITE);
      
      
          outlinePaint.setStyle(Paint.Style.STROKE);
      
      
          outlinePaint.setStrokeWidth(STROKE_WIDTH);
      
      
          outlinePaint.setAntiAlias(
       true);
      
         
      
         
       float r = 
       size / 
       2f;  
      
      
          canvas.drawCircle(r, r, r, avatarPaint); 
      
      
          canvas.drawCircle(r, r, r - STROKE_WIDTH / 
       2, outlinePaint);
      
       
      
      
          squaredBitmap.recycle();
      
         
       return bitmap;  
      
      
         }
      
       
      
      
         @Override  
      
        
       public 
       String 
       key() { 
      
         
       return 
       "circle)";
      
      
         }
      
      
       }
      

接着设置渲染模式

      
       1
      
      
       2
      
              
      
      
       Picasso
       .with(
       getApplication()) 
       .fit()
       .centerCrop()
      

清空缓存

新的版本2.52 已经无法直接拿到之前的cache,因此可以用Picasso.invalidate()的实现清楚缓存!

以前我们可以这样

      
       1
      
      
       2
      
      
      
       Clear
       .clearCache(
       Picasso
       .with(
       context));
      

但现在 不行了

稍加封装成了这样子:

      
       1
      
      
       2
      
      
       3
      
      
       4
      
      
       5
      
      
       6
      
      
       7
      
      
       8
      
      
       9
      
      
       10
      
      
       11
      
      
       12
      
      
       13
      
      
       14
      
      
       15
      
      
      
       void clearCache(Uri uri, 
       File 
       file, String path) {
      
         
      
      
       if (!TextUtils.isEmpty(uri.toString())) {
      
      
       mPicasso.invalidate(uri);
      
      
       return;
      
      
       }
      
      
       if (!NullUtils.isNull(
       file)) {
      
      
       mPicasso.invalidate(
       file);
      
      
       return;
      
      
       }
      
      
       if (!TextUtils.isEmpty(path)) {
      
      
       mPicasso.invalidate(path);
      
      
       }
      
      
       }
      

当然也可以这样!

      
       1
      
      
       2
      
      
      
       Picasso
       .with(
       getContext())
       .load(
       Url)
       .memoryPolicy(
       MemoryPolicy
       .NO_CACHE)
       .into(
       image);
      

在加载图片时直接不让做缓存!

加入缓存

当然2.5.2没做对oKhttp3.3的兼容,因此我们加入自定义的cilent,对okhttp做下缓存定制,请照着下面姿势作

构建OkHttpClient

      
       1
      
      
       2
      
      
       3
      
      
       4
      
      
       5
      
      
       6
      
      
       7
      
      
       // creat the OkHttpClient.
      
      
       OkHttpClient client =
       new 
       OkHttpClient
      
      
               .Builder()
      
      
               .cache(
       new 
       Cache(
       "你的缓存路径", 
       1000*
       1024))
      
      
               .addInterceptor(
       new 
       CaheInterceptor(context, 
       null))
      
      
               .addNetworkInterceptor(
       new 
       CaheInterceptor(context, 
       null))
      
      
               .build();
      

拦截器Interceptor

拦截器大家都不陌生,尤其是玩过okhttp和retofit的朋友,那肯定是拦截http的拦截请求和响应的.

public class CaheInterceptor implements Interceptor {

private Context context;
public CaheInterceptor(@NonNull Context context) {
    this.context = context;
}

@Override
public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    if (NetworkUtil.isNetworkAvailable(context)) {
        Response response = chain.proceed(request);
        // read from cache for 60 s
        int maxAge = 300;
        String cacheControl = request.cacheControl().toString();
        Log.e("Tamic", maxAge+ "s load cahe:" + cacheControl);
        return response.newBuilder()
                .removeHeader("Pragma")
                .removeHeader("Cache-Control")
                .header("Cache-Control", "public, max-age=" + maxAge)
                .build();
    } else {
        Log.e("Tamic", " no network load cahe");
        request = request.newBuilder()
                .cacheControl(CacheControl.FORCE_CACHE)
                .build();
        Response response = chain.proceed(request);
        //set cahe times is 3 days
        int maxStale = 60 * 60 * 24 * 3;
        return response.newBuilder()
                .removeHeader("Pragma")
                .removeHeader("Cache-Control")
                .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                .build();
    }
}
 }

添加到Picasso中

      
       1
      
      
       2
      
      
       3
      
      
       4
      
      
       5
      
      
           // 
       Generate the global 
       default Picasso instance.
      
      
         Picasso   mPicasso = getPicasso(
       context, 
       null);
      
      
           mPicasso.setLoggingEnabled(
       true);
      
      
      
       }
      

自定义DownLoader

为了兼容okhttp3.31 实现下载器!

public class ImageDownLoader implements Downloader {
 OkHttpClient client = null;

public ImageDownLoader(OkHttpClient client) {
    this.client = client;
}

@Override
public Response load(Uri uri, int networkPolicy) throws IOException {

    CacheControl cacheControl = null;
    if (networkPolicy != 0) {
        if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
            cacheControl = CacheControl.FORCE_CACHE;
        } else {
            CacheControl.Builder builder = new CacheControl.Builder();
            if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
                builder.noCache();
            }
            if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
                builder.noStore();
            }
            cacheControl = builder.build();
        }
    }

    Request.Builder builder = new Request.Builder().url(uri.toString());
    if (cacheControl != null) {
        builder.cacheControl(cacheControl);
    }

    okhttp3.Response response = client.newCall(builder.build()).execute();
    int responseCode = response.code();
    if (responseCode >= 300) {
        response.body().close();
        throw  大专栏  Picaso完美兼容OkHttp3.3,缓存优化两不误 - Tamic Developer"s Blognew ResponseException(responseCode + " " + response.message(), networkPolicy,
                responseCode);
    }

    boolean fromCache = response.cacheResponse() != null;

    ResponseBody responseBody = response.body();
    return new Response(responseBody.byteStream(), fromCache, responseBody.contentLength());

}

@Override
public void shutdown() {

    Cache cache = client.cache();
    if (cache != null) {
        try {
            cache.close();
        } catch (IOException ignored) {
        }
    }
 }
}

接着将ImageDownLoader 加入到Picasso

      
       1
      
      
       2
      
      
       3
      
      
       4
      
      
       5
      
      
       6
      
      
       7
      
      
       8
      
      
       9
      
      
       10
      
      
       11
      
      
       12
      
      
       13
      
      
       14
      
      
       15
      
      
       16
      
      
       17
      
      
       18
      
      
       19
      
          
       /**
      
      
            * Download Big Image only, Not singleton but shared cache
      
      
            */
      
           
       public Picasso getPicasso(Context context) {
      
      
               OkHttpClient client = getProgressBarClient();
      
              
       return 
       new 
       Picasso.Builder(context)
      
      
                       .downloader(
       new 
       ImageDownLoader(client))
      
      
                       .build();
      
      
           }
      
      
      
       /**
      
      
            * Not singleton
      
      
            */
      
          
       private  OkHttpClient getProgressBarClient() {
      
              
       return client.
       new
       Builder()
      
      
                       .addInterceptor(
       new 
       CaheInterceptor(context))
      
      
                       .addNetworkInterceptor(
       new 
       CaheInterceptor(contextr))
      
      
                       .build();
      
      
           }
      

这样我们在做图片加载时 就可以:

      
       1
      
      
       2
      
      
      
       (context) .load(Url).into(image)
      

因此用了Picasso我们可以直接将缓存策略用到retrofit上去,其实一箭双雕,大大简化了开发成本!

#如何支持Https

姿势很简单 利用上面构建好的downloader, 设置OkHttpprotocols即可,并构建ssl。

      
       1
      
      
       2
      
      
       3
      
      
       4
      
      
       5
      
      
       6
      
      
       7
      
      
       8
      
      
       9
      
      
       10
      
      
       11
      
      
       12
      
      
       13
      
      
       14
      
      
       15
      
      
       16
      
      
       17
      
      
       18
      
      
       19
      
      
       20
      
      
       21
      
      
       22
      
      
       23
      
      
       24
      
      
       25
      
      
       26
      
      
       27
      
      
       28
      
      
       29
      
      
       30
      
      
       31
      
      
       32
      
      
       33
      
      
       34
      
      
       35
      
      
       36
      
      
       37
      
      
       38
      
      
       39
      
      
       40
      
      
       41
      
      
       42
      
      
       43
      
      
      
       OkHttpClient client = 
       new OkHttpClient();
      
      
        client.setHostnameVerifier(
       new HostnameVerifier() {
      
           
      
           
       public boolean verify(String s, SSLSession sslSession) {
      
               
       return 
       true;
      
      
            }
      
      
        });
      
      
        TrustManager[] trustAllCerts = 
       new TrustManager[] { 
       new X509TrustManager() {
      
           
      
           
       public void checkClientTrusted(
      
      
                    java.security.cert.X509Certificate[] x509Certificates,
      
      
                    String s) 
       throws java.security.cert.CertificateException {
      
      
            }
      
      
           
      
           
       public void checkServerTrusted(
      
      
                    java.security.cert.X509Certificate[] x509Certificates,
      
      
                    String s) 
       throws java.security.cert.CertificateException {
      
      
            }
      
      
           
      
           
       public java.security.cert.X509Certificate[] getAcceptedIssuers() {
      
               
       return 
       new java.security.cert.X509Certificate[] {};
      
      
            }
      
      
        } };
      
       
       try {
      
      
            SSLContext sc = SSLContext.getInstance(
       "TLS");
      
      
            sc.init(
       null, trustAllCerts, 
       new java.security.SecureRandom());
      
      
            client.setSslSocketFactory(sc.getSocketFactory());
      
      
        } 
       catch (Exception e) {
      
      
            e.printStackTrace();
      
      
        }
      
      
      
      
             clent.protocols(Collections.singletonList(Protocol.HTTP_1_1))
      
      
             .build();
      
          
      
      
       final Picasso picasso = 
       new Picasso.Builder(
       this)
      
      
             .downloader(
       new ImageDownloader(client))
      
      
             .build();
      
          
      
      
       Picasso.setSingletonInstance(picasso);
      

优化相关

优化不缓存策略

      
       1
      
      
       2
      
      
       3
      
      
       4
      
         
       public RequestCreator skipMemoryCache(RequestCreator requestCreator) {
      
          
       return requestCreator.memoryPolicy(MemoryPolicy.NO_STORE, MemoryPolicy.NO_CACHE)
      
      
                   .networkPolicy(NetworkPolicy.NO_STORE, NetworkPolicy.NO_CACHE);
      
      
       }
      

降低内存消耗
设置RGB_565编码格式,降低内存消耗

      
       1
      
      
       2
      
      
       3
      
      
       4
      
      
      
       public 
       RequestCreator cutDownMemory(RequestCreator requestCreator) {
      
          
       return requestCreator.config(Bitmap.Config.RGB_565);
      
      
       }
      

取消加载

      
       1
      
      
       2
      
      
       3
      
      
       4
      
      
       5
      
      
       6
      
      
       7
      
      
       8
      
      
       9
      
      
       10
      
      
       11
      
      
       12
      
      
       13
      
      
       14
      
      
       15
      
      
       16
      
      
       17
      
      
       18
      
      
       19
      
      
       20
      
      
       21
      
      
       22
      
      
       23
      
      
       24
      
      
       25
      
      
       26
      
      
       27
      
      
       public 
       class TamicImageView extends ImageView {
      
      
      
      
      
       public TamicImageView(Context context) {
      
          
       this(context, 
       null, 
       0);
      
      
       }
      
      
      
       public TamicImageView(Context context, AttributeSet attrs) {
      
          
       this(context, attrs, 
       0);
      
      
       }
      
      
      
       public TamicImageView(Context context, AttributeSet attrs, int defStyleAttr) {
      
          
       super(context, attrs, defStyleAttr);
      
        
      
      
       }
      
         
      
      
      
       protected void onDetachedFromWindow() {
      
          
       super.onDetachedFromWindow();
      
           
       // 不可见时释放Bitmap
      
      
           setImageDrawable(
       null);
      
         
       // 暂停加载
      
      
          mPicasso.pauseTag(
       this);
      
      
      
        }
      
      
       }
      

还有很多api,比如:

  • requestCreator.tag(tag);设置key

  • requestCreator.error(); 设置加载失败图片

  • mPicasso.pauseTag(); 暂停加载

  • mPicasso.resumeTag();恢复加载

  • mPicasso.cancelRequest();取消加载

  • requestCreator.priority()优先级

  • requestCreator..rotate() 旋转之类

下面说几个常用的api

#扩展加载

当然还有一个对通知栏加载的api

通知栏支持

      
       1
      
      
       2
      
      
      
       into(RemoteViews remoteViews, 
       int viewId, 
       int notificationId,Notification notification)
      

widget支持

      
       1
      
      
       into(RemoteViews remoteViews, 
       int viewId, 
       int[] appWidgetIds)
      

第一个是远程视图,第二个view Id 第三个是widget的id数组

预加载

有返回值

      
       1
      
      
       2
      
      
      
       Picasso
       .with(
       context)
       .load(
       url)
       .get()
      

此api可以预先加载图片到disk和内存中,并有返回值Bitmap,此api必须同步调用,不能用UI主线程去调用,通常我们可以用在viewpager中预加载后面index的图片,或者提前拿到目标bitmap来进行业务操作,或者一些效果处理。

无返回值

      
       1
      
      
       2
      
      
      
       Picasso
       .with(
       context)
       .load(
       url)
       .fetch()
      

也有预加载图片功能,此api可以在主线程调用,主要有callback实现,提供失败和成功函数供上层调用。但无法获取的加载好的图片资源。

      
       1
      
      
       2
      
      
       3
      
      
       4
      
      
       5
      
      
       6
      
      
       7
      
      
       8
      
      
       9
      
      
       10
      
      
      
       public 
       static 
       class EmptyCallback implements Callback {
      
      
      
       @Override 
       public void onSuccess() {
      
      
       }
      
      
      
       @Override 
       public void onError() {
      
      
       }
      
      
         }
      
      
       }
      

#取消加载

  • cancelRequest(ImageView imageView)
  • cancelTag(Object obj)
  • cancelRequest(Target)

主要以后上面的三种方式,第一个不明思议,就是取消某个view的加载请求,通常我们在activity死亡时候调用,第三个方法方法是我们取消某个指定的加载action, 譬如一次加载中设置了picasso的 Picasso.with(context).tag()时,就可以用cancelTag(”tag”)取消指定的请求,那么最后一个又是什么,他需要我们加入Tag的包装类 Target来进行回调请求处理。方便开发者上层对取消流程的控制。

      
       1
      
      
       2
      
      
       3
      
      
       4
      
      
       5
      
      
       6
      
      
       7
      
      
       8
      
      
       9
      
      
       10
      
      
       11
      
      
       12
      
      
       13
      
      
       14
      
      
       15
      
      
       16
      
       
       mPicasso
       .cancelRequest(new Target() {
      
      
       @Override
      
      
       public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom afrom) {
      
         
      
      
       }
      
      
      
       @Override
      
      
       public void onBitmapFailed(Drawable errorDrawable) {
      
         
      
      
       }
      
      
      
       @Override
      
      
       public void onPrepareLoad(Drawable placeHolderDrawable) {
      
         
      
      
       }
      
      
       });
      

另外还有一个对通知栏图片的取消的接口

      
       1
      
      
       2
      
      
       3
      
      
       
      
      
       cancelRequest(RemoteViews remoteViews, int viewId)
      

通知栏的VIEW大家都非常熟悉,都是用RemoteViews 来进行转换展现的,那么在通知被cancel时我们就可以直接调用这个取消的方法

后记

总之虽然picasso 并不是最快的图片加载框架,但是他在基本的加载本地和网络图片基础上,还能很好的提供了让我们自我扩展能力,其扩展性和适应性更强,相信你结合了ohttp+ rxJava + Picasso 后你会发现他确实适合你!
如果你爱好glide请看这篇完美的文章:glide系列教程)