Picasso 源码分析(1)

前言

大家好,我是小黑(linheimx),一位快乐的2b程序员。

一段简洁的代码

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

这段代码将 异步的 加载url指定的资源到 imageview 上。

接下来,我们将以这段代码作为 切入点 ,来分析Picasso


1. with()

public static Picasso with(@NonNull Context context) {
    // 1.check
    if (context == null) {
      throw new IllegalArgumentException("context == null");
    }
    // 2. sync code
    if (singleton == null) {
      synchronized (Picasso.class) {
        if (singleton == null) {
          // 构造
          singleton = new Builder(context).build();
        }
      }
    }
    return singleton;
  } 

这段代码是为了获取 Picasso 实例。
有以下关键点:

  1. synchronized

当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

  1. builder

builder构造模式,建造Picasso所需要的信息。

细看Builder

builder 可以构建的信息有:

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;

build() 方法

 /** Create the {@link Picasso} instance. */
    public Picasso build() {
      Context context = this.context;

      if (downloader == null) {
        downloader = Utils.createDefaultDownloader(context);//待详细分析
      }
      if (cache == null) {
        cache = new LruCache(context);//待详细分析
      }
      if (service == null) {
        service = new PicassoExecutorService();//待详细分析
      }
      if (transformer == null) {
        transformer = RequestTransformer.IDENTITY;
      }

      Stats stats = new Stats(cache);

      Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);

      return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
          defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
    }

注意:

  1. Downloader downloader.

load:从外部存储,缓存或是网络来加载图片资源。

  1. Cache cache

内存缓存:存储最近使用最多的图片。

  1. ExecutorService service

异步线程并发执行器

以上3个小东西,构造了 Dispatcher。说明这个 Dispatcher 比较重要。(暂不分析)

2. load()

load 指定加载资源路径

1. load(@Nullable String path) // 可以是 url,file资源(file:),content资源(content:),android资源(resource:)
2. load(@NonNull File file) // image file
3. load(@DrawableRes int resourceId) // drawable resource ID

最终执行如下方法:

public RequestCreator load(@Nullable Uri uri) {
    return new RequestCreator(this, uri, 0);
}   

我们可以看到 load的最终目的是,构造一个 RequestCreator
RequestCreator 从名字可以看出 它 可以构建Request,它的一切字段,方法都是为Request来服务的。

我们大致看一下 RequestCreator:

  1. 字段:
    // 静态原子 int
    private static final AtomicInteger nextId = new AtomicInteger();

    private final Picasso picasso;

    private final Request.Builder data;//重要!!!

    private boolean noFade;
    private boolean deferred;// 推迟,延期
    private boolean setPlaceholder = true;
    private int placeholderResId;
    private int errorResId;
    private Drawable placeholderDrawable;
    private Drawable errorDrawable;
    private int memoryPolicy;
    private int networkPolicy;

    private Object tag;
  1. 方法

字段是私有的,很多方法是为字段服务的。在方法里面可做相关的非法检查等。

noPlaceholder()
placeholder(@DrawableRes int placeholderResId)
placeholder(@NonNull Drawable placeholderDrawable)

error(@DrawableRes int errorResId) 
error(@NonNull Drawable errorDrawable)

tag(@NonNull Object tag) 
fit() // Attempt to resize the image to fit exactly into the target {@link ImageView}'s bounds. This will result in delayed execution of the request until the {@link ImageView} has been laid out.
unfit() 

resizeDimen(int targetWidthResId, int targetHeightResId)
RequestCreator resize(int targetWidth, int targetHeight) // Resize the image to the specified size in pixels.
centerCrop()
centerInside()

等等。。。。。

注意:这里很多方法 都有return this--->便于链式的方法调用(写出来的代码方便直观!)
注意:这些方法很多都是为request服务的(Request 需要构建,Request.Builder data提供构造信息)

3. into()

RequestCreator 中很重要的方法!!!
into有几个重载:

  1. into(@NonNull Target target)
  2. into( @NonNull RemoteViews remoteViews, @IdRes int viewId, int notificationId, @NonNull Notification notification)
  3. into(ImageView target)
  4. into(ImageView target, Callback callback)

下面主要分析: into(ImageView target, Callback callback)

public void into(ImageView target, Callback callback) {
        long started = System.nanoTime();
        checkMain();

        if (target == null) {
            throw new IllegalArgumentException("Target must not be null.");
        }

        if (!data.hasImage()) {
            picasso.cancelRequest(target);
            if (setPlaceholder) {
                setPlaceholder(target, getPlaceholderDrawable());
            }
            return;
        }

        if (deferred) {
            if (data.hasSize()) {
                throw new IllegalStateException("Fit cannot be used with resize.");
            }
            int width = target.getWidth();
            int height = target.getHeight();
            if (width == 0 || height == 0 || target.isLayoutRequested()) {
                if (setPlaceholder) {
                    setPlaceholder(target, getPlaceholderDrawable());
                }
                picasso.defer(target, new DeferredRequestCreator(this, target, callback));
                return;
            }
            data.resize(width, height);
        }

        Request request = createRequest(started);
        String requestKey = createKey(request);

        if (shouldReadFromMemoryCache(memoryPolicy)) {
            Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
            if (bitmap != null) {
                picasso.cancelRequest(target);
                setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
                if (picasso.loggingEnabled) {
                    log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
                }
                if (callback != null) {
                    callback.onSuccess();
                }
                return;
            }
        }

        if (setPlaceholder) {
            setPlaceholder(target, getPlaceholderDrawable());
        }

        Action action =
                new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
                        errorDrawable, requestKey, tag, callback, noFade);

        picasso.enqueueAndSubmit(action);
    }

简单的画了下流程图,如下:

Sample Flowchart Template.png

简单直接的说几个关键点:

a. 从内存缓存中读取数据

if (shouldReadFromMemoryCache(memoryPolicy)) {
            Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
            if (bitmap != null) {
                picasso.cancelRequest(target);
                setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
                if (picasso.loggingEnabled) {
                    log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
                }
                if (callback != null) {
                    callback.onSuccess();
                }
                return;
            }
 }
  1. shouldReadFromMemoryCache() 此处方法
    检查 图片的内存策略

被定义在 enum MemoryPolicy中,枚举类 MemoryPolicy负责定义,负责do some job,比较有意思。

  1. Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);

Picasso 是个大管家。持有cache,方便对内存中的bitmap进行管理。

  1. picasso.cancelRequest(target);

Picasso 是个大管家。还可以cancel请求呢。

  1. setbitmap,calback
    有了bitmap,就可以显示啦。

setbitmap()这个方法很有意思,后面会聊它的(// todo analysis)

b. 再深入一点(亚麻跌):

这个Picasso对 tagert的请求管理。
picasso.cancelRequest(target): 取消与target对象相关的所有请求

 public void cancelRequest(@NonNull ImageView view) {
    // checkMain() is called from cancelExistingRequest()
    if (view == null) {
      throw new IllegalArgumentException("view cannot be null.");
    }
    cancelExistingRequest(view);// go
  }

private void cancelExistingRequest(Object target) {
    checkMain();
    Action action = targetToAction.remove(target);// 注意:targetToAction
    if (action != null) {
      /////////////////////// true cancel //////////////////////
      action.cancel();
      dispatcher.dispatchCancel(action);
       /////////////////////// true cancel //////////////////////
    }

    
    if (target instanceof ImageView) {
      ImageView targetImageView = (ImageView) target;

      ////////////////////////////  defer cancel   ///////////////////////////
      DeferredRequestCreator deferredRequestCreator =
          targetToDeferredRequestCreator.remove(targetImageView);
      if (deferredRequestCreator != null) {
        deferredRequestCreator.cancel();
      }
      ////////////////////////////  defer cancel   ///////////////////////////
    }
  }

我们着重看一下:targetToAction

final Map<Object, Action> targetToAction;

他是一个map,so有了map这个容器,我们来管理 target 这个key 对应的所有 action 岂不是非常容易!
当然相关的 任务 的执行是由 final Dispatcher dispatcher; 来管理的。这个下面细说。
再次判断,该target若是 imgeview,取消相关的其延迟请求,这个延迟(defer),后面说。

c. defer

defer: 延迟,延期。
为什么会有这个概念?
看下 RequestCreator中的这个方法

/** 
Attempt to resize the image to fit exactly into the target ImageView's bounds. 
This will result in delayed execution of the request until the ImageView has been laid out.
Note: This method works only when your target is an ImageView.
*/
        
        
public RequestCreator fit() {
        deferred = true;

        //////////////////////////////  return this  ///////////////////////////
        return this;
 }

你下载下来的 bitmap的大小和 imageview的大小不匹配。为了让bitmap适应 imageview的大小。
等你imageview被布局完毕后(imagview的大小固定了),这个延迟的请求才会被执行。这样他们就fit了。

Picasso 这个大管家。接手了 imagview 这个target的 延迟请求:

picasso.defer(target, new DeferredRequestCreator(this, target, callback));
---------------
 void defer(ImageView view, DeferredRequestCreator request) {
    // If there is already a deferred request, cancel it.
    if (targetToDeferredRequestCreator.containsKey(view)) {
      cancelExistingRequest(view);
    }
    targetToDeferredRequestCreator.put(view, request);
  }
---------------------------------
final Map<ImageView, DeferredRequestCreator> targetToDeferredRequestCreator;// 是个map哦。

d. 创建 Request 和 request key

  1. 创建request
  /**
     * Create the request optionally passing it through the request transformer.
     */
    private Request createRequest(long started) {
        int id = nextId.getAndIncrement();

        Request request = data.build();
        request.id = id;// 一个request,一个id
        request.started = started;

        boolean loggingEnabled = picasso.loggingEnabled;
        if (loggingEnabled) {
            log(OWNER_MAIN, VERB_CREATED, request.plainId(), request.toString());
        }

        Request transformed = picasso.transformRequest(request);
        if (transformed != request) {
            // If the request was changed, copy over the id and timestamp from the original.
            transformed.id = id;
            transformed.started = started;

            if (loggingEnabled) {
                log(OWNER_MAIN, VERB_CHANGED, transformed.logId(), "into " + transformed);
            }
        }

        return transformed;
    }

builder 创建了request(request需要好多信息啊!)

  /** Create the immutable {@link Request} object. */
    public Request build() {
      if (centerInside && centerCrop) {
        throw new IllegalStateException("Center crop and center inside can not be used together.");
      }
      if (centerCrop && (targetWidth == 0 && targetHeight == 0)) {
        throw new IllegalStateException(
            "Center crop requires calling resize with positive width and height.");
      }
      if (centerInside && (targetWidth == 0 && targetHeight == 0)) {
        throw new IllegalStateException(
            "Center inside requires calling resize with positive width and height.");
      }
      if (priority == null) {
        priority = Priority.NORMAL;
      }
  
      // 需要好多信息啊? 什么时候要使用这些信息呢???
      return new Request(uri, resourceId, stableKey, transformations, targetWidth, targetHeight,
          centerCrop, centerInside, onlyScaleDown, rotationDegrees, rotationPivotX, rotationPivotY,
          hasRotationPivot, purgeable, config, priority);
    }
  }

Request 有着唯一的 key(String类型),key的生成规则:

 static String createKey(Request data) {
    String result = createKey(data, MAIN_THREAD_KEY_BUILDER);
    MAIN_THREAD_KEY_BUILDER.setLength(0);
    return result;
  }
-------------
static String createKey(Request data, StringBuilder builder) {

    //-------------------------------------> 1
    if (data.stableKey != null) {// 1. stable key
      builder.ensureCapacity(data.stableKey.length() + KEY_PADDING);
      builder.append(data.stableKey);
    } else if (data.uri != null) {// 2. uri
      String path = data.uri.toString();
      builder.ensureCapacity(path.length() + KEY_PADDING);
      builder.append(path);
    } else {// 3. resource id
      builder.ensureCapacity(KEY_PADDING);
      builder.append(data.resourceId);
    }

    builder.append(KEY_SEPARATOR);

    //---------------------------------------> 2
    if (data.rotationDegrees != 0) {
      builder.append("rotation:").append(data.rotationDegrees);
      if (data.hasRotationPivot) {
        builder.append('@').append(data.rotationPivotX).append('x').append(data.rotationPivotY);
      }
      builder.append(KEY_SEPARATOR);
    }

    //---------------------------------------> 3
    if (data.hasSize()) {
      builder.append("resize:").append(data.targetWidth).append('x').append(data.targetHeight);
      builder.append(KEY_SEPARATOR);
    }

    //---------------------------------------> 4
    if (data.centerCrop) {
      builder.append("centerCrop").append(KEY_SEPARATOR);
    } else if (data.centerInside) {
      builder.append("centerInside").append(KEY_SEPARATOR);
    }

    //---------------------------------------> 5
    if (data.transformations != null) {
      //noinspection ForLoopReplaceableByForEach
      for (int i = 0, count = data.transformations.size(); i < count; i++) {
        builder.append(data.transformations.get(i).key());
        builder.append(KEY_SEPARATOR);
      }
    }

    return builder.toString();
  }

e. Action

一个request被包装成了 action 被执行。

Action action =
                new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
                        errorDrawable, requestKey, tag, callback, noFade);

picasso.enqueueAndSubmit(action);

现在问题来了:

  1. action 是什么?
  2. action 是什么?
  3. action 是什么?

看一下Action这个类:

  final Picasso picasso;
  final Request request;
  final WeakReference<T> target;
  final boolean noFade;
  final int memoryPolicy;
  final int networkPolicy;
  final int errorResId;
  final Drawable errorDrawable;
  final String key;
  final Object tag;

  boolean willReplay;
  boolean cancelled;

包裹里这么多信息,他到底想干嘛?
再看下他定义的方法:

///////////////////////////////////////////////////////////////
  abstract void complete(Bitmap result, Picasso.LoadedFrom from);
  abstract void error();
  void cancel() {
    cancelled = true;
  }
///////////////////////////////////////////////////////////////

  Request getRequest() {
    return request;
  }

  T getTarget() {
    return target == null ? null : target.get();
  }

  String getKey() {
    return key;
  }

  boolean isCancelled() {
    return cancelled;
  }

  boolean willReplay() {
    return willReplay;
  }

  int getMemoryPolicy() {
    return memoryPolicy;
  }

  int getNetworkPolicy() {
    return networkPolicy;
  }

  Picasso getPicasso() {
    return picasso;
  }

  Priority getPriority() {
    return request.priority;
  }

从以上方法猜测,action关注:

他包裹的请求被 处理完成后(complete),或是失败后的动作处理(error),或是 cancel的处理

再看action的子类:

Paste_Image.png

我们关注 ImageViewAction,看下他怎么处理 complete,error,cancel:

@Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
    if (result == null) {
      throw new AssertionError(
          String.format("Attempted to complete action with no result!\n%s", this));
    }

    ImageView target = this.target.get();
    if (target == null) {
      return;
    }

    Context context = picasso.context;
    boolean indicatorsEnabled = picasso.indicatorsEnabled;
    PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);

    if (callback != null) {
      callback.onSuccess();
    }
  }

----------------
 @Override public void error() {
    ImageView target = this.target.get();
    if (target == null) {
      return;
    }
    Drawable placeholder = target.getDrawable();
    if (placeholder instanceof AnimationDrawable) {
      ((AnimationDrawable) placeholder).stop();
    }
    if (errorResId != 0) {
      target.setImageResource(errorResId);
    } else if (errorDrawable != null) {
      target.setImageDrawable(errorDrawable);
    }

    if (callback != null) {
      callback.onError();
    }
  }

---------------------
@Override void cancel() {
    super.cancel();
    if (callback != null) {
      callback = null;
    }
  }

接下来我们来关注,这个action由谁来操作的。

f. 管理action

你猜是谁?

Picasso 这个大管家

picasso.enqueueAndSubmit(action);
void enqueueAndSubmit(Action action) {
    Object target = action.getTarget();
    if (target != null && targetToAction.get(target) != action) {
      // This will also check we are on the main thread.
      cancelExistingRequest(target);
      targetToAction.put(target, action); // action与 target相关联
    }
    submit(action);
  }
void submit(Action action) {  dispatcher.dispatchSubmit(action);}

dispatcher 来处理这个action。

dispatcher 由Picasso持有,它的构建在Picasso 的builer下构建(在build下我也提到过~)

那么问题来了:
Dispatcher是什么?

留待下文分解。

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

推荐阅读更多精彩内容