Picasso 源碼及流程

具體點(diǎn)說屿讽,圖片顯示到界面上這個過程中可能會遇到這些情況:

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

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

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

認(rèn)識核心 API

圖片.png

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


圖片.png

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

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

請求信息相關(guān)的類


圖片.png

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

首先看 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 中放的是一個圖片的本地信息、要進(jìn)行的轉(zhuǎn)換操作信息绍在、圖片配置信息以及優(yōu)先級等门扇。

這里我們可以學(xué)習(xí)到的是:如果一個請求參數(shù)很多,我們最好用一個類給它封裝起來偿渡,避免在傳遞時傳遞多個參數(shù)臼寄;如果經(jīng)常使用的話,還可以創(chuàng)建一個對象池溜宽,節(jié)省開銷吉拳。

接著看第二個類 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è)置占位圖、是否有漸變動畫嫉嘀、是否延遲處理炼邀、以及占位圖錯誤圖資源 ID、內(nèi)存使用策略剪侮、網(wǎng)絡(luò)請求策略等拭宁。
RequestCreator 是相當(dāng)重要的一個類,我們后面會進(jìn)一步介紹它票彪。

接著看第三個類 DeferredRequestCreator:

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

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

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

DeferredRequestCreator 中比較重要的就是這個 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 一樣大時(即調(diào)用 RequestCreator.fit() 方法),會使用到 DeferredRequestCreator驻仅。

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

public enum MemoryPolicy {
  //當(dāng)請求圖片時不去內(nèi)存緩存里找
  NO_CACHE(1 << 0),
  //拿到圖片后不寫到內(nèi)存緩存里噪服,一般用于一次性請求
  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ò)請求策略:

NO_CACHE: 跳過檢查磁盤緩存毡泻,強(qiáng)制請求網(wǎng)絡(luò)
NO_STORE: 拿到結(jié)果不寫入磁盤緩存中
OFFLINE: 不請求網(wǎng)絡(luò),只能去磁盤緩存里查找
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)于請求信息的五個類粘优,小結(jié)一下仇味,它們的作用如下:

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

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

了解完請求信息相關(guān)的類后卵迂,我們再看看 action 文件夾下,關(guān)于加載行為的類(這里的 “加載行為” 是我臨時起的名绒净,可能不是很容易理解狭握,稍后我再解釋一下)。


圖片.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 的成員變量里包含了一個圖片的請求信息和加載策略论颅、錯誤占位圖,同時定義了兩個抽象方法囱嫩,這兩個方法的作用是當(dāng)圖片加載成功后會調(diào)用 complete()(參數(shù)是拿到的圖片和加載來源)恃疯,加載失敗后會調(diào)用 eror(),子類繼承后可以實(shí)現(xiàn)自己特定的操作墨闲。

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

發(fā)起一個圖片加載請求的目的可能有多種鸳碧,最常見的就是加載到圖片上盾鳞,對應(yīng) Picasso 里的 ImageViewAction(加載完成時它會把圖片設(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();
    }
  }

    //失敗時給 ImageView 設(shè)置錯誤圖片
  @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 還提供了四種其他用途的加載行為類,源碼比較容易理解,這里就直接貼出作用:

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

接著介紹 handler 文件夾下的類捂蕴,這個文件夾中類的功能就是:處理去不同渠道加載圖片的請求。


圖片.png

其中 RequestHandler 是基類闪幽,我們先來看看它啥辨。

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 代碼也比較簡單,除了幾個計(jì)算圖片尺寸的方法外盯腌,最關(guān)鍵的就是上述的兩個抽象方法:

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

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

  public static final class Result {
    private final Picasso.LoadedFrom loadedFrom;    //從哪兒加載的(網(wǎng)絡(luò)、內(nèi)存级乍、磁盤)
    private final Bitmap bitmap;
    private final Source source;    //okio 中定義的數(shù)據(jù)流類
    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 的子類實(shí)現(xiàn)都比較簡單舌劳,這里我們就選常見的處理網(wǎng)絡(luò)和文件請求的獲取類來看看。

從名字就可以看出的從網(wǎng)絡(luò)獲取圖片處理類 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ù)請求 uri 的 scheme 判斷是否為 http/https 請求
  @Override public boolean canHandleRequest(Request data) {
    String scheme = data.uri.getScheme();
    return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
  }

    //去網(wǎng)絡(luò)加載一個圖片
  @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 來完成下載的功能甚淡,其中的下載器 downloader 就是通過構(gòu)造一個 okhttp.Call 來完成同步下載文件:

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

從文件獲取圖片的請求處理類 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);
  }
}

也很簡單是吧,根據(jù) URI 獲取輸入流通過 ContentResolver.openInputStream( Uri uri) 可以實(shí)現(xiàn)捅厂,這個可以記一下以后可能會用到贯卦,拿到 IO 流后,接下來的的操作直接通過 Okio 實(shí)現(xiàn)了恒傻。

通過這幾個圖片請求處理類我們可以看到 Picasso 的設(shè)計(jì)多么精巧脸侥,每個類即精簡又功能獨(dú)立,我們在開發(fā)時最好可以參考這樣的代碼盈厘,先定義接口和基類睁枕,然后再考慮不同的實(shí)現(xiàn)。

分析完這些“大家族”后沸手,剩下的就是一些單獨(dú)的類了外遇。

調(diào)度器 Dispatcher

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

我們來看看 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;    //后面介紹山上,獲取圖片最核心的類
  final NetworkBroadcastReceiver receiver;
  final boolean scansNetworkChanges;

  boolean airplaneMode;
}

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

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

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

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

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

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

  public static final int REQUEST_SUBMIT = 1;    //提交請求
  public static final int REQUEST_CANCEL = 2;    //取消請求
  public static final int REQUEST_GCED = 3;    //請求被回收
  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;

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

Dispatcher 的內(nè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)建接受請求的方法:

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)建處理請求的方法了叹侄,比如提交圖片獲取操作的方法:

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)度器的基本信息趾代,掌握如何寫一個調(diào)度器的流程即可

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

public class BitmapHunter implements Runnable {
  //解碼 bitmap 使用的全局鎖,確保一次只解碼一個得湘,避免內(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)鍵類。同時它實(shí)現(xiàn)了 Runnable 接口淘正,在 run() 方法中執(zhí)行耗時任務(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() 方法非常簡單,調(diào)用 hunt() 方法后就是一長串異常捕獲和調(diào)度鸿吆,這里可以看出自定義異常的重要性囤采,在復(fù)雜的 IO、網(wǎng)絡(luò)操作中惩淳,有很多產(chǎn)生異常的可能蕉毯,在不同操作里拋出不同類型的異常乓搬,有助于最后的排查、處理代虾。

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

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

  if (shouldReadFromMemoryCache(memoryPolicy)) {    //1.根據(jù)請求的緩存策略进肯,判斷是否要讀取緩存
    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 來處理圖片加載請求
  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ù)據(jù)還沒有解碼棉磨,因此需要進(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 去處理圖片加載請求
如果 RequestHandler 加載成功但是這個圖片數(shù)據(jù)還沒有解碼衙傀,就去解碼
拿到解碼后的圖片就進(jìn)入下一步抬吟,轉(zhuǎn)換
轉(zhuǎn)換有 Picasso 支持的轉(zhuǎn)換(比如裁剪什么的),也有自定義的
最后返回轉(zhuǎn)換后的圖片

整體類:

圖片.png

流程圖:

圖片.png

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末统抬,一起剝皮案震驚了整個濱河市火本,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蓄喇,老刑警劉巖发侵,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異妆偏,居然都是意外死亡刃鳄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門钱骂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叔锐,“玉大人,你說我怎么就攤上這事见秽∮淅樱” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵解取,是天一觀的道長步责。 經(jīng)常有香客問我,道長禀苦,這世上最難降的妖魔是什么蔓肯? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮振乏,結(jié)果婚禮上蔗包,老公的妹妹穿的比我還像新娘。我一直安慰自己慧邮,他們只是感情好调限,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布舟陆。 她就那樣靜靜地躺著,像睡著了一般耻矮。 火紅的嫁衣襯著肌膚如雪秦躯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天淘钟,我揣著相機(jī)與錄音宦赠,去河邊找鬼。 笑死米母,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的毡琉。 我是一名探鬼主播铁瞒,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼桅滋!你這毒婦竟也來了慧耍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤丐谋,失蹤者是張志新(化名)和其女友劉穎芍碧,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體号俐,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡泌豆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吏饿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片踪危。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖猪落,靈堂內(nèi)的尸體忽然破棺而出贞远,到底是詐尸還是另有隱情,我是刑警寧澤笨忌,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布蓝仲,位于F島的核電站,受9級特大地震影響官疲,放射性物質(zhì)發(fā)生泄漏袱结。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一袁余、第九天 我趴在偏房一處隱蔽的房頂上張望擎勘。 院中可真熱鬧,春花似錦颖榜、人聲如沸棚饵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽噪漾。三九已至硼砰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間欣硼,已是汗流浹背题翰。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留诈胜,地道東北人豹障。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像焦匈,于是被迫代替她去往敵國和親血公。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354