Picasso 源碼及流程

具體點(diǎn)說(shuō),圖片顯示到界面上這個(gè)過(guò)程中可能會(huì)遇到這些情況:

加載的圖片可能有網(wǎng)絡(luò)、本地等多種來(lái)源;
如果是網(wǎng)絡(luò)的話,就得先下載下來(lái);
下載過(guò)程中可能需要暫停、恢復(fù)或者取消;
下載后需要解碼、對(duì)圖片進(jìn)行一些額外操作(比如裁剪、轉(zhuǎn)變等);
最好還有個(gè)緩存系統(tǒng),避免每次都去網(wǎng)絡(luò)請(qǐng)求;
為了實(shí)現(xiàn)性能監(jiān)控,最好再有個(gè)數(shù)據(jù)統(tǒng)計(jì)功能…

有了以上需求,根據(jù)職責(zé)分離的原則,我們可以定義一些核心類(lèi)來(lái)完成上述功能:

請(qǐng)求信息類(lèi):其中包含了所有可以配置的選項(xiàng),比如圖片地址、要進(jìn)行的操作等
圖片獲取類(lèi):根據(jù)不同的來(lái)源去不同地方獲取,比如網(wǎng)絡(luò)、本地、內(nèi)存等
調(diào)度器類(lèi):實(shí)現(xiàn)圖片獲取的入隊(duì)、執(zhí)行、完成、取消、暫停等
圖片處理類(lèi):圖片拿到后進(jìn)行解碼、反轉(zhuǎn)、裁剪等
緩存類(lèi):圖片的內(nèi)存、磁盤(pán)緩存控制
監(jiān)控類(lèi):統(tǒng)計(jì)核心數(shù)據(jù),比如當(dāng)前內(nèi)存、磁盤(pán)緩存的大小、某個(gè)圖片的加載時(shí)間等

認(rèn)識(shí)核心 API

圖片.png

我給 Picasso 文件夾結(jié)構(gòu)進(jìn)行了調(diào)整,變成了這樣:


圖片.png

主要分為幾個(gè)關(guān)鍵部分:

request 文件夾中的:請(qǐng)求信息相關(guān)的類(lèi)
action 文件夾中的:加載行為相關(guān)的類(lèi)
handler 文件夾中的:圖片獲取具體處理的類(lèi)
Dispatcher:調(diào)度器
BitmapHunter:耗時(shí)任務(wù)執(zhí)行者
Picasso:暴露給用戶(hù)的類(lèi)

請(qǐng)求信息相關(guān)的類(lèi)


圖片.png

上圖中的 request 文件夾里放的是 Picasso 中構(gòu)建圖片請(qǐng)求信息相關(guān)的類(lèi),總共有五個(gè),我們來(lái)分別了解下它們。

首先看 Request.java的成員變量(直接看它的 Builder ):

/** Builder for creating {@link Request} instances. */
public static final class Builder {
  private Uri uri;
  private int resourceId;
  private String stableKey;
  private int targetWidth;
  private int targetHeight;
  private boolean centerCrop;
  private int centerCropGravity;
  private boolean centerInside;
  private boolean onlyScaleDown;
  private float rotationDegrees;
  private float rotationPivotX;
  private float rotationPivotY;
  private boolean hasRotationPivot;
  private boolean purgeable;
  private List<Transformation> transformations;
  private Bitmap.Config config;
  private Priority priority;
  //...
}

可以看到,Request 中放的是一個(gè)圖片的本地信息、要進(jìn)行的轉(zhuǎn)換操作信息、圖片配置信息以及優(yōu)先級(jí)等。

這里我們可以學(xué)習(xí)到的是:如果一個(gè)請(qǐng)求參數(shù)很多,我們最好用一個(gè)類(lèi)給它封裝起來(lái),避免在傳遞時(shí)傳遞多個(gè)參數(shù);如果經(jīng)常使用的話,還可以創(chuàng)建一個(gè)對(duì)象池,節(jié)省開(kāi)銷(xiāo)。

接著看第二個(gè)類(lèi) RequestCreator:

public class RequestCreator {
  private static final AtomicInteger nextId = new AtomicInteger();

  private final Picasso picasso;
  private final Request.Builder data;

  private boolean noFade;
  private boolean deferred;
  private boolean setPlaceholder = true;
  private int placeholderResId;
  private int errorResId;
  private int memoryPolicy;
  private int networkPolicy;
  private Drawable placeholderDrawable;
  private Drawable errorDrawable;
  private Object tag;
   //...
}

可以看到, RequestCreator 中包含了 Request.Builder,此外還有了些額外的信息,比如是否設(shè)置占位圖、是否有漸變動(dòng)畫(huà)、是否延遲處理、以及占位圖錯(cuò)誤圖資源 ID、內(nèi)存使用策略、網(wǎng)絡(luò)請(qǐng)求策略等。
RequestCreator 是相當(dāng)重要的一個(gè)類(lèi),我們后面會(huì)進(jìn)一步介紹它。

接著看第三個(gè)類(lèi) DeferredRequestCreator:

public class DeferredRequestCreator implements OnPreDrawListener, OnAttachStateChangeListener {
  private final RequestCreator creator;
  public @VisibleForTesting final WeakReference<ImageView> target;
  @VisibleForTesting
  public Callback callback;
  //...
}

可以看到, DeferredRequestCreator 中引用了 RequestCreator,此外還有一個(gè)要加載的 ImageView 弱引用對(duì)象,還有一個(gè) Callback,它實(shí)現(xiàn)了 OnPreDrawListener 和 onAttachStateChangeListener 接口,這兩個(gè)接口的作用如下:

OnPreDrawListener:當(dāng)布局樹(shù)將要繪制前,會(huì)回調(diào)這個(gè)借口的 onPreDraw() 方法
onAttachStateChangeListener:當(dāng)布局綁定到一個(gè) window 或者解除綁定和一個(gè) window 時(shí)會(huì)調(diào)用

DeferredRequestCreator 中比較重要的就是這個(gè) onPreDraw() 方法:


@Override public boolean onPreDraw() {
  ImageView target = this.target.get();
  if (target == null) {
    return true;
  }

  ViewTreeObserver vto = target.getViewTreeObserver();
  if (!vto.isAlive()) {
    return true;
  }

  int width = target.getWidth();
  int height = target.getHeight();

  if (width <= 0 || height <= 0) {
    return true;
  }

  target.removeOnAttachStateChangeListener(this);
  vto.removeOnPreDrawListener(this);
  this.target.clear();

  this.creator.unfit().resize(width, height).into(target, callback);
  return true;
}

在加載網(wǎng)絡(luò)圖片后需要讓圖片的尺寸和目標(biāo) ImageView 一樣大時(shí)(即調(diào)用 RequestCreator.fit() 方法),會(huì)使用到 DeferredRequestCreator。

剩下的兩個(gè)枚舉 MemoryPolicy 和 NetworkPolicy 就簡(jiǎn)單多了。
MemoryPolicy 指定了兩種內(nèi)存緩存策略:不去內(nèi)存緩存里查找和不寫(xiě)入內(nèi)存緩存。

public enum MemoryPolicy {
  //當(dāng)請(qǐng)求圖片時(shí)不去內(nèi)存緩存里找
  NO_CACHE(1 << 0),
  //拿到圖片后不寫(xiě)到內(nèi)存緩存里,一般用于一次性請(qǐng)求
  NO_STORE(1 << 1);

  public static boolean shouldReadFromMemoryCache(int memoryPolicy) {
    return (memoryPolicy & MemoryPolicy.NO_CACHE.index) == 0;
  }

  public static boolean shouldWriteToMemoryCache(int memoryPolicy) {
    return (memoryPolicy & MemoryPolicy.NO_STORE.index) == 0;
  }
}

NetworkPolicy 指定了三種網(wǎng)絡(luò)請(qǐng)求策略:

NO_CACHE: 跳過(guò)檢查磁盤(pán)緩存,強(qiáng)制請(qǐng)求網(wǎng)絡(luò)
NO_STORE: 拿到結(jié)果不寫(xiě)入磁盤(pán)緩存中
OFFLINE: 不請(qǐng)求網(wǎng)絡(luò),只能去磁盤(pán)緩存里查找
public enum NetworkPolicy {
  NO_CACHE(1 << 0),
  NO_STORE(1 << 1),
  OFFLINE(1 << 2);

  public static boolean shouldReadFromDiskCache(int networkPolicy) {
    return (networkPolicy & NetworkPolicy.NO_CACHE.index) == 0;
  }

  public static boolean shouldWriteToDiskCache(int networkPolicy) {
    return (networkPolicy & NetworkPolicy.NO_STORE.index) == 0;
  }

  public static boolean isOfflineOnly(int networkPolicy) {
    return (networkPolicy & NetworkPolicy.OFFLINE.index) != 0;
  }
}

上面介紹了 Picasso 中關(guān)于請(qǐng)求信息的五個(gè)類(lèi),小結(jié)一下,它們的作用如下:

Request:保存一個(gè)圖片的本地信息、要進(jìn)行的轉(zhuǎn)換操作信息、圖片配置信息以及優(yōu)先級(jí)
RequestCreator:保存了一個(gè)圖片加載請(qǐng)求的完整信息,包括圖片信息、是否設(shè)置占位圖、是否有漸變動(dòng)畫(huà)、是否延遲處理、以及占位圖錯(cuò)誤圖資源 ID、內(nèi)存使用策略、網(wǎng)絡(luò)請(qǐng)求策略等
MemoryPolicy:定義了加載圖片時(shí)的兩種圖片緩存策略
NetworkPolicy:定義了加載圖片時(shí)的三種網(wǎng)絡(luò)請(qǐng)求策略

加載行為相關(guān)的類(lèi)

了解完請(qǐng)求信息相關(guān)的類(lèi)后,我們?cè)倏纯?action 文件夾下,關(guān)于加載行為的類(lèi)(這里的 “加載行為” 是我臨時(shí)起的名,可能不是很容易理解,稍后我再解釋一下)。


圖片.png
public abstract class Action<T> {

  public final Picasso picasso;
  public final Request request;
  public final WeakReference<T> target;
  public final boolean noFade;
  public final int memoryPolicy;
  public final int networkPolicy;
  public final int errorResId;
  public final Drawable errorDrawable;
  public final String key;
  public final Object tag;

  public boolean willReplay;
  public boolean cancelled;

  /**
   * 圖片獲取到后要調(diào)用的方法
   * @param result
   * @param from
   */
  public abstract void complete(Bitmap result, Picasso.LoadedFrom from);

  /**
   * 圖片獲取失敗后要調(diào)用的方法
   * @param e
   */
  public  abstract void error(Exception e);
}

可以看到, Action 的成員變量里包含了一個(gè)圖片的請(qǐng)求信息和加載策略、錯(cuò)誤占位圖,同時(shí)定義了兩個(gè)抽象方法,這兩個(gè)方法的作用是當(dāng)圖片加載成功后會(huì)調(diào)用 complete()(參數(shù)是拿到的圖片和加載來(lái)源),加載失敗后會(huì)調(diào)用 eror(),子類(lèi)繼承后可以實(shí)現(xiàn)自己特定的操作。

前面提到這些 action 表示的是加載行為,所謂“加載行為”簡(jiǎn)單點(diǎn)說(shuō)就是“拿到圖片要干啥”

發(fā)起一個(gè)圖片加載請(qǐng)求的目的可能有多種,最常見(jiàn)的就是加載到圖片上,對(duì)應(yīng) Picasso 里的 ImageViewAction(加載完成時(shí)它會(huì)把圖片設(shè)置給 ImageView):

public class ImageViewAction extends Action<ImageView> {

  Callback callback;

    //加載成功,將圖片設(shè)置給 ImageView
  @Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
    if (result == null) {
      throw new AssertionError(
          String.format("Attempted to complete action with no result!\n%s", this));
    }

    ImageView target = this.target.get();
    if (target == null) {
      return;
    }

    Context context = picasso.context;
    boolean indicatorsEnabled = picasso.indicatorsEnabled;
    PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);  //設(shè)置圖片

    if (callback != null) {
      callback.onSuccess();
    }
  }

    //失敗時(shí)給 ImageView 設(shè)置錯(cuò)誤圖片
  @Override public void error(Exception e) {
    ImageView target = this.target.get();
    if (target == null) {
      return;
    }
    Drawable placeholder = target.getDrawable();
    if (placeholder instanceof Animatable) {
      ((Animatable) placeholder).stop();
    }
    if (errorResId != 0) {
      target.setImageResource(errorResId);
    } else if (errorDrawable != null) {
      target.setImageDrawable(errorDrawable);
    }

    if (callback != null) {
      callback.onError(e);
    }
  }
}

除此外,Picasso 還提供了四種其他用途的加載行為類(lèi),源碼比較容易理解,這里就直接貼出作用:

FetchAction: 拿到圖片后會(huì)有個(gè)回調(diào),除此外不會(huì)將圖片顯示到界面上
    Picasso.fetch() 方法會(huì)使用到它,這個(gè)方法在后臺(tái)線程異步加載圖片,只會(huì)將圖片保存到硬盤(pán)或者內(nèi)存上,不會(huì)顯示到界面上。如果你不久之后就用這個(gè)圖片,或者想要減少加載時(shí)間,你可以提前將圖片下載緩存起來(lái)。
GetAction:拿到圖片后不會(huì)有任何操作,不知道干啥的
    Picasso.get() 方法會(huì)使用到它,這個(gè)方法會(huì)同步加載圖片并返回 Bitmap 對(duì)象,請(qǐng)確保你沒(méi)有在Ui線程里面使用.get() 方法。這將會(huì)阻塞UI!
RemoteViewsAction: 拿到圖片后設(shè)置給 RemoteView,有兩個(gè)實(shí)現(xiàn)類(lèi) AppWidgetAction 和 NotificationAction,分別對(duì)應(yīng)桌面插件和提醒欄
TargetAction:首先 Target 是 Picasso 中定義的一個(gè)接口,表示對(duì)圖片加載的監(jiān)聽(tīng);TargetAction 在拿到圖片后會(huì)調(diào)用 Target 接口的方法

接著介紹 handler 文件夾下的類(lèi),這個(gè)文件夾中類(lèi)的功能就是:處理去不同渠道加載圖片的請(qǐng)求。


圖片.png

其中 RequestHandler 是基類(lèi),我們先來(lái)看看它。

public abstract class RequestHandler {
  /**
   * Whether or not this {@link RequestHandler} can handle a request with the given {@link Request}.
   */
  public abstract boolean canHandleRequest(Request data);

  /**
   * Loads an image for the given {@link Request}.
   *
   * @param request the data from which the image should be resolved.
   * @param networkPolicy the {@link NetworkPolicy} for this request.
   */
  @Nullable public abstract Result load(Request request, int networkPolicy) throws IOException;

RequestHandler 代碼也比較簡(jiǎn)單,除了幾個(gè)計(jì)算圖片尺寸的方法外,最關(guān)鍵的就是上述的兩個(gè)抽象方法:

boolean canHandleRequest(Request data) 表示當(dāng)前獲取類(lèi)能否處理這個(gè)請(qǐng)求,一般子類(lèi)會(huì)根據(jù)請(qǐng)求的 URI 來(lái)判斷
Result load(Request request, int networkPolicy) 表示根據(jù)網(wǎng)絡(luò)策略加載某個(gè)請(qǐng)求,返回加載結(jié)果

加載結(jié)果 Result 也比較簡(jiǎn)單:

  public static final class Result {
    private final Picasso.LoadedFrom loadedFrom;    //從哪兒加載的(網(wǎng)絡(luò)、內(nèi)存、磁盤(pán))
    private final Bitmap bitmap;
    private final Source source;    //okio 中定義的數(shù)據(jù)流類(lèi)
    private final int exifOrientation;    //圖片的旋轉(zhuǎn)方向

    public Result(@NonNull Bitmap bitmap, @NonNull Picasso.LoadedFrom loadedFrom) {
      this(checkNotNull(bitmap, "bitmap == null"), null, loadedFrom, 0);
    }

    public Result(@NonNull Source source, @NonNull Picasso.LoadedFrom loadedFrom) {
      this(null, checkNotNull(source, "source == null"), loadedFrom, 0);
    }

    Result(
        @Nullable Bitmap bitmap,
        @Nullable Source source,
        @NonNull Picasso.LoadedFrom loadedFrom,
        int exifOrientation) {
      if ((bitmap != null) == (source != null)) {
        throw new AssertionError();
      }
      this.bitmap = bitmap;
      this.source = source;
      this.loadedFrom = checkNotNull(loadedFrom, "loadedFrom == null");
      this.exifOrientation = exifOrientation;
    }
  }

RequestHandler 的子類(lèi)實(shí)現(xiàn)都比較簡(jiǎn)單,這里我們就選常見(jiàn)的處理網(wǎng)絡(luò)和文件請(qǐng)求的獲取類(lèi)來(lái)看看。

從名字就可以看出的從網(wǎng)絡(luò)獲取圖片處理類(lèi) NetworkRequestHandler:


public class NetworkRequestHandler extends RequestHandler {
  private static final String SCHEME_HTTP = "http";
  private static final String SCHEME_HTTPS = "https";

  private final Downloader downloader;
  private final Stats stats;

    public NetworkRequestHandler(Downloader downloader, Stats stats) {
    this.downloader = downloader;
    this.stats = stats;
  }
   //根據(jù)請(qǐng)求 uri 的 scheme 判斷是否為 http/https 請(qǐng)求
  @Override public boolean canHandleRequest(Request data) {
    String scheme = data.uri.getScheme();
    return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
  }

    //去網(wǎng)絡(luò)加載一個(gè)圖片
  @Override public Result load(Request request, int networkPolicy) throws IOException {
    okhttp3.Request downloaderRequest = createRequest(request, networkPolicy);
    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.");
    }
    return new Result(body.source(), loadedFrom);
  }
}

從上面的代碼我們可以看到,Picasso 使用 okhttp3 來(lái)完成下載的功能,其中的下載器 downloader 就是通過(guò)構(gòu)造一個(gè) okhttp.Call 來(lái)完成同步下載文件:

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

從文件獲取圖片的請(qǐng)求處理類(lèi) FileRequestHandler:

public class FileRequestHandler extends ContentStreamRequestHandler {

  public FileRequestHandler(Context context) {
    super(context);
  }

  @Override public boolean canHandleRequest(Request data) {
    return SCHEME_FILE.equals(data.uri.getScheme());
  }

  @Override public Result load(Request request, int networkPolicy) throws IOException {
    Source source = Okio.source(getInputStream(request));
    return new Result(null, source, DISK, getFileExifRotation(request.uri));
  }

InputStream getInputStream(Request request) throws FileNotFoundException {
  ContentResolver contentResolver = context.getContentResolver();
  return contentResolver.openInputStream(request.uri);
}


  static int getFileExifRotation(Uri uri) throws IOException {
    ExifInterface exifInterface = new ExifInterface(uri.getPath());
    return exifInterface.getAttributeInt(TAG_ORIENTATION, ORIENTATION_NORMAL);
  }
}

也很簡(jiǎn)單是吧,根據(jù) URI 獲取輸入流通過(guò) ContentResolver.openInputStream( Uri uri) 可以實(shí)現(xiàn),這個(gè)可以記一下以后可能會(huì)用到,拿到 IO 流后,接下來(lái)的的操作直接通過(guò) Okio 實(shí)現(xiàn)了。

通過(guò)這幾個(gè)圖片請(qǐng)求處理類(lèi)我們可以看到 Picasso 的設(shè)計(jì)多么精巧,每個(gè)類(lèi)即精簡(jiǎn)又功能獨(dú)立,我們?cè)陂_(kāi)發(fā)時(shí)最好可以參考這樣的代碼,先定義接口和基類(lèi),然后再考慮不同的實(shí)現(xiàn)。

分析完這些“大家族”后,剩下的就是一些單獨(dú)的類(lèi)了。

調(diào)度器 Dispatcher

調(diào)度器的角色在許多框架里可以看到,實(shí)際上在稍微復(fù)雜一點(diǎn)的業(yè)務(wù)邏輯,都需要這么一個(gè)調(diào)度器類(lèi),它負(fù)責(zé)業(yè)務(wù)邏輯在不同線程的切換、執(zhí)行、取消。

我們來(lái)看看 Picasso 中的調(diào)度器,首先看它的成員變量:

public class Dispatcher {

  private static final String DISPATCHER_THREAD_NAME = "Dispatcher";
  private static final int BATCH_DELAY = 200; // ms

  final DispatcherThread dispatcherThread;    //HandlerThread,用于為子線程 Handler 準(zhǔn)備 Looper
  final Context context;
  final ExecutorService service;    //線程池
  final Downloader downloader;    //下載器
  final Map<String, BitmapHunter> hunterMap;    //Action's key 和 圖片獵人 的關(guān)聯(lián)關(guān)系
  final Map<Object, Action> failedActions;  //失敗的操作 map
  final Map<Object, Action> pausedActions;  //暫停的操作 map
  final Set<Object> pausedTags;    //暫停的 tag
  final Handler handler;    //子線程的 Handler
  final Handler mainThreadHandler;    //ui 線程的 Handler
  final Cache cache;    //緩存
  final Stats stats;    //數(shù)據(jù)統(tǒng)計(jì)
  final List<BitmapHunter> batch;    //后面介紹,獲取圖片最核心的類(lèi)
  final NetworkBroadcastReceiver receiver;
  final boolean scansNetworkChanges;

  boolean airplaneMode;
}

可以看到,Dispatcher 的成員變量有 HandlerThread,兩個(gè) Handler、線程池,下載器、BitmapHunter(我叫它“圖片獵手”,后面介紹它)、緩存、數(shù)據(jù)統(tǒng)計(jì)等等。

從 Picasso 的 Dispatcher 中,我們可以學(xué)到如何創(chuàng)建一個(gè)復(fù)雜業(yè)務(wù)的調(diào)度器。

復(fù)雜業(yè)務(wù)往往需要在子線程中進(jìn)行,于是需要用到線程池;線程之間切換需要用到 Handler,為了省去創(chuàng)建 Looper 的功夫,就需要使用 HandlerThread;此外還需要持有幾個(gè)列表、Map,來(lái)保存操作數(shù)據(jù)。

作為調(diào)度器,最重要的功能就是給外界提供各種功能的接口,一般我們都使用不同的常量來(lái)標(biāo)識(shí)不同的邏輯,在開(kāi)始寫(xiě)業(yè)務(wù)之前,最好先定好功能、確定常量。

我們來(lái)看看 Dispatcher 中定義的常量都代表著什么功能:

  private static final int RETRY_DELAY = 500;    //重試的延遲時(shí)間
  private static final int AIRPLANE_MODE_ON = 1;
  private static final int AIRPLANE_MODE_OFF = 0;

  public static final int REQUEST_SUBMIT = 1;    //提交請(qǐng)求
  public static final int REQUEST_CANCEL = 2;    //取消請(qǐng)求
  public static final int REQUEST_GCED = 3;    //請(qǐng)求被回收
  public static final int HUNTER_COMPLETE = 4;    //圖片獲取完成
  public static final int HUNTER_RETRY = 5;        //重試圖片獲取
  public static final int HUNTER_DECODE_FAILED = 6;    //圖片解碼失敗
  public static final int HUNTER_DELAY_NEXT_BATCH = 7;   
  public static final int HUNTER_BATCH_COMPLETE = 8;    //圖片批量獲取成功
  public static final int NETWORK_STATE_CHANGE = 9;    //網(wǎng)絡(luò)狀態(tài)改變
  public static final int AIRPLANE_MODE_CHANGE = 10;    //飛行模式改變
  public static final int TAG_PAUSE = 11;
  public static final int TAG_RESUME = 12;
  public static final int REQUEST_BATCH_RESUME = 13;

上圖中對(duì)大多數(shù)操作的功能做了注釋。確定好功能后,就可以創(chuàng)建 Handler 了,它負(fù)責(zé)接收不同線程發(fā)出的請(qǐng)求。

Dispatcher 的內(nèi)部類(lèi) DispatcherHandler 是在子線程中執(zhí)行的 Handler:

private static class DispatcherHandler extends Handler {
  private final Dispatcher dispatcher;

  DispatcherHandler(Looper looper, Dispatcher dispatcher) {
    super(looper);
    this.dispatcher = 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;
      }
      //...
    }
  }
}


然后在 Dispatcher 中創(chuàng)建接受請(qǐng)求的方法:

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

public void dispatchCancel(Action action) {
  handler.sendMessage(handler.obtainMessage(REQUEST_CANCEL, action));
}

public void dispatchPauseTag(Object tag) {
  handler.sendMessage(handler.obtainMessage(TAG_PAUSE, tag));
}

public void dispatchResumeTag(Object tag) {
  handler.sendMessage(handler.obtainMessage(TAG_RESUME, tag));
}

最后就是創(chuàng)建處理請(qǐng)求的方法了,比如提交圖片獲取操作的方法:

public void performSubmit(Action action, boolean dismissFailed) {
  if (pausedTags.contains(action.getTag())) {
    pausedActions.put(action.getTarget(), action);
    return;
  }

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

  if (service.isShutdown()) {
    return;
  }

  hunter = forRequest(action.getPicasso(), this, cache, stats, action);
  hunter.future = service.submit(hunter);
  hunterMap.put(action.getKey(), hunter);
  if (dismissFailed) {
    failedActions.remove(action.getTarget());
  }
}

具體一些方法如何實(shí)現(xiàn)的,我們后面再看。這里了解調(diào)度器的基本信息,掌握如何寫(xiě)一個(gè)調(diào)度器的流程即可

最核心的 圖片獵手 BitmapHunter
前面介紹了那么多 API,它們基本是各自實(shí)現(xiàn)一個(gè)單獨(dú)的模塊功能,Picasso 中的 BitmapHunter 是把這些組合起來(lái),具體實(shí)現(xiàn)圖片的獲取、解碼、轉(zhuǎn)換操作的類(lèi)。

public class BitmapHunter implements Runnable {
  //解碼 bitmap 使用的全局鎖,確保一次只解碼一個(gè),避免內(nèi)存溢出
  private static final Object DECODE_LOCK = new Object();

  private static final AtomicInteger SEQUENCE_GENERATOR = new AtomicInteger();
  final int sequence;
  final Picasso picasso;
  final Dispatcher dispatcher;
  final Cache cache;
  final Stats stats;
  final String key;
  final Request data;
  final int memoryPolicy;
  int networkPolicy;
  final RequestHandler requestHandler;

  Action action;
  List<Action> actions;   //要執(zhí)行的操作列表
  Bitmap result;
  Future<?> future;
  Picasso.LoadedFrom loadedFrom;
  Exception exception;
  int exifOrientation; // Determined during decoding of original resource.
  int retryCount;
  Priority priority;
}

可以看到, BitmapHunter 的成員變量有我們前面介紹的那些關(guān)鍵類(lèi)。同時(shí)它實(shí)現(xiàn)了 Runnable 接口,在 run() 方法中執(zhí)行耗時(shí)任務(wù):

@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 (NetworkRequestHandler.ResponseException e) {
    if (!NetworkPolicy.isOfflineOnly(e.networkPolicy) || e.code != 504) {
      exception = e;
    }
    dispatcher.dispatchFailed(this);
  } catch (IOException e) {
    exception = e;
    dispatcher.dispatchRetry(this);    //重試
  } catch (OutOfMemoryError e) {
    StringWriter writer = new StringWriter();
    stats.createSnapshot().dump(new PrintWriter(writer));    //保存內(nèi)存、緩存信息
    exception = new RuntimeException(writer.toString(), e);
    dispatcher.dispatchFailed(this);    
  } catch (Exception e) {
    exception = e;
    dispatcher.dispatchFailed(this);
  } finally {
    Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);
  }
}

run() 方法非常簡(jiǎn)單,調(diào)用 hunt() 方法后就是一長(zhǎng)串異常捕獲和調(diào)度,這里可以看出自定義異常的重要性,在復(fù)雜的 IO、網(wǎng)絡(luò)操作中,有很多產(chǎn)生異常的可能,在不同操作里拋出不同類(lèi)型的異常,有助于最后的排查、處理。

我們來(lái)看看完成主要任務(wù)的 hunt() 方法:

public Bitmap hunt() throws IOException {
  Bitmap bitmap = null;

  if (shouldReadFromMemoryCache(memoryPolicy)) {    //1.根據(jù)請(qǐng)求的緩存策略,判斷是否要讀取緩存
    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.調(diào)用適當(dāng)?shù)?requestHandler 來(lái)處理圖片加載請(qǐng)求
  networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
  RequestHandler.Result result = requestHandler.load(data, networkPolicy);
  if (result != null) {   //加載成功
    loadedFrom = result.getLoadedFrom();
    exifOrientation = result.getExifOrientation();
    bitmap = result.getBitmap();

    //拿到圖片加載結(jié)果時(shí),有可能這個(gè)數(shù)據(jù)還沒(méi)有解碼,因此需要進(jìn)行解碼
    if (bitmap == null) {
      Source source = result.getSource();
      try {
        bitmap = decodeStream(source, data);  //解碼操作
      } finally {
        try {
          source.close();
        } catch (IOException ignored) {
        }
      }
    }
  }

  //3.拿到圖片加載結(jié)果后有解碼好的 bitmap,進(jìn)入下一步,轉(zhuǎn)換
  if (bitmap != null) {   
    if (picasso.loggingEnabled) {
      log(OWNER_HUNTER, VERB_DECODED, data.logId());
    }
    stats.dispatchBitmapDecoded(bitmap);
    if (data.needsTransformation() || exifOrientation != 0) {
      synchronized (DECODE_LOCK) {
        if (data.needsMatrixTransform() || exifOrientation != 0) {
          bitmap = transformResult(data, bitmap, exifOrientation);
          if (picasso.loggingEnabled) {
            log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());
          }
        }
        if (data.hasCustomTransformations()) {    //進(jìn)行自定義的轉(zhuǎn)換
          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;
}

可以看到,BitmapHunter 中獲取圖片的 hunt() 方法的邏輯如下:

如果緩存策略允許去內(nèi)存緩存讀取,就去緩存里找,找到就返回
否則調(diào)用適當(dāng)?shù)?RequestHandler 去處理圖片加載請(qǐng)求
如果 RequestHandler 加載成功但是這個(gè)圖片數(shù)據(jù)還沒(méi)有解碼,就去解碼
拿到解碼后的圖片就進(jìn)入下一步,轉(zhuǎn)換
轉(zhuǎn)換有 Picasso 支持的轉(zhuǎn)換(比如裁剪什么的),也有自定義的
最后返回轉(zhuǎn)換后的圖片

整體類(lèi):

圖片.png

流程圖:

圖片.png

最后編輯于
?著作權(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ù)。

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