Picasso源码分析(五):into方法追本溯源和责任链模式创建BitmapHunter

Picasso源码分析(一):单例模式、建造者模式、面向接口编程

Picasso源码分析(二):默认的下载器、缓存、线程池和转换器

Picasso源码分析(三):快照功能实现和HandlerThread的使用

Picasso源码分析(四):不变模式、建造者模式和Request的预处理

Picasso源码分析(五):into方法追本溯源和责任链模式创建BitmapHunter

Picasso源码分析(六):BitmapHunter与请求结果的处理

Picasso异步加载图片流程回顾

首先通过with方法创建单例Picasso对象

  public static Picasso with(Context context) {
    if (singleton == null) {
      synchronized (Picasso.class) {
        if (singleton == null) {
          singleton = new Builder(context).build();
        }
      }
    }
    return singleton;
  }

在单例模式里边又通过建造者模式构建Picasso对象,并不是直接通过new创建。

加载图片需要通过load方法告诉Picasso图片的地址

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

load方法构造了一个RequestCreator对象并返回。

RequestCreator提供了图片的加载和变换规则,这些变换规则包括设置占位图,图片压缩裁剪,显示模式设置等,提供的设置方法如下:

在加载和变换规则设置完成后,最终通过into方法进行网络异步请求

into方法分析

使用into方法就是将加载到的图片注入(显示)在控件上

  /**
   * Asynchronously fulfills the request into the specified  ImageView.
   * Note: This method keeps a weak reference to the  ImageView instance and will
   * automatically support object recycling.
   */
  public void into(ImageView target) {
    into(target, null);
  }

into(ImageView target)方法调用了into的两个参数的重载方法,并设置第二个方法参数为null,表示用户没有传入加载成功或失败后的回调,所以不执行回调。

  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) {
        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);
  }

into方法首先记录了请求的开始时间戳,随后会根据这个事件戳创建Request请求。

接着检查了是否在主线程调用into方法,控件是否为null,在非主线程调用into方法,或者控件为null,均会抛出异常,对于程序中的异常进行要早发现早治疗。

之后判断该请求是否设置了uri或者资源id,如果没有的话就取消Picasso在此控件上的请求,然后设置占位图并返回。

    private final Request.Builder data;
    ...
    boolean hasImage() {
      return uri != null || resourceId != 0;
    }
    ....
    if (!data.hasImage()) {
      picasso.cancelRequest(target);
      if (setPlaceholder) {
        setPlaceholder(target, getPlaceholderDrawable());
      }
      return;
    }

接着判断用户是否调用了fit方法,让图片自适应控件大小,因为必须等到控件完全加载显示出来后才能够获取控件占据的空间大小,因此需要延迟执行调用了fit方法的请求,控件显示出来后再执行。

  public RequestCreator fit() {
    deferred = true;
    return this;
  }
  ...
  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) {
        if (setPlaceholder) {
          setPlaceholder(target, getPlaceholderDrawable());
        }
        picasso.defer(target, new DeferredRequestCreator(this, target, callback));
        return;
      }
      data.resize(width, height);
    }

从源码中可以看到如果调用了fit自适应压缩图片,就不能调用resize方法手动压缩图片,否则抛出”Fit cannot be used with resize.”的IllegalStateException异常;

当然调用了fit方法并不是一定会延迟处理,只有在控件没有绘制出来的时候才会延迟处理,没有绘制出来的控件调用getWidth()获取到的宽度是0或者调用getHeight()获取到的高度为0,此时需要设置占位图。如果获取到的宽高都不是0,则说明控件已经绘制出来,此时不必设置延迟处理。

假如需要延迟处理的话,就交给Picasso的defer方法延迟处理,然后直接返回

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

如果不需要延迟处理,说明获取到的控件的宽高都不是0,那么就通过resize方法对图片进行尺寸压缩

      data.resize(width, height);

接着要为此次请求创建一个Request类型的对象,并创建相应的requestKey

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

然后如果可以从缓存中获取图片的话,就根据requestkey

从缓存中获取对应的Bitmap对象,获取成功的话就取消此次请求,将获取到的bitmap显示在控件上,如果设置了回调的话还要进行请求成功的回调,然后直接返回。

      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());
    }

然后终于开始网络请求了。

通过构造一个ImageViewAction类型的对象action,然后将此action递交给Picasso,让Picasso去调度Dispatcher进行相应的处理。

如何构建一个Request请求

在into方法中通过createRequest方法创建了一个请求,接下来观察下该方法是如何实现的。

  /** 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.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;
}

首先通过原子整数AtomicInteger类型的nextId对象创建一个唯一的请求id

    int id = nextId.getAndIncrement();

接着调用Request.Builder的build方法构建Request对象(建造者模式)。

Request类有三个属性没有被final修饰

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

分别是请求id,请求时间started和网络策略networkPolicy,因为这三个属性是非final的,所以建造者模式中的build方法并未为Request对象设置这三个属性,所以需要创建出Request对象后直接设置这些属性。

    Request request = data.build();
    request.id = id;
    request.started = started;

创建Request对象后还需要对该Request进行加工变换处理,变换后返回Request对象。

为请求Request创建缓存的key

在into方法中,会根据创建的Request对象创建一个key,这个key就是用来创建和读取缓存的key。

    String requestKey = createKey(request);
  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) {
    if (data.stableKey != null) {
      builder.ensureCapacity(data.stableKey.length() + KEY_PADDING);
      builder.append(data.stableKey);
    } else if (data.uri != null) {
      String path = data.uri.toString();
      builder.ensureCapacity(path.length() + KEY_PADDING);
      builder.append(path);
    } else {
      builder.ensureCapacity(KEY_PADDING);
      builder.append(data.resourceId);
    }
    builder.append(KEY_SEPARATOR);

    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);
    }
    if (data.hasSize()) {
      builder.append("resize:").append(data.targetWidth).append(‘x‘).append(data.targetHeight);
      builder.append(KEY_SEPARATOR);
    }
    if (data.centerCrop) {
      builder.append("centerCrop").append(KEY_SEPARATOR);
    } else if (data.centerInside) {
      builder.append("centerInside").append(KEY_SEPARATOR);
    }

    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();
}

可见为请求创建key的策略是这样的,先设置key的头部,三种互斥情况,请求的stableKey属性,请求的uri,资源id。

    if (data.stableKey != null) {
      builder.ensureCapacity(data.stableKey.length() + KEY_PADDING);
      builder.append(data.stableKey);
    } else if (data.uri != null) {
      String path = data.uri.toString();
      builder.ensureCapacity(path.length() + KEY_PADDING);
      builder.append(path);
    } else {
      builder.ensureCapacity(KEY_PADDING);
      builder.append(data.resourceId);
    }

之后添加一个换行符(之后的各种信息之间也以换行符分割)

    builder.append(KEY_SEPARATOR);

如果该请求设置了旋转角度的话,key中要包含该旋转信息

    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);
    }

如果该请求调用了resize方法进行手动压缩图片,需要把设置的大小也要添加进缓存的key中,之后还有显示位置信息和一系列变换规则都会添加到key中。

提交请求任务Action给Picasso调度

在into方法的最后会创建一个ImageViewAction对象,该对象继承自Action,Action是一个抽象类,表示一个获取图片的抽象动作。抽象类Action有两个抽象方法必须由子类实现,分别是获取图片成功的complete方法和获取图片失败的error方法。ImageViewAction对这两个抽象方法进行了重写,获取图片成功就在控件显示获取到的图片,失败就显示占位图。

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

    picasso.enqueueAndSubmit(action);

into方法的最后把创建的action交给了Picasso

  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);
    }
    submit(action);
  }

Picasso会判断该action对应的控件上是不是已经有请求在进行了,有的话就取消之前的请求,因为一个控件上没必要进行多次请求,只保留最后一次的请求即可,节约资源。

  void submit(Action action) {
    dispatcher.dispatchSubmit(action);
  }

Picasso实际上是把action交给了Dispatcher对象去调度

  ...
  static class DispatcherThread extends HandlerThread {
    DispatcherThread() {
      super(Utils.THREAD_PREFIX + DISPATCHER_THREAD_NAME, THREAD_PRIORITY_BACKGROUND);
    }
  }
  ...
  this.dispatcherThread = new DispatcherThread();
  this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
  ...
  void dispatchSubmit(Action action) {
    handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
  }

可见调度器的handler绑定了dispatcherThread的looper,所以任务处理方法handlerMessage方法会在dispatchThread所在的线程执行。

    @Override public void handleMessage(final Message msg) {
      switch (msg.what) {
        case REQUEST_SUBMIT: {
          Action action = (Action) msg.obj;
          dispatcher.performSubmit(action);
          break;
          ...

这样通过handler达到了线程切换的目的,从主线程切换到了工作者线程,并在工作者线程调用dispatcher.performSubmit(action)方法,进行真正的耗时请求

  void performSubmit(Action action) {
    performSubmit(action, true);
  }
  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;
    }

    if (service.isShutdown()) {
      if (action.getPicasso().loggingEnabled) {
        log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down");
      }
      return;
    }

    hunter = forRequest(action.getPicasso(), this, cache, stats, action);
    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());
    }
  }

performSubmit方法先判断此action的tag是不是被暂停执行了,是的话就不用处理直接返回就好。

    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;
    }

该action已经分配BitmapHunter的话也会直接返回,不重复为同一个action分配多个hunter。

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

线程池已经关闭的话也会直接返回

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

经过上边三重检查,终于可以为该action分配hunter进行图片请求了

    hunter = forRequest(action.getPicasso(), this, cache, stats, action);

最后将这个hunter提交该线程池处理

通过责任链模式为Action创建BitmapHunter

  static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,
      Action action) {
    Request request = action.getRequest();
    List<RequestHandler> requestHandlers = picasso.getRequestHandlers();

    // Index-based loop to avoid allocating an iterator.
    //noinspection ForLoopReplaceableByForEach
    for (int i = 0, count = requestHandlers.size(); i < count; i++) {
      RequestHandler requestHandler = requestHandlers.get(i);
      if (requestHandler.canHandleRequest(request)) {
        return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
      }
    }

    return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
    }

Picasso在构造函数里边创建了多个RequestHandler,这些RequestHandler各司其职,能从多个地方加载图片,最常用的是NetworkRequestHandler。

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

    // ResourceRequestHandler needs to be the first in the list to avoid
    // forcing other RequestHandlers to perform null checks on request.uri
    // to cover the (request.resourceId != 0) case.
    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);

forRequest方法中遍历list,找到能处理该request的RequestHandler后,直接使用该RequestHandler构建Bitmap对象返回。

随后会分析BitmapHunter的源码。

时间: 2024-07-30 19:49:21

Picasso源码分析(五):into方法追本溯源和责任链模式创建BitmapHunter的相关文章

[Android] Volley源码分析(五)答疑

Volley源码分析系列出了有一段日子了,有不少看官私底下给我留言,同时抛出了一些问题.对于一些比较简单的问题我们跳过去,这两天接到网友是@smali提出的问题.不得不赞一下这位看官看源码时候的细腻程度,我引出这个问题供大家一块思考一下. Q:在写入文件头数据的时候为何不直接写入Int而是通过移位的方式来完成? 我们来看一下对应的源码: writeInt(os, CACHE_MAGIC); static void writeInt(OutputStream os, int n) throws I

baksmali和smali源码分析(五)

官方文档对于dex中的class数据结构表示如下: class_idx uint index into the type_ids list for this class. This must be a class type, and not an array or primitive type. access_flags uint access flags for the class (public, final, etc.). See "access_flags Definitions&quo

BufferedReader源码分析之readLine方法

readLine方法是BufferedReader中一个非常常用的方法 使用它我们可以从一段输入流中一行一行的读数据 行的区分用"\r","\n"或者是"\r\n",下面就是BufferedReader中readLine的源代码 详细的分析都写在注释里了 String readLine(boolean ignoreLF) throws IOException { StringBuffer s = null; int startChar; sync

Nouveau源码分析(五):NVIDIA设备初始化之nouveau_drm_load (2)

Nouveau源码分析(五) 接着上一篇来,先把nouveau_drm_load再贴出一遍来吧: // /drivers/gpu/drm/nouveau/nouveau_drm.c 364 static int 365 nouveau_drm_load(struct drm_device *dev, unsigned long flags) 366 { 367 struct pci_dev *pdev = dev->pdev; 368 struct nouveau_drm *drm; 369 i

Android图片加载库Picasso源码分析

图片加载在Android开发中是非常重要,好的图片加载库也比比皆是.ImageLoader.Picasso.Glide.Fresco均是优秀的图片加载库. 以上提到的几种图片加载库各有特色.用法与比较,网上已经很多了. 出于学习的角度,个人认为从Picasso入手较好.代码量小,同时API优美,很适合我们学习. 今天笔者就Picasso的源码进行分析,抛出一些图片加载的技术细节供园友参考. PS:建议园友先大致看一下源码. 我们对图片加载的要求 1.加载速度要快 2.资源消耗要低 3.加载图片不

Vue系列---理解Vue.nextTick使用及源码分析(五)

_ 阅读目录 一. 什么是Vue.nextTick()? 二. Vue.nextTick()方法的应用场景有哪些? 2.1 更改数据后,进行节点DOM操作. 2.2 在created生命周期中进行DOM操作. 三. Vue.nextTick的调用方式如下: 四:vm.$nextTick 与 setTimeout 的区别是什么? 五:理解 MutationObserver 六:nextTick源码分析 回到顶部 一. 什么是Vue.nextTick()? 官方文档解释为:在下次DOM更新循环结束之

Vue 2.0 深入源码分析(五) 基础篇 methods属性详解

用法 methods中定义了Vue实例的方法,官网是这样介绍的: 例如:: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script src="https://cdn.bootcss.com/vue/2.5.16/vue.js"></script> <title>Document&

MPTCP 源码分析(五) 接收端窗口值

简述: 在TCP协议中影响数据发送的三个因素分别为:发送端窗口值.接收端窗口值和拥塞窗口值. 本文主要分析MPTCP中各个子路径对接收端窗口值rcv_wnd的处理. 接收端窗口值的初始化 根据<MPTCP 源码分析(二) 建立子路径>中描述服务端在发送完SYN/ACK并接收到ACK的时候建立新的sock. 在内核实现中,针对连接请求分为两个步骤处理: SYN队列处理:当服务端收到SYN的时候,此连接请求request_sock将被存放于listening socket的SYN队列,服务端发送S

五分钟一个设计模式之责任链模式

五分钟一个设计模式,用最简单的方法来描述设计模式.查看更多设计模式,请点击五分钟一个设计模式系列 http://blog.csdn.net/daguanjia11/article/category/3259443 请假流程 假设现在一个公司的请假流程如下:一天及以下由小组组长审批,一天以上三天以下由经理审批,三天以上七天以下由老板审批,七天以上直接劝退. 如果每次请假时都很长的if-else-来判断该去找谁请假,很不容易扩展,我们使用责任链模式来实现. 首先,是一个抽象的父类 public ab