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

Posted 王世晖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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提供了图片的加载和变换规则,这些变换规则包括设置占位图,图片压缩裁剪,显示模式设置等,提供的设置方法如下:
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的源码。

以上是关于Picasso源码分析:into方法追本溯源和责任链模式创建BitmapHunter的主要内容,如果未能解决你的问题,请参考以下文章

Picasso源码解析

Picasso源码解析

Picasso源码解析

Picasso 源码解读

Android 常用开源框架源码解析 系列 picasso 图片框架

Picasso源码解析之Lrucache算法源码解析