Android 圖片加載框架 Picasso 源碼解析

Picasso 是 Square 公司出品的一款十分優(yōu)秀的開(kāi)源圖片框架,也是目前 Android 開(kāi)發(fā)中十分流行的一款圖片加載框架。提到 Square 公司大家一定不會(huì)陌生,OkHttp、Retrofit、LeakCanary 等等 Android 開(kāi)發(fā)者十分熟悉的開(kāi)源庫(kù)都出自他們之手,個(gè)人認(rèn)為他們公司的開(kāi)源庫(kù)都十分值得研究,今天就讓我們來(lái)研究一下 Picasso 這款圖片加載框架。

Picasso 屬于三大圖片框架(Glide、Picasso、Fresco)之一。相比其他兩個(gè)框架,它的特點(diǎn)是輕量,占用的體積更少,同時(shí)功能相對(duì)來(lái)說(shuō)也比較完善。那么今天就來(lái)跟我一起分析一波 Picasso 這個(gè)圖片選擇框架的源碼。

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

初始化

以我的閱讀源碼的習(xí)慣,都是從使用的時(shí)候的入口開(kāi)始入手,因此我們這里從 Picasso 類(lèi)入手。舊版本的 Picasso 使用了 with 方法作為入口,而在新版本中 with 方法則被 get 方法所替代,并且不再需要傳入 Context 參數(shù)。那么它是如何實(shí)現(xiàn)的呢?下面我們看到它的 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;
}

可以看到,這里是一個(gè)單例類(lèi),而它的 Context 則由一個(gè)沒(méi)有任何實(shí)現(xiàn)的 PicassoProvider 這個(gè) ContentProvider 來(lái)提供,從而使用戶不再需要傳入一個(gè) Context。

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

  @SuppressLint("StaticFieldLeak") static Context context;

  @Override public boolean onCreate() {
    context = getContext();
    return true;
  }
    // ...省略 ContentProvider 的默認(rèn)實(shí)現(xiàn)
}

之后,它調(diào)用了 Builder 的 build 方法返回了一個(gè) Picasso 對(duì)象。我們先看到 Builder 的構(gòu)造方法:

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 方法中對(duì) downloader、cache 等變量進(jìn)行了初始化,同時(shí)返回了一個(gè)新的 Picasso 對(duì)象,前面的變量我們先不關(guān)心。先看到 Picasso 的構(gòu)造方法:

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

可以看到,主要是對(duì) requestHandlers 這個(gè) List 進(jìn)行初始化以及各個(gè)變量進(jìn)行初始化。通過(guò)上面的幾個(gè)名字可以看出來(lái) RequestHandler 就是 Picasso 對(duì)各種類(lèi)型的圖片加載的抽象。通過(guò)實(shí)現(xiàn) RequestHandler 接口可以實(shí)現(xiàn)不同的圖片加載策略。

創(chuàng)建請(qǐng)求

之后我們調(diào)用了 load 方法并傳入了具體的參數(shù)。它有許多重載,可以傳入 Uri、String、File、resourceId 等等類(lèi)型的數(shù)據(jù)。

我們以 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));
}

可以看到,它最終調(diào)用的還是 load(Uri) 方法。其實(shí)所有的其他重載最后都會(huì)指向 load(Uri) 方法,也就是說(shuō)我們-各種形式的數(shù)據(jù)源最后都是以 Uri 的形式存在于 Picasso 中。我們下面看到 load(Uri):

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

它構(gòu)造了一個(gè) RequestCreator 并返回。接下來(lái)我們看到 RequestCreator 的構(gòu)造方法:

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

它調(diào)用了 Request.Builder 的構(gòu)造方法來(lái)為 data 進(jìn)行賦值,我們看到這個(gè)構(gòu)造方法:

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

可以看到,這里主要是對(duì) Bitmap.Config 等屬性進(jìn)行設(shè)置。

配置加載屬性

在我們創(chuàng)建了 RequestCreator 后,可以調(diào)用它的 placeholder、error 等等方法為本次加載設(shè)置占位圖、錯(cuò)誤圖等等各種屬性的設(shè)置,下面我們以 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;
}

其實(shí)這里就是為 RequestCreator 中的這些屬性賦值。

那么所有通過(guò) RequestCreator 設(shè)定的屬性都是放在 RequestCreator 這個(gè)類(lèi)中的么?

其實(shí)不是的,與加載過(guò)程有關(guān)的屬性是放在 RequestCreator 中的,而與圖片相關(guān)的屬性則是放在 Request.Builder 中。

可能看到這里有點(diǎn)亂,大概解釋一下。

比如 placeholder、error、memoryPolicy 這些屬性就是與加載過(guò)程有關(guān)而與圖片無(wú)關(guān)的

而比如 resize、centerCrop 這些就是與圖片的顯示效果有關(guān)的屬性,也就是圖片相關(guān)屬性。

我們以 resize 為例來(lái)看看整體流程:

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 中的一些屬性進(jìn)行了賦值。

加載圖片

當(dāng)屬性都設(shè)定完后,我們便可以調(diào)用 into 方法來(lái)加載圖片,我們看到 into(ImageView):

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

它調(diào)用了 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()) {
    // 之前設(shè)置的 uri 是否有數(shù)據(jù)(實(shí)際上也是判空)
    picasso.cancelRequest(target);
    if (setPlaceholder) {
      setPlaceholder(target, getPlaceholderDrawable());
    }
    return;
  }
  // 2
  if (deferred) {
    // 是否自適應(yīng) 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)) {
    // 從內(nèi)存緩存中獲取圖片
    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);
}

這里代碼比較長(zhǎng),我們慢慢分析,先看看整體大體流程。

首先在注釋 1 處進(jìn)行了一系列判斷操作,具體可看注釋

之后在注釋 2 處,是 fit() 的具體實(shí)現(xiàn)。如果外部調(diào)用了 fit 使圖片自適應(yīng) target 的大小,則獲取 target 的大小并調(diào)用 resize 方法進(jìn)行設(shè)置。這里要特別注意的是如果寬高為 0 則說(shuō)明 ImageView 的尺寸還沒(méi)有獲取到,此時(shí)會(huì)延時(shí)該圖片請(qǐng)求直到獲取到 ImageView 的寬高。

之后 3 處構(gòu)建了一個(gè) Request,并調(diào)用 createKey 方法由該 Request 及其各種信息構(gòu)建了一個(gè) String 類(lèi)型的 key。

之后在注釋 4 處,在使用內(nèi)存緩存策略的情況下,先調(diào)用 quickMemoryCacheCheck 方法獲取到了內(nèi)存緩存中的 BitMap,如果找到則調(diào)用 setBitmap 方法將圖片應(yīng)用到 target 中。

然后在注釋 5 處,如果內(nèi)存沒(méi)有緩存,且設(shè)置了占位圖,則給它添加占位圖。

最后在注釋 6 處,構(gòu)造了一個(gè) Action 對(duì)象然后調(diào)用了 picasso 的 enqueueAndSubmit 進(jìn)行網(wǎng)絡(luò)請(qǐng)求。

Request 的創(chuàng)建

首先,我們看看 Request 是如何創(chuàng)建的,看到 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 處調(diào)用了 Request.Builder 的 build 方法創(chuàng)建了 Request,之后在注釋 2 處調(diào)用了 picasso 的 transformRequest 方法對(duì) Request 進(jìn)行轉(zhuǎn)換。

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

這里就是創(chuàng)建 Request 對(duì)象并將各種 Request.Builder 中的屬性傳遞給這個(gè) Request 對(duì)象。

轉(zhuǎn)換 Request

然后我們?cè)倏纯?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;
}

這里調(diào)用了 requestTransformer 的 transformRequest 方法來(lái)進(jìn)行轉(zhuǎn)換。而這個(gè) requestTrasformer 則是之前在 Picasso.Builder 中的 build 方法中初始化給 transformer 的 RequestTransformer.IDENTITY:

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

我們看看它的 transformRequest 的實(shí)現(xiàn):

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

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

既然都是返回默認(rèn) Request,為什么 Picasso 還要在創(chuàng)建的時(shí)候添加這一步 transform 的過(guò)程呢?

其實(shí)這個(gè) transformer 我們是可以通過(guò) Builder 的 requestTransformer 方法來(lái)進(jìn)行設(shè)置的。也就是說(shuō)這里主要是提供給用戶對(duì) Request 進(jìn)行一些特殊處理的渠道,使得我們可以對(duì)圖片加載的過(guò)程進(jìn)行一定的擴(kuò)展與定制。這種設(shè)計(jì)是值得我們?nèi)W(xué)習(xí)的。

之后我們回到 Request 創(chuàng)建的部分,可以看到這里如果對(duì) Request 進(jìn)行了修改,在注釋 3 處會(huì)將原 Request 的 id 和 started 賦值過(guò)去,從而防止用戶對(duì)它們進(jìn)行修改。

key 的生成

我們?cè)賮?lái)看看 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();
}

其實(shí)這里就是用一個(gè) StringBuilder 構(gòu)造了一個(gè) String,將 Request 中的各類(lèi)信息都存放于 key 中。這個(gè) key 其實(shí)就是用于內(nèi)存緩存中的 key。

圖片的加載

我們先不去查看內(nèi)存緩存的部分,留到后面來(lái)講解,我們先看看圖片是如何從網(wǎng)絡(luò)加載的。先看到 into 方法的下面這兩句:

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

構(gòu)造 Action

我們先看到 FetchAction 的構(gòu)造方法:

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

調(diào)用了父類(lèi)的構(gòu)造方法:

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

可以看出來(lái),Action 類(lèi)實(shí)際上就是一個(gè)攜帶了需要的信息的類(lèi)。

分發(fā) Action

接著,調(diào)用了 picasso 的 submit 方法:

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

這里調(diào)用了 dispatcher 的 dispatchSubmit 方法:

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

這里用到了一個(gè) DispatcherHandler 類(lèi)的對(duì)象調(diào)用 sendMessage 方法發(fā)送一條信息。這里的 DispatcherHandler 的作用主要是根據(jù)不同的調(diào)用將 Action 分發(fā)到不同的方法中。

下面我們看到 DispatcherHandler 的實(shí)現(xiàn),它是 Dispatcher 的一個(gè)內(nèi)部類(lèi):

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

這里根據(jù)不同的 Message 調(diào)用了不同的方法,我們的 submit 方法調(diào)用了 Dispatcher 中的 performSubmit 方法:

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

它調(diào)用了 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 處根據(jù) Action 獲取到了其對(duì)應(yīng)的 BitmapHunter。

之后在注釋 2 處檢查 service 是否被殺掉。

然后在注釋 3 處,調(diào)用了 forRequest 獲取到了 Action 對(duì)應(yīng)的 BitmapHunter,然后調(diào)用了 service 的 submit 方法。

之后將該 action 與 BitmapHunter 放入了 hunterMap 中。

BitmapHunter 的獲取

我們看一下前面的步驟中 BitmapHunter 是如何獲取的,來(lái)到 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);
}

這里主要是依次遍歷各個(gè) RequestHandler,找到可以處理該類(lèi) Request 的 Handler,并構(gòu)建 BitmapHunter。

我們先看看 RequestHunter 是如何判斷能否處理該類(lèi) Request 的,我們以 NetworkRequestHandler 舉例:

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

可以看到,它是通過(guò)判斷 uri 的 scheme 來(lái)判斷能否處理該類(lèi) Request 的。

我們接著看到 BitmapHunter 的構(gòu)造函數(shù):

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

這里構(gòu)建了一個(gè) PicassoFutureTask,然后調(diào)用了 execute 方法

我們先看看 PicassoFutureTask 的構(gòu)造方法:

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

PicassoFutureTask 是 FutureTask 的子類(lèi),這里主要是變量的賦值。

圖片資源的獲取

接著我們看到 execute 方法,這里其實(shí)是調(diào)用了 Java 自帶的 ThreadPoolExecutor 的 execute 方法。同時(shí)這里也說(shuō)明了這里是一個(gè)異步的過(guò)程。

其實(shí) BitmapHunter 是一個(gè) Runnable,當(dāng)調(diào)用了 execute 方法后便會(huì)執(zhí)行它的 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
}

這里調(diào)用了 hunt 方法獲取到了結(jié)果 Bitmap,同時(shí)在后面根據(jù)不同的結(jié)果通過(guò) dispatcher 進(jìn)行結(jié)果的處理:

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

這里代碼很長(zhǎng),我們慢慢分析:

首先在注釋 1 處嘗試從內(nèi)存通過(guò) key 獲取對(duì)應(yīng) bitmap,若獲取到則直接返回。

之后在注釋 2 處,根據(jù) requestHandler 中的 retryCount 來(lái)判斷是否是網(wǎng)絡(luò)請(qǐng)求,從而獲取不同的 networkPolicy。若 retryCount 為 0 則為離線策略。

之后在注釋 3 處,通過(guò) requestHandler 的 load 方法進(jìn)行數(shù)據(jù)的加載,若數(shù)據(jù)加載成功則進(jìn)行一些變量的賦值,并獲取 bitmap。

若 bitmap 為空則說(shuō)明我們需要在注釋4處將其從流中 decode 出來(lái)。

之后在注釋 5 處就是 Picasso 的加載過(guò)程中支持用戶對(duì)圖片進(jìn)行定制后再應(yīng)用的具體實(shí)現(xiàn)了。這里首先判斷是否需要 transform。

在注釋 6 處判斷如果需要進(jìn)行矩陣變換(旋轉(zhuǎn),放大縮小等),則調(diào)用 transformResult 方法進(jìn)行變換。

在注釋 7 處判斷如果有自定義變換,則調(diào)用 applyCustomTransformations 進(jìn)行自定義變換。

這里的自定義變換比較類(lèi)似前面的自定義 Request 轉(zhuǎn)換,用戶可以在外部自定義 Transformation,并通過(guò) RequestCreator 的 transform 方法傳入,這樣就可以在圖片應(yīng)用前對(duì) Bitmap 進(jìn)行一些自定義 (如高斯模糊等)后再應(yīng)用于 target。這種設(shè)計(jì)是我們值得學(xué)習(xí)的。

RequestHandler 的實(shí)現(xiàn)

下面我們以網(wǎng)絡(luò)圖片對(duì)應(yīng)的 NetworkRequestHandler 為例看看它們的實(shí)現(xiàn),其他的子類(lèi)可以自己去了解。讓我們看到它的 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);
}

可以看到,這里是通過(guò) OkHttp3 來(lái)實(shí)現(xiàn)的圖片的加載。

首先調(diào)用 createRequest 方法創(chuàng)建了 OkHttp 的 Request。然后通過(guò)自己實(shí)現(xiàn)的 OkHttp3Downloader 的 load 方法來(lái)實(shí)現(xiàn)對(duì)這個(gè) Request 的下載請(qǐng)求。

之后根據(jù)緩存的相應(yīng)是否是空判斷數(shù)據(jù)的來(lái)源是從本地還是網(wǎng)絡(luò)。

最終構(gòu)造了一個(gè) Result 并返回。

OkHttp3.Request 的 創(chuàng)建

我們先看看如何將 Request 轉(zhuǎn)換為 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();
}

可以看到,首先根據(jù) Request 和 NetworkPolicy 的參數(shù)設(shè)置緩存的各種參數(shù),之后調(diào)用 okhttp3.Request.Builder 的構(gòu)造函數(shù)并傳入 uri 創(chuàng)建了 Request。

OkHttp3 數(shù)據(jù)的獲取

之后我們看到 OkHttp3Downloader 的 load 方法,看看數(shù)據(jù)獲取是如何實(shí)現(xiàn)的:

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

其實(shí)就是調(diào)用 OkHttpClient 的 newCall 方法并調(diào)用 execute 獲取一個(gè) Response。

結(jié)果的處理

前面提到,在 BitmapHunter 的 run 方法中根據(jù) hunt() 返回的結(jié)果成功與否調(diào)用了 dispatcher 的不同方法來(lái)進(jìn)行的結(jié)果處理,讓我們看看是如何處理的

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

我們先看到 dispatchComplete 方法,它最終通過(guò) handler 調(diào)用到了 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");
  }
}

可以看到,這里如果獲取到了結(jié)果,且需要內(nèi)存緩存,則將其放入內(nèi)存緩存。然后將這個(gè) BitmapHunter 從 Map 中刪除。

之后我們看到 dispatchFailed 方法,它最終通過(guò) handler 調(diào)用到了 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 中移除,然后就沒(méi)有進(jìn)行其他處理了。

內(nèi)存緩存

為了優(yōu)化流量消耗,Picasso 加入了內(nèi)存緩存機(jī)制,下面我們來(lái)看看 Picasso 內(nèi)存緩存的實(shí)現(xiàn)。

就像其他部分一樣,它的內(nèi)存緩存也考慮到了擴(kuò)展性,給了用戶自己實(shí)現(xiàn)的接口。

我們可以調(diào)用 Picasso 類(lèi)的 memoryCache 方法為其設(shè)置 Cache 接口的子類(lèi),從而實(shí)現(xiàn)自己的內(nèi)存緩存。

若用戶不傳入指定緩存,則默認(rèn)使用 Picasso 自己實(shí)現(xiàn)的 LruCache。

具體的 LruCache 的設(shè)計(jì)這里不深入講解,有興趣的各位可以去了解一下 LRU 算法,以后可能可以專(zhuān)門(mén)開(kāi)一篇博客講講 LRU 算法。

Dispatcher 設(shè)計(jì)

其實(shí)從前面的講解中,你會(huì)發(fā)現(xiàn),其實(shí)如圖片的加載請(qǐng)求,緩存命中等等事件都是由一個(gè)叫 Dispatcher 的類(lèi)分發(fā)的,它內(nèi)部由 Handler 實(shí)現(xiàn),負(fù)責(zé)將請(qǐng)求封裝,并按優(yōu)先級(jí)排序,之后按照類(lèi)型分發(fā)。

這種設(shè)計(jì)也很值得我們學(xué)習(xí),它作為一個(gè)分發(fā)中心管理我們的各類(lèi)請(qǐng)求。使得我們的設(shè)計(jì)更為清晰,也使得庫(kù)更容易維護(hù)。

線程池設(shè)計(jì)

之前沒(méi)有提到的就是 Picasso 對(duì)線程池也有一些優(yōu)化,它自己實(shí)現(xiàn)了一個(gè) PicassoExecutorService 類(lèi),它可以根據(jù)當(dāng)前的網(wǎng)絡(luò)狀態(tài),采用不同的線程池?cái)?shù)量,從而使得網(wǎng)絡(luò)不會(huì)過(guò)于擁塞。

具體可以看下面這個(gè)方法:

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

可以看到,線程池最大線程個(gè)數(shù)如下:

  • 在 WIFI 網(wǎng)絡(luò)下,采用最多 4 個(gè)線程的線程池
  • 在 4G 網(wǎng)絡(luò)下,采用最多 3 個(gè)線程的線程池
  • 在 3G 網(wǎng)絡(luò)下,采用最多 2 個(gè)線程的線程池
  • 在 2 G 網(wǎng)絡(luò)下,采用最多 1 個(gè)線程的線程池

總結(jié)

Picasso 是一個(gè)非常值得我們學(xué)習(xí)的輕量級(jí)圖片加載庫(kù),它采用 OkHttp3 來(lái)加載網(wǎng)絡(luò)圖片,并使用了二級(jí)內(nèi)存緩存來(lái)提高加載速度。它的 Dispatcher 思想以及對(duì)外部的擴(kuò)展開(kāi)放的思想十分值得我們學(xué)習(xí),這次源碼的閱讀還是給了我很大的啟發(fā)的。

當(dāng)然,由于篇幅有限,這篇文章并沒(méi)有包含 Picasso 的方方面面,它的代碼中還有如下的一些點(diǎn)在本文還沒(méi)有分析,讀者們有興趣的可以從下面的點(diǎn)去研究這個(gè)庫(kù):

  • 圖片加載的暫停與取消
  • 圖片的變換實(shí)現(xiàn)
  • 請(qǐng)求的優(yōu)先級(jí)
  • 監(jiān)控機(jī)制
  • 本地資源的加載
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容