zoukankan      html  css  js  c++  java
  • Picaso完美兼容OkHttp3.3,缓存优化两不误

    为何在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消耗内存图

    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.newBuilder()
    .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系列教程)



  • 相关阅读:
    2.5亿!华为成立新公司!
    两年半换第 4 份工作,做个总结
    不懂什么叫编程?
    Google 为什么把几十亿行代码放在一个库?
    IntelliJ 平台 2020 年路线图
    别找了,这是 Pandas 最详细教程了
    MongoDB是什么?看完你就知道了!
    有了这个神器,轻松用 Python 写 APP !
    整理出来几个比较实用的代码对比工具
    学习进度条 第六十一-七十五天 SpringMVC学习笔记
  • 原文地址:https://www.cnblogs.com/lijianming180/p/12262458.html
Copyright © 2011-2022 走看看