Android 图片加载框架 Picasso 源码解析

Picasso 是 Square 公司出品的一款十分优秀的开源图片框架,也是目前 Android 开发中十分流行的一款图片加载框架。提到 Square 公司大家一定不会陌生,OkHttp、Retrofit、LeakCanary 等等 Android 开发者十分熟悉的开源库都出自他们之手,个人认为他们公司的开源库都十分值得研究,今天就让我们来研究一下 Picasso 这款图片加载框架。

Picasso 属于三大图片框架(Glide、Picasso、Fresco)之一。相比其他两个框架,它的特点是轻量,占用的体积更少,同时功能相对来说也比较完善。那么今天就来跟我一起分析一波 Picasso 这个图片选择框架的源码。

此篇文章的源码解析基于 2.71828 版本。

初始化

以我的阅读源码的习惯,都是从使用的时候的入口开始入手,因此我们这里从 Picasso 类入手。旧版本的 Picasso 使用了 with 方法作为入口,而在新版本中 with 方法则被 get 方法所替代,并且不再需要传入 Context 参数。那么它是如何实现的呢?下面我们看到它的 get 方法:

public static Picasso get() {
  if (singleton == null) {
    synchronized (Picasso.class) {
      if (singleton == null) {
        if (PicassoProvider.context == null) {
          throw new IllegalStateException("context == null");
        }
        singleton = new Builder(PicassoProvider.context).build();
      }
    }
  }
  return singleton;
}

可以看到,这里是一个单例类,而它的 Context 则由一个没有任何实现的 PicassoProvider 这个 ContentProvider 来提供,从而使用户不再需要传入一个 Context。

@RestrictTo(LIBRARY)
public final class PicassoProvider extends ContentProvider {

  @SuppressLint("StaticFieldLeak") static Context context;

  @Override public boolean onCreate() {
    context = getContext();
    return true;
  }
    // ...省略 ContentProvider 的默认实现
}

之后,它调用了 Builder 的 build 方法返回了一个 Picasso 对象。我们先看到 Builder 的构造方法:

public Builder(@NonNull Context context) {
  if (context == null) {
    throw new IllegalArgumentException("Context must not be null.");
  }
  this.context = context.getApplicationContext();
}

可以看到仅仅是判空并赋值。接着我们看看 build 方法:

public Picasso build() {
  Context context = this.context;
  if (downloader == null) {
    downloader = new OkHttp3Downloader(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);
}

build 方法中对 downloader、cache 等变量进行了初始化,同时返回了一个新的 Picasso 对象,前面的变量我们先不关心。先看到 Picasso 的构造方法:

Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
    RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
    Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
  this.context = context;
  this.dispatcher = dispatcher;
  this.cache = cache;
  this.listener = listener;
  this.requestTransformer = requestTransformer;
  this.defaultBitmapConfig = defaultBitmapConfig;
  int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.
  int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
  List<RequestHandler> allRequestHandlers = new ArrayList<>(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);
  this.stats = stats;
  this.targetToAction = new WeakHashMap<>();
  this.targetToDeferredRequestCreator = new WeakHashMap<>();
  this.indicatorsEnabled = indicatorsEnabled;
  this.loggingEnabled = loggingEnabled;
  this.referenceQueue = new ReferenceQueue<>();
  this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);
  this.cleanupThread.start();
}

可以看到,主要是对 requestHandlers 这个 List 进行初始化以及各个变量进行初始化。通过上面的几个名字可以看出来 RequestHandler 就是 Picasso 对各种类型的图片加载的抽象。通过实现 RequestHandler 接口可以实现不同的图片加载策略。

创建请求

之后我们调用了 load 方法并传入了具体的参数。它有许多重载,可以传入 Uri、String、File、resourceId 等等类型的数据。

我们以 load(String) 为例:

public RequestCreator load(@Nullable String path) {
  if (path == null) {
    return new RequestCreator(this, null, 0);
  }
  if (path.trim().length() == 0) {
    throw new IllegalArgumentException("Path must not be empty.");
  }
  return load(Uri.parse(path));
}

可以看到,它最终调用的还是 load(Uri) 方法。其实所有的其他重载最后都会指向 load(Uri) 方法,也就是说我们-各种形式的数据源最后都是以 Uri 的形式存在于 Picasso 中。我们下面看到 load(Uri):

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

它构造了一个 RequestCreator 并返回。接下来我们看到 RequestCreator 的构造方法:

RequestCreator(Picasso picasso, Uri uri, int resourceId) {
  if (picasso.shutdown) {
    throw new IllegalStateException(
        "Picasso instance already shut down. Cannot submit new requests.");
  }
  this.picasso = picasso;
  this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}

它调用了 Request.Builder 的构造方法来为 data 进行赋值,我们看到这个构造方法:

Builder(Uri uri, int resourceId, Bitmap.Config bitmapConfig) {
  this.uri = uri;
  this.resourceId = resourceId;
  this.config = bitmapConfig;
}

可以看到,这里主要是对 Bitmap.Config 等属性进行设置。

配置加载属性

在我们创建了 RequestCreator 后,可以调用它的 placeholder、error 等等方法为本次加载设置占位图、错误图等等各种属性的设置,下面我们以 placeholder(int) 方法举例:

public RequestCreator placeholder(@DrawableRes int placeholderResId) {
  if (!setPlaceholder) {
    throw new IllegalStateException("Already explicitly declared as no placeholder.");
  }
  if (placeholderResId == 0) {
    throw new IllegalArgumentException("Placeholder image resource invalid.");
  }
  if (placeholderDrawable != null) {
    throw new IllegalStateException("Placeholder image already set.");
  }
  this.placeholderResId = placeholderResId;
  return this;
}

其实这里就是为 RequestCreator 中的这些属性赋值。

那么所有通过 RequestCreator 设定的属性都是放在 RequestCreator 这个类中的么?

其实不是的,与加载过程有关的属性是放在 RequestCreator 中的,而与图片相关的属性则是放在 Request.Builder 中。

可能看到这里有点乱,大概解释一下。

比如 placeholder、error、memoryPolicy 这些属性就是与加载过程有关而与图片无关的

而比如 resize、centerCrop 这些就是与图片的显示效果有关的属性,也就是图片相关属性。

我们以 resize 为例来看看整体流程:

public RequestCreator resize(int targetWidth, int targetHeight) {
  data.resize(targetWidth, targetHeight);
  return this;
}

我们看到 Request.Builder 中的 resize 方法:

public Builder resize(@Px int targetWidth, @Px int targetHeight) {
  if (targetWidth < 0) {
    throw new IllegalArgumentException("Width must be positive number or 0.");
  }
  if (targetHeight < 0) {
    throw new IllegalArgumentException("Height must be positive number or 0.");
  }
  if (targetHeight == 0 && targetWidth == 0) {
    throw new IllegalArgumentException("At least one dimension has to be positive number.");
  }
  this.targetWidth = targetWidth;
  this.targetHeight = targetHeight;
  return this;
}

这里就是将 Request.Builder 中的一些属性进行了赋值。

加载图片

当属性都设定完后,我们便可以调用 into 方法来加载图片,我们看到 into(ImageView):

public void into(ImageView target) {
  into(target, null);
}

它调用了 into(ImageView, Callback):

public void into(ImageView target, Callback callback) {
  long started = System.nanoTime();
  // 1
  // 检查是否在主线程
  checkMain();  
  if (target == null) {
    throw new IllegalArgumentException("Target must not be null.");
  }
  if (!data.hasImage()) {
    // 之前设置的 uri 是否有数据(实际上也是判空)
    picasso.cancelRequest(target);
    if (setPlaceholder) {
      setPlaceholder(target, getPlaceholderDrawable());
    }
    return;
  }
  // 2
  if (deferred) {
    // 是否自适应 Target 宽高
    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);
  }
  // 3
  Request request = createRequest(started);
  String requestKey = createKey(request);
  // 4
  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;
    }
  }
  // 5
  if (setPlaceholder) {
    setPlaceholder(target, getPlaceholderDrawable());
  }
  // 6
  Action action =
      new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
          errorDrawable, requestKey, tag, callback, noFade);
  picasso.enqueueAndSubmit(action);
}

这里代码比较长,我们慢慢分析,先看看整体大体流程。

首先在注释 1 处进行了一系列判断操作,具体可看注释

之后在注释 2 处,是 fit() 的具体实现。如果外部调用了 fit 使图片自适应 target 的大小,则获取 target 的大小并调用 resize 方法进行设置。这里要特别注意的是如果宽高为 0 则说明 ImageView 的尺寸还没有获取到,此时会延时该图片请求直到获取到 ImageView 的宽高。

之后 3 处构建了一个 Request,并调用 createKey 方法由该 Request 及其各种信息构建了一个 String 类型的 key。

之后在注释 4 处,在使用内存缓存策略的情况下,先调用 quickMemoryCacheCheck 方法获取到了内存缓存中的 BitMap,如果找到则调用 setBitmap 方法将图片应用到 target 中。

然后在注释 5 处,如果内存没有缓存,且设置了占位图,则给它添加占位图。

最后在注释 6 处,构造了一个 Action 对象然后调用了 picasso 的 enqueueAndSubmit 进行网络请求。

Request 的创建

首先,我们看看 Request 是如何创建的,看到 createRequest 方法:

private Request createRequest(long started) {
  int id = nextId.getAndIncrement();
  // 1
  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());
  }
  // 2
  Request transformed = picasso.transformRequest(request);
  if (transformed != request) {
    // 3
    // If the request was changed, copy over the id and timestamp from the orig
    transformed.id = id;
    transformed.started = started;
    if (loggingEnabled) {
      log(OWNER_MAIN, VERB_CHANGED, transformed.logId(), "into " + transformed)
    }
  }
  return transformed;
}

可以看到,这里首先在注释 1 处调用了 Request.Builder 的 build 方法创建了 Request,之后在注释 2 处调用了 picasso 的 transformRequest 方法对 Request 进行转换。

获取 Request

我们先看看 Request.Builder 的 build 方法:

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, centerCropGravity, onlyScaleDown, rotationDegrees,
      rotationPivotX, rotationPivotY, hasRotationPivot, purgeable, config, priority);
}

这里就是创建 Request 对象并将各种 Request.Builder 中的属性传递给这个 Request 对象。

转换 Request

然后我们再看看 picasso 的 transformRequest 方法:

Request transformRequest(Request request) {
  Request transformed = requestTransformer.transformRequest(request);
  if (transformed == null) {
    throw new IllegalStateException("Request transformer "
        + requestTransformer.getClass().getCanonicalName()
        + " returned null for "
        + request);
  }
  return transformed;
}

这里调用了 requestTransformer 的 transformRequest 方法来进行转换。而这个 requestTrasformer 则是之前在 Picasso.Builder 中的 build 方法中初始化给 transformer 的 RequestTransformer.IDENTITY:

if (transformer == null) {
  transformer = RequestTransformer.IDENTITY;
}

我们看看它的 transformRequest 的实现:

RequestTransformer IDENTITY = new RequestTransformer() {
  @Override public Request transformRequest(Request request) {
    return request;
  }
};

可以看到这里是返回了原始的 Request。

既然都是返回默认 Request,为什么 Picasso 还要在创建的时候添加这一步 transform 的过程呢?

其实这个 transformer 我们是可以通过 Builder 的 requestTransformer 方法来进行设置的。也就是说这里主要是提供给用户对 Request 进行一些特殊处理的渠道,使得我们可以对图片加载的过程进行一定的扩展与定制。这种设计是值得我们去学习的。

之后我们回到 Request 创建的部分,可以看到这里如果对 Request 进行了修改,在注释 3 处会将原 Request 的 id 和 started 赋值过去,从而防止用户对它们进行修改。

key 的生成

我们再来看看 Request 的 key 是如何生成的:

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

其实这里就是用一个 StringBuilder 构造了一个 String,将 Request 中的各类信息都存放于 key 中。这个 key 其实就是用于内存缓存中的 key。

图片的加载

我们先不去查看内存缓存的部分,留到后面来讲解,我们先看看图片是如何从网络加载的。先看到 into 方法的下面这两句:

Action action =
    new FetchAction(picasso, request, memoryPolicy, networkPolicy, tag, key, callback);
picasso.submit(action);

构造 Action

我们先看到 FetchAction 的构造方法:

FetchAction(Picasso picasso, Request data, int memoryPolicy, int networkPolicy, Object tag,
    String key, Callback callback) {
  super(picasso, null, data, memoryPolicy, networkPolicy, 0, null, key, tag, false);
  this.target = new Object();
  this.callback = callback;
}

调用了父类的构造方法:

Action(Picasso picasso, T target, Request request, int memoryPolicy, int networkPolicy,
    int errorResId, Drawable errorDrawable, String key, Object tag, boolean noFade) {
  this.picasso = picasso;
  this.request = request;
  this.target =
      target == null ? null : new RequestWeakReference<>(this, target, picasso.referenceQueue);
  this.memoryPolicy = memoryPolicy;
  this.networkPolicy = networkPolicy;
  this.noFade = noFade;
  this.errorResId = errorResId;
  this.errorDrawable = errorDrawable;
  this.key = key;
  this.tag = (tag != null ? tag : this);
}

可以看出来,Action 类实际上就是一个携带了需要的信息的类。

分发 Action

接着,调用了 picasso 的 submit 方法:

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

这里调用了 dispatcher 的 dispatchSubmit 方法:

void dispatchSubmit(Action action) {
  handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}

这里用到了一个 DispatcherHandler 类的对象调用 sendMessage 方法发送一条信息。这里的 DispatcherHandler 的作用主要是根据不同的调用将 Action 分发到不同的方法中。

下面我们看到 DispatcherHandler 的实现,它是 Dispatcher 的一个内部类:

@Override public void handleMessage(final Message msg) {
  switch (msg.what) {
    case REQUEST_SUBMIT: {
      Action action = (Action) msg.obj;
      dispatcher.performSubmit(action);
      break;
    }
    case REQUEST_CANCEL: {
      Action action = (Action) msg.obj;
      dispatcher.performCancel(action);
      break;
    }
    case TAG_PAUSE: {
      Object tag = msg.obj;
      dispatcher.performPauseTag(tag);
      break;
    }
    case TAG_RESUME: {
      Object tag = msg.obj;
      dispatcher.performResumeTag(tag);
      break;
    }
    case HUNTER_COMPLETE: {
      BitmapHunter hunter = (BitmapHunter) msg.obj;
      dispatcher.performComplete(hunter);
      break;
    }
    case HUNTER_RETRY: {
      BitmapHunter hunter = (BitmapHunter) msg.obj;
      dispatcher.performRetry(hunter);
      break;
    }
    case HUNTER_DECODE_FAILED: {
      BitmapHunter hunter = (BitmapHunter) msg.obj;
      dispatcher.performError(hunter, false);
      break;
    }
    case HUNTER_DELAY_NEXT_BATCH: {
      dispatcher.performBatchComplete();
      break;
    }
    case NETWORK_STATE_CHANGE: {
      NetworkInfo info = (NetworkInfo) msg.obj;
      dispatcher.performNetworkStateChange(info);
      break;
    }
    case AIRPLANE_MODE_CHANGE: {
      dispatcher.performAirplaneModeChange(msg.arg1 == AIRPLANE_MODE_ON);
      break;
    }
    default:
      Picasso.HANDLER.post(new Runnable() {
        @Override public void run() {
          throw new AssertionError("Unknown handler message received: " + msg.what);
        }
      });
  }
}

这里根据不同的 Message 调用了不同的方法,我们的 submit 方法调用了 Dispatcher 中的 performSubmit 方法:

void performSubmit(Action action) {
  performSubmit(action, true);
}

它调用了 performSubmit(Action, boolean) 方法:

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;
  }
  // 1
  BitmapHunter hunter = hunterMap.get(action.getKey());
  if (hunter != null) {
    hunter.attach(action);
    return;
  }
  
  // 2
  if (service.isShutdown()) {
    if (action.getPicasso().loggingEnabled) {
      log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down")
    }
    return;
  }
  // 3
  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());
  }
}

首先,在注释 1 处根据 Action 获取到了其对应的 BitmapHunter。

之后在注释 2 处检查 service 是否被杀掉。

然后在注释 3 处,调用了 forRequest 获取到了 Action 对应的 BitmapHunter,然后调用了 service 的 submit 方法。

之后将该 action 与 BitmapHunter 放入了 hunterMap 中。

BitmapHunter 的获取

我们看一下前面的步骤中 BitmapHunter 是如何获取的,来到 forRequest方法:

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

这里主要是依次遍历各个 RequestHandler,找到可以处理该类 Request 的 Handler,并构建 BitmapHunter。

我们先看看 RequestHunter 是如何判断能否处理该类 Request 的,我们以 NetworkRequestHandler 举例:

@Override public boolean canHandleRequest(Request data) {
  String scheme = data.uri.getScheme();
  return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
}

可以看到,它是通过判断 uri 的 scheme 来判断能否处理该类 Request 的。

我们接着看到 BitmapHunter 的构造函数:

BitmapHunter(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action, RequestHandler requestHandler) {
  this.sequence = SEQUENCE_GENERATOR.incrementAndGet();
  this.picasso = picasso;
  this.dispatcher = dispatcher;
  this.cache = cache;
  this.stats = stats;
  this.action = action;
  this.key = action.getKey();
  this.data = action.getRequest();
  this.priority = action.getPriority();
  this.memoryPolicy = action.getMemoryPolicy();
  this.networkPolicy = action.getNetworkPolicy();
  this.requestHandler = requestHandler;
  this.retryCount = requestHandler.getRetryCount();
}

可以看到,这里主要是各种变量的赋值。

接着我们看到 service 的 submit 方法,这里的 service 是 PicassoExecutorService:

@Override
public Future<?> submit(Runnable task) {
  PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task);
  execute(ftask);
  return ftask;
}

这里构建了一个 PicassoFutureTask,然后调用了 execute 方法

我们先看看 PicassoFutureTask 的构造方法:

PicassoFutureTask(BitmapHunter hunter) {
  super(hunter, null);
  this.hunter = hunter;
}

PicassoFutureTask 是 FutureTask 的子类,这里主要是变量的赋值。

图片资源的获取

接着我们看到 execute 方法,这里其实是调用了 Java 自带的 ThreadPoolExecutor 的 execute 方法。同时这里也说明了这里是一个异步的过程。

其实 BitmapHunter 是一个 Runnable,当调用了 execute 方法后便会执行它的 run 方法。我们可以看到它的 run 方法:

@Override public void run() {
  try {
    updateThreadName(data);
    if (picasso.loggingEnabled) {
      log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));
    }
    result = hunt();
    if (result == null) {
      dispatcher.dispatchFailed(this);
    } else {
      dispatcher.dispatchComplete(this);
    }
  }
  // 省略后面的 catch
}

这里调用了 hunt 方法获取到了结果 Bitmap,同时在后面根据不同的结果通过 dispatcher 进行结果的处理:

Bitmap hunt() throws IOException {
  Bitmap bitmap = null;
  // 1
  if (shouldReadFromMemoryCache(memoryPolicy)) {
    bitmap = cache.get(key);
    if (bitmap != null) {
      stats.dispatchCacheHit();
      loadedFrom = MEMORY;
      if (picasso.loggingEnabled) {
        log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache");
      }
      return bitmap;
    }
  }
  // 2
  networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
  // 3
  RequestHandler.Result result = requestHandler.load(data, networkPolicy);
  if (result != null) {
    loadedFrom = result.getLoadedFrom();
    exifOrientation = result.getExifOrientation();
    bitmap = result.getBitmap();
    // If there was no Bitmap then we need to decode it from the stream.
    // 4
    if (bitmap == null) {
      Source source = result.getSource();
      try {
        bitmap = decodeStream(source, data);
      } finally {
        try {
          //noinspection ConstantConditions If bitmap is null then source is guranteed non-null.
          source.close();
        } catch (IOException ignored) {
        }
      }
    }
  }
  
  // 5
  if (bitmap != null) {
    if (picasso.loggingEnabled) {
      log(OWNER_HUNTER, VERB_DECODED, data.logId());
    }
    stats.dispatchBitmapDecoded(bitmap);
    if (data.needsTransformation() || exifOrientation != 0) {
      synchronized (DECODE_LOCK) {
                // 6
        if (data.needsMatrixTransform() || exifOrientation != 0) {
          bitmap = transformResult(data, bitmap, exifOrientation);
          if (picasso.loggingEnabled) {
            log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());
          }
        }
        // 7
        if (data.hasCustomTransformations()) {
          bitmap = applyCustomTransformations(data.transformations, bitmap);
          if (picasso.loggingEnabled) {
            log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), "from custom transformations");
          }
        }
      }
      if (bitmap != null) {
        stats.dispatchBitmapTransformed(bitmap);
      }
    }
  }
  return bitmap;
}

这里代码很长,我们慢慢分析:

首先在注释 1 处尝试从内存通过 key 获取对应 bitmap,若获取到则直接返回。

之后在注释 2 处,根据 requestHandler 中的 retryCount 来判断是否是网络请求,从而获取不同的 networkPolicy。若 retryCount 为 0 则为离线策略。

之后在注释 3 处,通过 requestHandler 的 load 方法进行数据的加载,若数据加载成功则进行一些变量的赋值,并获取 bitmap。

若 bitmap 为空则说明我们需要在注释4处将其从流中 decode 出来。

之后在注释 5 处就是 Picasso 的加载过程中支持用户对图片进行定制后再应用的具体实现了。这里首先判断是否需要 transform。

在注释 6 处判断如果需要进行矩阵变换(旋转,放大缩小等),则调用 transformResult 方法进行变换。

在注释 7 处判断如果有自定义变换,则调用 applyCustomTransformations 进行自定义变换。

这里的自定义变换比较类似前面的自定义 Request 转换,用户可以在外部自定义 Transformation,并通过 RequestCreator 的 transform 方法传入,这样就可以在图片应用前对 Bitmap 进行一些自定义 (如高斯模糊等)后再应用于 target。这种设计是我们值得学习的。

RequestHandler 的实现

下面我们以网络图片对应的 NetworkRequestHandler 为例看看它们的实现,其他的子类可以自己去了解。让我们看到它的 load 方法:

@Override public Result load(Request request, int networkPolicy) throws IOException {
    // 1 
  okhttp3.Request downloaderRequest = createRequest(request, networkPolicy);
  // 2
  Response response = downloader.load(downloaderRequest);
  ResponseBody body = response.body();
  if (!response.isSuccessful()) {
    body.close();
    throw new ResponseException(response.code(), request.networkPolicy);
  }
  // Cache response is only null when the response comes fully from the network. Both completely
  // cached and conditionally cached responses will have a non-null cache response.
  Picasso.LoadedFrom loadedFrom = response.cacheResponse() == null ? NETWORK : DISK;
  // Sometimes response content length is zero when requests are being replayed. Haven't found
  // root cause to this but retrying the request seems safe to do so.
  if (loadedFrom == DISK && body.contentLength() == 0) {
    body.close();
    throw new ContentLengthException("Received response with 0 content-length header.");
  }
  if (loadedFrom == NETWORK && body.contentLength() > 0) {
    stats.dispatchDownloadFinished(body.contentLength());
  }
  return new Result(body.source(), loadedFrom);
}

可以看到,这里是通过 OkHttp3 来实现的图片的加载。

首先调用 createRequest 方法创建了 OkHttp 的 Request。然后通过自己实现的 OkHttp3Downloader 的 load 方法来实现对这个 Request 的下载请求。

之后根据缓存的相应是否是空判断数据的来源是从本地还是网络。

最终构造了一个 Result 并返回。

OkHttp3.Request 的 创建

我们先看看如何将 Request 转换为 OkHttp3.Request。让我们看到 createRequest 方法:

private static okhttp3.Request createRequest(Request request, int networkPolicy) {
  CacheControl cacheControl = null;
  if (networkPolicy != 0) {
    if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
      cacheControl = CacheControl.FORCE_CACHE;
    } else {
      CacheControl.Builder builder = new CacheControl.Builder();
      if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
        builder.noCache();
      }
      if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
        builder.noStore();
      }
      cacheControl = builder.build();
    }
  }
  okhttp3.Request.Builder builder = new okhttp3.Request.Builder().url(request.uri.toString());
  if (cacheControl != null) {
    builder.cacheControl(cacheControl);
  }
  return builder.build();
}

可以看到,首先根据 Request 和 NetworkPolicy 的参数设置缓存的各种参数,之后调用 okhttp3.Request.Builder 的构造函数并传入 uri 创建了 Request。

OkHttp3 数据的获取

之后我们看到 OkHttp3Downloader 的 load 方法,看看数据获取是如何实现的:

@NonNull @Override public Response load(@NonNull Request request) throws IOException {
  return client.newCall(request).execute();
}

其实就是调用 OkHttpClient 的 newCall 方法并调用 execute 获取一个 Response。

结果的处理

前面提到,在 BitmapHunter 的 run 方法中根据 hunt() 返回的结果成功与否调用了 dispatcher 的不同方法来进行的结果处理,让我们看看是如何处理的

if (result == null) {
   dispatcher.dispatchFailed(this);
  } else {
   dispatcher.dispatchComplete(this);
}

我们先看到 dispatchComplete 方法,它最终通过 handler 调用到了 performComplete 方法中:

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

可以看到,这里如果获取到了结果,且需要内存缓存,则将其放入内存缓存。然后将这个 BitmapHunter 从 Map 中删除。

之后我们看到 dispatchFailed 方法,它最终通过 handler 调用到了 performError 方法:

void performError(BitmapHunter hunter, boolean willReplay) {
  if (hunter.getPicasso().loggingEnabled) {
    log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter),
        "for error" + (willReplay ? " (will replay)" : ""));
  }
  hunterMap.remove(hunter.getKey());
  batch(hunter);
}

这里它将 BitmapHunter 从 Map 中移除,然后就没有进行其他处理了。

内存缓存

为了优化流量消耗,Picasso 加入了内存缓存机制,下面我们来看看 Picasso 内存缓存的实现。

就像其他部分一样,它的内存缓存也考虑到了扩展性,给了用户自己实现的接口。

我们可以调用 Picasso 类的 memoryCache 方法为其设置 Cache 接口的子类,从而实现自己的内存缓存。

若用户不传入指定缓存,则默认使用 Picasso 自己实现的 LruCache。

具体的 LruCache 的设计这里不深入讲解,有兴趣的各位可以去了解一下 LRU 算法,以后可能可以专门开一篇博客讲讲 LRU 算法。

Dispatcher 设计

其实从前面的讲解中,你会发现,其实如图片的加载请求,缓存命中等等事件都是由一个叫 Dispatcher 的类分发的,它内部由 Handler 实现,负责将请求封装,并按优先级排序,之后按照类型分发。

这种设计也很值得我们学习,它作为一个分发中心管理我们的各类请求。使得我们的设计更为清晰,也使得库更容易维护。

线程池设计

之前没有提到的就是 Picasso 对线程池也有一些优化,它自己实现了一个 PicassoExecutorService 类,它可以根据当前的网络状态,采用不同的线程池数量,从而使得网络不会过于拥塞。

具体可以看下面这个方法:

void adjustThreadCount(NetworkInfo info) {
  if (info == null || !info.isConnectedOrConnecting()) {
    setThreadCount(DEFAULT_THREAD_COUNT);
    return;
  }
  switch (info.getType()) {
    case ConnectivityManager.TYPE_WIFI:
    case ConnectivityManager.TYPE_WIMAX:
    case ConnectivityManager.TYPE_ETHERNET:
      setThreadCount(4);
      break;
    case ConnectivityManager.TYPE_MOBILE:
      switch (info.getSubtype()) {
        case TelephonyManager.NETWORK_TYPE_LTE:  // 4G
        case TelephonyManager.NETWORK_TYPE_HSPAP:
        case TelephonyManager.NETWORK_TYPE_EHRPD:
          setThreadCount(3);
          break;
        case TelephonyManager.NETWORK_TYPE_UMTS: // 3G
        case TelephonyManager.NETWORK_TYPE_CDMA:
        case TelephonyManager.NETWORK_TYPE_EVDO_0:
        case TelephonyManager.NETWORK_TYPE_EVDO_A:
        case TelephonyManager.NETWORK_TYPE_EVDO_B:
          setThreadCount(2);
          break;
        case TelephonyManager.NETWORK_TYPE_GPRS: // 2G
        case TelephonyManager.NETWORK_TYPE_EDGE:
          setThreadCount(1);
          break;
        default:
          setThreadCount(DEFAULT_THREAD_COUNT);
      }
      break;
    default:
      setThreadCount(DEFAULT_THREAD_COUNT);
  }
}

可以看到,线程池最大线程个数如下:

  • 在 WIFI 网络下,采用最多 4 个线程的线程池
  • 在 4G 网络下,采用最多 3 个线程的线程池
  • 在 3G 网络下,采用最多 2 个线程的线程池
  • 在 2 G 网络下,采用最多 1 个线程的线程池

总结

Picasso 是一个非常值得我们学习的轻量级图片加载库,它采用 OkHttp3 来加载网络图片,并使用了二级内存缓存来提高加载速度。它的 Dispatcher 思想以及对外部的扩展开放的思想十分值得我们学习,这次源码的阅读还是给了我很大的启发的。

当然,由于篇幅有限,这篇文章并没有包含 Picasso 的方方面面,它的代码中还有如下的一些点在本文还没有分析,读者们有兴趣的可以从下面的点去研究这个库:

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

推荐阅读更多精彩内容