Picasso源码分析

摘要

分析Picasso加载图片的流程。以以下代码为主线分析。

1
Picasso.with(context).load(url).placeholder(R.drawable.placeholder).error(R.drawable.error).fit().tag(context).into(imageview);

根据自己写的Test页面,通过picasso以下加载方式,

1
Picasso.with(context).load(url).placeholder(R.drawable.placeholder).error(R.drawable.error).fit().tag(context).into(imageview);

分析picasso加载图片的流程。

缺少的内容:

  1. 默认7种RequestHandler并没有具体分析,对于NetworkRequestHandler应该是分析的重点,因为还涉及到网络库OkHttp的使用;
  2. Cache相关;

对于流程图没有说清楚的补充说明:

加载图片的方式:

  1. 加载一张图片

    1
    Picasso.with(this).load("url").placeholder(R.mipmap.ic_default).into(imageView);
  2. 加载一张图片并设置一个回调接口

    1
    2
    3
    4
    5
    6
    7
    8
    Picasso.with(this).load("url").placeholder(R.mipmap.ic_default).into(imageview, new Callback() {
    @Override
    public void onSuccess() {
    }
    @Override
    public void onError() {
    }
    });
  3. 同步加载一张图片,注意只能在子线程中调用并且Bitmap不会被缓存到内存里

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    new Thread() {
    @Override
    public void run() {
    try {
    final Bitmap bitmap = Picasso.with(getApplicationContext()).load("url").get();
    mHandler.post(new Runnable() {
    @Override
    public void run() {
    imageView.setImageBitmap(bitmap);
    }
    });
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }.start();
  4. 加载一张图片并设置tag,可以通过tag来暂定或者继续加载,可以用于当ListView滚动是暂定加载,停止滚动恢复加载

    1
    2
    3
    Picasso.with(this).load("url").tag(mContext).into(imageView);
    Picasso.with(this).pauseTag(mContext);
    Picasso.with(this).resumeTag(mContxt);
  5. 加载一张图片并自适应imageView的大小,如果imageView设置了wrap_content,会显示不出来,直到该ImageView的LayoutParams被设置而且调用了该View的ViewTreeObserver.OnPreDrawListener回调接口后才会显示

    1
    Picasso.with(this).load("url").fit().into(imageView);
  6. 加载一张图片并按照指定尺寸以centerInside()的形式缩放,并设置加载的优先级为高。注意centerInside()或centerCrop()只能同时使用一种,而且必须指定resize()或者resizeDimen()

    1
    Picasso.with(this).load("url").resize(400,400).centerInside().priority(Picasso.Priority.HIGH).into(imageView);
  7. 加载一张图片旋转并且添加一个Transformation,可以对图片进行各种变化处理,例如圆形头像.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Picasso.with(this).load("url").rotate(10).transform(new Transformation() {
    @Override
    public Bitmap transform(Bitmap source) {
    //处理Bitmap
    return null;
    }
    @Override
    public String key() {
    return null;
    }
    }).into(imageView);

注释的补充说明:

new Picasso.Builder(context)#build()方法里
  1. PicassoExecutorService默认有3个执行线程,corePoolSize=maximumPoolSize=3,会根据网络状况自动切换线程数

  2. 除了添加可以自定义的extraRequestHandlers,另外默认添加了7个RequestHandler分别用来处理加载不同来源的资源,可能是Resource里的,也可能是File,也可能是来源于网络的资源;

  3. 在调用into()方法之前,所有的操作都是在设定我们需要处理的参数,真正的操作都是有into()方法引起的;

  4. deferred=true表示fit()模式,即表示延时加载,fit()模式是适应target的宽高加载,所以并不能手动设置resize,如果设置就抛出异常;和加载图片方式中6不同;

    1
    picasso.defer(target, new DeferredRequestCreator(this, target, callback));

该方法监听ImageView的ViewTreeObserver.OnPreDrawListener接口,一旦ImageView的宽高被赋值,就按照ImageView的宽高继续加载;

  1. Picasso#complete(BitmapHunter hunter) -> Picasso#deliverAction(Bitmap result, LoadedFrom from, Action action) -> action#complete(Bitmap result, Picasso.LoadedFrom from)在这个流程中,我们在RequestCreator#into(ImageView target, Callback callback)方法中创建的是ImageAction,所以最终调用到ImageAction#complete(Bitmap result, Picasso.LoadedFrom from)的方法中。通过了PicassoDrawable.setBitmap()将Bitmap设置到ImageView上,最后并回调callback接口,这里使用PicassoDrawabl来设置Bitmap是因为Picasso自带渐变的加载动画,这里就是处理渐变动画的地方

Picasso的缓存逻辑:

LruCache(使用LinkedHashMap实现,和android.util.LruCache中的实现类似)是基于内存的缓存,硬盘缓存的控制在OkHttp中,具体使用DiskLruCache

  • 加载图片的url及图片属性在内存缓存中存在,如果存在读取内存
  • 如果不命中内存缓存,加载图片到disk硬盘缓存和memory内存缓存中
  • 内存和硬盘缓存相互独立

个人评价:

Picasso代码量小,维护起来比较方便,但扩展性比较弱,也没有Gif支持。

参考

  1. Picasso 源码分析
  2. Picasso源代码分析
  3. Picasso源码解析
  4. Picasso源码分析
  5. Picasso内部缓存