Picasso源码分析

一 介绍

Square公司开源的图片加载库。优点是功能还算完善,能满足基本的图片加载需求,使用简单,体量轻易推倒。
官方链接:http://square.github.io/picasso/
Git: https://github.com/square/picasso

二 分析

1. 简单调用
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);
2. 流程图
Paste_Image.png
3. 核心类分析

(主流程顺序,以下贴出的代码都是经过筛选了比较重要的代码)

①Picasso
-- 单例,构造ExecutorService线程池(默认3个线程),Dispatcher,LruCache,Downloader(调用okhttp),其他基本没干什么正事。

//Builder模式构建Picasso实例
public static class Builder {
  private final Context context;  
  private Downloader downloader;  
  private ExecutorService service;  
  private Cache cache;  
  private Listener listener;  
  private RequestTransformer transformer;  
  private List<RequestHandler> requestHandlers;  
  private Bitmap.Config defaultBitmapConfig;  
  private boolean indicatorsEnabled;  
  private boolean loggingEnabled;  
  /** Start building a new {@link Picasso} instance. */ 
  public Builder(Context context) {   
     if (context == null) {         
        throw new IllegalArgumentException("Context must not be null.");  
     }
     this.context = context.getApplicationContext();  
  }
  ....//省略以下代码
}

②RequestCreator
-- 主流程调用的方法into(), 里面做的最重要的两件事:1.创建Request, 2.把Action加到Dispatcher队列。(Request, Action, Dispatcher几个概念后面会做说明)

public void into(ImageView target, Callback callback) {
   
    Request request = createRequest(started);
    String requestKey = createKey(request);
    //查看内存缓存是否已经有了,有的话直接set,不用再去构造Action获取了
    if (shouldReadFromMemoryCache(memoryPolicy)) {
      Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
      if (bitmap != null) {
        picasso.cancelRequest(target);
        setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
        if (callback != null) {
          callback.onSuccess();
        }
        return;
      }
    }
  
    if (setPlaceholder) {
      setPlaceholder(target, getPlaceholderDrawable());
    }
    //构造Action
    Action action =
        new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
            errorDrawable, requestKey, tag, callback, noFade);
    //扔给任务调度器
    picasso.enqueueAndSubmit(action);
  }

③Request
-- 请求model,存储请求相关的数据,包括Transformation list,部分内置的转换属性,Bitmap压缩属性, 任务优先级, 图片资源的相关信息等。

//类结构,原生注释很详细
public final class Request {
  private static final long TOO_LONG_LOG = TimeUnit.SECONDS.toNanos(5);

  /** A unique ID for the request. */
  int id;
  /** The time that the request was first submitted (in nanos). */
  long started;
  /** The {@link NetworkPolicy} to use for this request. */
  int networkPolicy;

  /**
   * The image URI.
   * <p>
   * This is mutually exclusive with {@link #resourceId}.
   */
  public final Uri uri;
  /**
   * The image resource ID.
   * <p>
   * This is mutually exclusive with {@link #uri}.
   */
  public final int resourceId;
  /**
   * Optional stable key for this request to be used instead of the URI or resource ID when
   * caching. Two requests with the same value are considered to be for the same resource.
   */
  public final String stableKey;
  /** List of custom transformations to be applied after the built-in transformations. */
  public final List<Transformation> transformations;
  /** Target image width for resizing. */
  public final int targetWidth;
  /** Target image height for resizing. */
  public final int targetHeight;
  /**
   * True if the final image should use the 'centerCrop' scale technique.
   * <p>
   * This is mutually exclusive with {@link #centerInside}.
   */
  public final boolean centerCrop;
  /**
   * True if the final image should use the 'centerInside' scale technique.
   * <p>
   * This is mutually exclusive with {@link #centerCrop}.
   */
  public final boolean centerInside;
  public final boolean onlyScaleDown;
  /** Amount to rotate the image in degrees. */
  public final float rotationDegrees;
  /** Rotation pivot on the X axis. */
  public final float rotationPivotX;
  /** Rotation pivot on the Y axis. */
  public final float rotationPivotY;
  /** Whether or not {@link #rotationPivotX} and {@link #rotationPivotY} are set. */
  public final boolean hasRotationPivot;
  /** True if image should be decoded with inPurgeable and inInputShareable. */
  public final boolean purgeable;
  /** Target image config for decoding. */
  public final Bitmap.Config config;
  /** The priority of this request. */
  public final Priority priority;
....//省略以下代码

④Action
-- 可以看做单个图片加载的任务model,抽象类。
实现类可见下图,常用的是ImageViewAction,它会在抽象方法complete真正实现把Bitmap给ImageView的行为。


Paste_Image.png
abstract class Action<T> {
  final Picasso picasso;  //picasso单例
  final Request request;  //当前任务的request
  final WeakReference<T> target;  //目标,imageview
  final boolean noFade;  //是否渐进渐出
  final int memoryPolicy;  //内存缓存策略
  final int networkPolicy;  //网络缓存策略
  final int errorResId;  //错误占位图资源id
  final Drawable errorDrawable;  //错误占位图资源drawable
  final String key; //内存缓存的key
  final Object tag; //dispatcher的任务标识

  boolean willReplay;  //是否再次放到任务队列中
  boolean cancelled;  //是否取消

  //Bitmap加载成功回调
  abstract void complete(Bitmap result, Picasso.LoadedFrom from);
  //加载失败回调
  abstract void error();
....//省略以下代码

⑤Dispatcher
-- 看名字就看出来是个任务分发调度器,

dispatcher方法流程:

Paste_Image.png
class Dispatcher {
  
  final DispatcherThread dispatcherThread;  //dispatcher线程,主要作用是获取looper给handler用
  final Context context;  
  final ExecutorService service;  //picasso创建的线程池,唯一
  final Downloader downloader;  //下载器,唯一
  final Map<String, BitmapHunter> hunterMap;  //所有的BitmapHunter map
  final Map<Object, Action> failedActions;
  final Map<Object, Action> pausedActions;
  final Set<Object> pausedTags;
  final Handler handler;  //dispatcher线程handler,任务调用使用
  final Handler mainThreadHandler;  //主线程handler,用于complete后的行为
  final Cache cache;  //内存缓存
  final Stats stats;
  final List<BitmapHunter> batch;  //批量complete状态的BitmapHunter
  final NetworkBroadcastReceiver receiver; //监控网络状态,用于网络缓存策略
  
  //从Action构造BitmapHunter并且放到线程池中
  void performSubmit(Action action, boolean dismissFailed) {
    if (pausedTags.contains(action.getTag())) {
      pausedActions.put(action.getTarget(), action);
      if (action.getPicasso().loggingEnabled) {
        log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),
            "because tag '" + action.getTag() + "' is paused");
      }
      return;
    }

    BitmapHunter hunter = hunterMap.get(action.getKey());
    if (hunter != null) {
      hunter.attach(action);
      return;
    }

    //构造request
    hunter = forRequest(action.getPicasso(), this, cache, stats, action);
   //执行hunter
    hunter.future = service.submit(hunter);
    hunterMap.put(action.getKey(), hunter);
    if (dismissFailed) {
      failedActions.remove(action.getTarget());
    }

    if (action.getPicasso().loggingEnabled) {
      log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
    }
  }
  //单个任务完成 ,batch聚合成功任务结果
  void performComplete(BitmapHunter hunter) {
    if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
      cache.set(hunter.getKey(), hunter.getResult());
    }
    hunterMap.remove(hunter.getKey());
    batch(hunter);
    if (hunter.getPicasso().loggingEnabled) {
      log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), "for completion");
    }
  }
  //给picasso主线程处理批量结果
  void performBatchComplete() {
    List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);
    batch.clear();
    mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
    logBatch(copy);
  }

⑥BitmapHunter
-- 比较简单,实现了Runnable接口, 重点关注hunt()方法。

Bitmap hunt() throws IOException {
    Bitmap bitmap = null;
   //省略从内存读bitmap代码...

    //从requestHandler 同步load result,RequestHandler见后面分析
    RequestHandler.Result result = requestHandler.load(data, networkPolicy);
    if (result != null) {
      bitmap = result.getBitmap();
      // If there was no Bitmap then we need to decode it from the stream.
      if (bitmap == null) {
        InputStream is = result.getStream();
        bitmap = decodeStream(is, data);
      }
    }

    if (bitmap != null) {
      //如果需要做内置的转换,则转出相应的bitmap
      if (data.needsTransformation() || exifOrientation != 0) {
            bitmap = transformResult(data, bitmap, exifOrientation);
          }
      //如果需要做自定义的转换,则转出相应的bitmap
          if (data.hasCustomTransformations()) {
            bitmap = applyCustomTransformations(data.transformations, bitmap);
            }
          }
        }
      }
    }
    return bitmap;
  }

⑦RequestHandler
-- 抽象类,抽象方法load() 方法获取Result, Result里有bitmap和inputstream

实现类:


Paste_Image.png

我们来看下最常用的NetworkRequestHandler

class NetworkRequestHandler extends RequestHandler {
  //根据request加载出bitmap或者inputstream
  @Override public Result load(Request request, int networkPolicy) throws IOException {
   
    //走Downloader请求,downloader内部是okhttp实现
    Response response = downloader.load(request.uri, request.networkPolicy);

    Bitmap bitmap = response.getBitmap();
    if (bitmap != null) {
      return new Result(bitmap, loadedFrom);
    }

    InputStream is = response.getInputStream();
    if (is == null) {
      return null;
    }
  }

BitmapHunter里List<RequestHandler>是在picasso的构造方法中注入的。

Picasso类相关代码:

    List<RequestHandler> allRequestHandlers =
        new ArrayList<RequestHandler>(builtInHandlers + extraCount);

    allRequestHandlers.add(new ResourceRequestHandler(context));
    if (extraRequestHandlers != null) {
      allRequestHandlers.addAll(extraRequestHandlers);
    }
    allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
    allRequestHandlers.add(new MediaStoreRequestHandler(context));
    allRequestHandlers.add(new ContentStreamRequestHandler(context));
    allRequestHandlers.add(new AssetRequestHandler(context));
    allRequestHandlers.add(new FileRequestHandler(context));
    allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
    requestHandlers = Collections.unmodifiableList(allRequestHandlers);

BitmapHunter类相关代码:

    List<RequestHandler> requestHandlers = picasso.getRequestHandlers();

    for (int i = 0, count = requestHandlers.size(); i < count; i++) {
      RequestHandler requestHandler = requestHandlers.get(i);
      //责任链模式,第一个可用的handler直接使用
      if (requestHandler.canHandleRequest(request)) {
        return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
      }
    }

三 内部机制及优缺点

1、提供了双缓存,Memory缓存使用LinkedHashMap实现,提供命中率统计。
Disk缓存使用的是okhttp内部的,无法设置其他的Disk缓存,要想实现只能自己改造代码了。(OkHttpDownloader和OkHttp3Downloader)
2、自定制Bitmap转换,需要继承Transformation接口,在构造RequestCreator的时候add进去。类似切圆角,高斯模糊等需求都在这里完成。Picasso本身提供了Resize,CenterCrop,Rotate,CenterInside转换方式。
3、图片渲染只提供了fade方式, 见PicassoDrawable。
4、可load的图片资源种类:Resource,Asset, File, Http, (Https之前版本不支持,从最新依赖的okhttp 3.0.1看应该是支持的,未测试过)
5、不支持Gif, 支持webp。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容