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

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

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

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

初始化

以我的閱讀源碼的習慣,都是從使用的時候的入口開始入手,因此我們這里從 Picasso 類入手。舊版本的 Picasso 使用了 with 方法作為入口,而在新版本中 with 方法則被 get 方法所替代,并且不再需要傳入 Context 參數(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;
}

可以看到,這里是一個單例類,而它的 Context 則由一個沒有任何實現(xiàn)的 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 的默認實現(xiàn)
}

之后,它調(diào)用了 Builder 的 build 方法返回了一個 Picasso 對象。我們先看到 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 方法中對 downloader、cache 等變量進行了初始化,同時返回了一個新的 Picasso 對象,前面的變量我們先不關(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();
}

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

創(chuàng)建請求

之后我們調(diào)用了 load 方法并傳入了具體的參數(shù)。它有許多重載,可以傳入 Uri、String、File、resourceId 等等類型的數(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) 方法。其實所有的其他重載最后都會指向 load(Uri) 方法,也就是說我們-各種形式的數(shù)據(jù)源最后都是以 Uri 的形式存在于 Picasso 中。我們下面看到 load(Uri):

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

它構(gòu)造了一個 RequestCreator 并返回。接下來我們看到 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)造方法來為 data 進行賦值,我們看到這個構(gòu)造方法:

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

可以看到,這里主要是對 Bitmap.Config 等屬性進行設置。

配置加載屬性

在我們創(chuàng)建了 RequestCreator 后,可以調(diào)用它的 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 這個類中的么?

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

可能看到這里有點亂,大概解釋一下。

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

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

我們以 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 中的一些屬性進行了賦值。

加載圖片

當屬性都設定完后,我們便可以調(diào)用 into 方法來加載圖片,我們看到 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()) {
    // 之前設置的 uri 是否有數(shù)據(jù)(實際上也是判空)
    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)) {
    // 從內(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);
}

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

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

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

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

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

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

最后在注釋 6 處,構(gòu)造了一個 Action 對象然后調(diào)用了 picasso 的 enqueueAndSubmit 進行網(wǎ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 方法對 Request 進行轉(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 對象并將各種 Request.Builder 中的屬性傳遞給這個 Request 對象。

轉(zhuǎn)換 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;
}

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

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

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

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

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

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

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

之后我們回到 Request 創(chuàng)建的部分,可以看到這里如果對 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 構(gòu)造了一個 String,將 Request 中的各類信息都存放于 key 中。這個 key 其實就是用于內(nèi)存緩存中的 key。

圖片的加載

我們先不去查看內(nèi)存緩存的部分,留到后面來講解,我們先看看圖片是如何從網(wǎng)絡加載的。先看到 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)用了父類的構(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);
}

可以看出來,Action 類實際上就是一個攜帶了需要的信息的類。

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

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

下面我們看到 DispatcherHandler 的實現(xiàn),它是 Dispatcher 的一個內(nè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 獲取到了其對應的 BitmapHunter。

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

然后在注釋 3 處,調(diào)用了 forRequest 獲取到了 Action 對應的 BitmapHunter,然后調(diào)用了 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,并構(gòu)建 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 的構(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)建了一個 PicassoFutureTask,然后調(diào)用了 execute 方法

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

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

PicassoFutureTask 是 FutureTask 的子類,這里主要是變量的賦值。

圖片資源的獲取

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

其實 BitmapHunter 是一個 Runnable,當調(diào)用了 execute 方法后便會執(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,同時在后面根據(jù)不同的結(jié)果通過 dispatcher 進行結(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;
}

這里代碼很長,我們慢慢分析:

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

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

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

若 bitmap 為空則說明我們需要在注釋4處將其從流中 decode 出來。

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

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

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

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

RequestHandler 的實現(xiàn)

下面我們以網(wǎng)絡圖片對應的 NetworkRequestHandler 為例看看它們的實現(xiàn),其他的子類可以自己去了解。讓我們看到它的 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 來實現(xiàn)的圖片的加載。

首先調(diào)用 createRequest 方法創(chuàng)建了 OkHttp 的 Request。然后通過自己實現(xiàn)的 OkHttp3Downloader 的 load 方法來實現(xiàn)對這個 Request 的下載請求。

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

最終構(gòu)造了一個 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ù),之后調(diào)用 okhttp3.Request.Builder 的構(gòu)造函數(shù)并傳入 uri 創(chuàng)建了 Request。

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

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

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

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

結(jié)果的處理

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

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

我們先看到 dispatchComplete 方法,它最終通過 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)存緩存。然后將這個 BitmapHunter 從 Map 中刪除。

之后我們看到 dispatchFailed 方法,它最終通過 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 中移除,然后就沒有進行其他處理了。

內(nèi)存緩存

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

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

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

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

具體的 LruCache 的設計這里不深入講解,有興趣的各位可以去了解一下 LRU 算法,以后可能可以專門開一篇博客講講 LRU 算法。

Dispatcher 設計

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

這種設計也很值得我們學習,它作為一個分發(fā)中心管理我們的各類請求。使得我們的設計更為清晰,也使得庫更容易維護。

線程池設計

之前沒有提到的就是 Picasso 對線程池也有一些優(yōu)化,它自己實現(xiàn)了一個 PicassoExecutorService 類,它可以根據(jù)當前的網(wǎng)絡狀態(tài),采用不同的線程池數(shù)量,從而使得網(wǎng)絡不會過于擁塞。

具體可以看下面這個方法:

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

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

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

總結(jié)

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

當然,由于篇幅有限,這篇文章并沒有包含 Picasso 的方方面面,它的代碼中還有如下的一些點在本文還沒有分析,讀者們有興趣的可以從下面的點去研究這個庫:

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

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

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