Android圖片加載框架Picasso源碼分析(基于Picasso 2.71828)

Picasso 是 Android 開發(fā)中最受歡迎的圖片請求加載框架之一 吃既,它誕生于 2013 年,距今已有五年的生命申鱼。在這五年間 Picasso 發(fā)布過 21 個版本更新,而最近的一次更新為今年的 3 月 8 日匣砖,更新的版本號為 2.71828(文中統(tǒng)稱為新版)对人,該版本離上一次發(fā)布更新相隔了三年。本文主要分析新版 Picasso 的源碼實現(xiàn)和它的一些 API 變化。

1. 新版 Picasso 的使用

新版 Picasso 最直觀的變化就是在 App 中的調(diào)用方式為:

Picasso.get().load(url).into(imageView);

該調(diào)用跟原來版本的調(diào)用區(qū)別是咱台,沒有了需要傳 Context 的 with 方法,取而代之是一個不需要傳參的 get 方法來獲取全局唯一 Picasso 實例遗遵。

本文也主要通過分析 Picasso.get().load(url).into(imageView) 該句調(diào)用的來龍去脈來理清新版 Picasso 框架的實現(xiàn)原理。

2. Picasso 實例的獲取

Picasso 實例是通過調(diào)用 Picasso 類中的靜態(tài)方法 get 獲取的屯蹦,該方法也是新版 Picasso 的入口,我們從該方法開始看起:

static volatile Picasso singleton = null;

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

上面這段代碼是一個非常經(jīng)典的雙重檢查鎖模式 (Double Checked Locking Pattern)。

首先進入 get 方法即檢查一次 Picasso 實例 singleton 是否為 null跪削,不為 null 就可以直接返回該實例。

接著進入到同步塊毫玖,因為可能會一個線程進入同步塊后創(chuàng)建完對象后退出,另一個線程又緊接著進入同步塊二打,因此進入同步塊后需要再檢查一次 singleton 是否為 null,如果還為 null莲趣,這時候可以開始初始化該實例。

最后靜態(tài)變量 singleton 需要用 volatile 關鍵字來修飾,目的是為了防止重排序溉仑。

2.1 使用 ContentProvider 獲取 Context

新版 Picasso 提供給我們的入口方法 get 不需要傳 Context,因為它使用的是 PicassoProvider 類中的 context,我們看一下 PicassoProvider 類:

public final class PicassoProvider extends ContentProvider {

  @SuppressLint("StaticFieldLeak") static Context context;

  @Override public boolean onCreate() {
    context = getContext();
    return true;
  }
    
}

PicassoProvider 類繼承自 ContentProvider后频,除了 onCreate 方法,其他方法都是默認實現(xiàn) (為了節(jié)省篇幅露久,省略了該部分代碼),而 onCreate 方法也只是調(diào)用 getContext 方法并賦值給靜態(tài)變量 context眶痰,然后返回 true 表示成功加載了該 ContentProvider。

Picasso 這么做的理由是,只要將 PicassoProvider 在 AndroidManifest 文件中注冊打厘,那么 App 在啟動的時候饲化,系統(tǒng)就會自動回調(diào) PicassoProvider 的 onCreate 方法硫眨,因此也就自動獲取到了 Context姥闭。

2.2 Picasso 實例的創(chuàng)建

接著回到 Picasso.get 方法中卜高,Picasso 實例通過該句代碼創(chuàng)建:

singleton = new Builder(PicassoProvider.context).build();

這里使用到了常用的 Builder 設計模式庭敦。當一個類的屬性過多,通過構造函數(shù)構造一個對象過于復雜時薪缆,可以選擇使用 Builder 設計模式來簡化對象的構造過程秧廉。

看下 Picasso 類中靜態(tài)內(nèi)部類 Builder 的構造函數(shù):

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

該構造函數(shù)確保傳進來的 Context 實例不為 null伞广,然后獲取全局的 Application Context。

接著是 Picasso.Builder 類中的 build 方法:

private final Context context;
private Downloader downloader;
private ExecutorService service;
private Cache cache;
private Listener listener;
private RequestTransformer transformer;
private List<RequestHandler> requestHandlers;
private Bitmap.Config defaultBitmapConfig;

public Picasso build() {
  Context context = this.context;
  // 配置下載器 Downloader疼电,用于從網(wǎng)絡下載圖片資源嚼锄,默認為 OkHttp3Downloader
  if (downloader == null) {
    downloader = new OkHttp3Downloader(context);
  }
  // 配置緩存 Cache,用來保存最近查看使用的圖片拾因,默認為 LruCache
  if (cache == null) {
    cache = new LruCache(context);
  }
  // 配置 ExecutorService惠遏,默認為 PicassoExecutorService
  // 后面 Bitmap 的獲取任務就在該線程池中完成
  if (service == null) {
    service = new PicassoExecutorService();
  }
  // 配置 RequestTransformer 實例
  if (transformer == null) {
    transformer = RequestTransformer.IDENTITY;
  }
  // 創(chuàng)建 Stats 實例帚豪,Stats 類用來進行一些統(tǒng)計煤禽,如緩存命中數(shù)知牌,圖片下載數(shù)等
  Stats stats = new Stats(cache);
  // 創(chuàng)建 Dispatcher 實例,Dispatcher 類顧名思義疟游,它的作用就是用來分發(fā)處理
  // 各種圖片操作事件的如提交圖片請求事件,圖片獲取完成事件等容劳;
  // 傳入前面配置好的對象和 HANDLER 實例給 Dispatcher 類構造函數(shù)
  // 該 HANDLER 在主線程接收處理事件,后面獲取到 Bitmap 后需要回調(diào)到
  // 該 HANDLER 的 handleMessage 方法中以便將 Bitmap 切換回主線程顯示
  Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
  // 傳入前面配置好的一系列參數(shù),創(chuàng)建 Picasso 實例
  return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
      defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}

該方法的邏輯與相關類作用已在注釋中進行了說明侈沪。

接下來看 Picasso 類的構造函數(shù):

private final Listener listener;
private final RequestTransformer requestTransformer;
private final CleanupThread cleanupThread;
private final List<RequestHandler> requestHandlers;

final Context context;
final Dispatcher dispatcher;
final Cache cache;
final Stats stats;
final Map<Object, Action> targetToAction;
final Map<ImageView, DeferredRequestCreator> targetToDeferredRequestCreator;
final ReferenceQueue<Object> referenceQueue;
final Bitmap.Config defaultBitmapConfig;

boolean indicatorsEnabled;
volatile boolean loggingEnabled;

boolean shutdown;

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;
  // Picasso 默認包含七個內(nèi)置 RequestHandler 分別用來處理七種不同類型的請求
  // 你也可以自己繼承 RequestHandler 類來處理你的自定義請求
  // 自定義請求放在 extraRequestHandlers 中
  int builtInHandlers = 7;
  int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
  List<RequestHandler> allRequestHandlers = new ArrayList<>(builtInHandlers + extraCount);
  // 添加 ResourceRequestHandler盲泛,用于處理加載圖片資源 id 的情況
  // ResourceRequestHandler 需要第一個進行添加
  // 避免其他的 RequestHandler 檢查 (request.resourceId != 0) 的情況
  allRequestHandlers.add(new ResourceRequestHandler(context));
  // 然后添加自定義的 RequestHandler (如果有的話)
  if (extraRequestHandlers != null) {
    allRequestHandlers.addAll(extraRequestHandlers);
  }
  // 添加 ContactsPhotoRequestHandler瘸彤,用于處理手機聯(lián)系人圖片
  allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
  // 添加 MediaStoreRequestHandler,用于處理 content://media/ 開頭的 URI
  allRequestHandlers.add(new MediaStoreRequestHandler(context));
  // 添加 ContentStreamRequestHandler,用于處理 scheme 為 content 的 URI
  allRequestHandlers.add(new ContentStreamRequestHandler(context));
  // 添加 AssetRequestHandler黍氮,用于處理 file:///android_asset/ 開頭的 URI
  allRequestHandlers.add(new AssetRequestHandler(context));
  // 添加 FileRequestHandler本股,用于處理 scheme 為 file 的 URI
  allRequestHandlers.add(new FileRequestHandler(context));
  // 添加 NetworkRequestHandler蟆盐,用于處理 http 或 https 圖片 url
  allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
  // 調(diào)用 Collections 的靜態(tài)方法 unmodifiableList 
  // 返回一個不能進行修改操作的 List 實例,防止 requestHandlers 被修改
  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();
}

到這里 Picasso 實例就創(chuàng)建完畢了咙崎。

2.3 Dispatcher 實例的創(chuàng)建

Picasso.Builder 類的 build 方法在創(chuàng)建 Picasso 實例前先創(chuàng)建了 Dispatcher 類的實例优幸,Dispatcher 類對后面分發(fā)處理圖片事件至關重要,這里先看一下它的構造函數(shù):

final DispatcherThread dispatcherThread;
final Context context;
final ExecutorService service;
final Downloader downloader;
final Map<String, BitmapHunter> hunterMap;
final Map<Object, Action> failedActions;
final Map<Object, Action> pausedActions;
final Set<Object> pausedTags;
final Handler handler;
final Handler mainThreadHandler;
final Cache cache;
final Stats stats;
final List<BitmapHunter> batch;
final NetworkBroadcastReceiver receiver;
final boolean scansNetworkChanges;

boolean airplaneMode;

Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler,
    Downloader downloader, Cache cache, Stats stats) {
  // 創(chuàng)建靜態(tài)內(nèi)部類 DispatcherThread 的實例并啟動
  this.dispatcherThread = new DispatcherThread();
  this.dispatcherThread.start();
  this.context = context;
  this.service = service;
  this.hunterMap = new LinkedHashMap<>();
  this.failedActions = new WeakHashMap<>();
  this.pausedActions = new WeakHashMap<>();
  this.pausedTags = new LinkedHashSet<>();
  // 創(chuàng)建靜態(tài)內(nèi)部類 DispatcherHandler 的實例
  this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
  this.downloader = downloader褪猛;
  // 保存前面 Picasso 類傳進來的主線程 HANDLER
  this.mainThreadHandler = mainThreadHandler;
  this.cache = cache;
  this.stats = stats;
  this.batch = new ArrayList<>(4);
}

該構造函數(shù)首先創(chuàng)建了 DispatcherThread 實例网杆,而后面 DispatcherHandler 實例的創(chuàng)建用到了 DispatcherThread 中的 Looper。

DispatcherThread 和 DispatcherHandler 都是 Dispatcher 中的靜態(tài)內(nèi)部類:

static class DispatcherThread extends HandlerThread {
  DispatcherThread() {
    super(Utils.THREAD_PREFIX + DISPATCHER_THREAD_NAME, THREAD_PRIORITY_BACKGROUND);
  }
}
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 HUNTER_COMPLETE: {
        BitmapHunter hunter = (BitmapHunter) msg.obj;
        dispatcher.performComplete(hunter);
        break;
      }
      default:
        Picasso.HANDLER.post(new Runnable() {
          @Override public void run() {
            throw new AssertionError("Unknown handler message received: " + msg.what);
          }
        });
    }
  }
}

可以看到伊滋,DispatcherThread 繼承自 HandlerThread碳却,而 DispatcherHandler 實例是通過 DispatcherThread 的 Looper 創(chuàng)建的,因此 DispatcherHandler 發(fā)送的消息將切換到工作線程 (即 DispatcherThread) 中處理笑旺,即 DispatcherHandler 的 handleMessage 方法會在工作線程中執(zhí)行昼浦。

3. Picasso 類中的 load 方法

獲取到 Picasso 實例后,緊接著調(diào)用 Picasso 類的 load 方法燥撞,該方法主要作用就是創(chuàng)建并返回一個 RequestCreator 實例座柱。

RequestCreator 類的主要作用就是創(chuàng)建 Request 對象,并提供了一系列的 into 方法來開始圖片請求物舒。

先來看一下 Picasso 類中的 load 方法:

public RequestCreator load(@Nullable String path) {
  // 如果傳進來的 path 為 null色洞,創(chuàng)建并返回一個
  // Uri 為 null 的 RequestCreator 對象。
  if (path == null) {
    return new RequestCreator(this, null, 0);
  }
  // 如果 path 為空字符串冠胯,拋出異常火诸。
  if (path.trim().length() == 0) {
    throw new IllegalArgumentException("Path must not be empty.");
  }
  return load(Uri.parse(path));
}

最后 path 不為 null 也不為空字符串,則調(diào)用 Uri.parse(path) 方法對 path 進行解析并返回 一個 Uri 對象傳給 load(@Nullable Uri uri) 方法荠察。

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

該方法創(chuàng)建并返回一個 RequestCreator 實例置蜀,看一下 RequestCreator 類的構造函數(shù):

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

RequestCreator(Picasso picasso, Uri uri, int resourceId) {
  this.picasso = picasso;
  this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}

可以看到 RequestCreator 對象持有了 Picasso 的一份引用,然后創(chuàng)建了 Request.Builder 類的實例 data悉盆,這里又用到了 Builder 設計模式盯荤。

Request.Builder 的構造函數(shù)為:

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

到這里 RequestCreator 就創(chuàng)建完成了,接下來就可以調(diào)用 RequestCreator 類中的許多方法焕盟,如 placeholder 方法秋秤,centerCrop 方法等,我們這里直接前往 into 方法。

4. RequestCreator 類的 into 方法

經(jīng)過 load 方法后灼卢,緊接著就來到了 into 方法绍哎,該方法是整個 API 調(diào)用流程的最后一步,可以說是整個調(diào)用流程的重頭戲鞋真,因此篇幅也比較大崇堰。

我們往 into 方法傳的是 ImageView 實例,看一下該方法源碼:

public void into(ImageView target) {
  into(target, null);
}
public void into(ImageView target, Callback callback) {
  // 記錄開始處理的時間戳
  long started = System.nanoTime();
  // 檢查當前方法是否在主線程進行調(diào)用涩咖,如果不是拋出異常
  checkMain();
  // ImageView 實例 target 不能為 null海诲,否則拋異常
  if (target == null) {
    throw new IllegalArgumentException("Target must not be null.");
  }
  // data 即前面的 Request.Builder 實例
  // 如果 data 中沒有圖片(例如傳入的 path 為 null)
  // 直接對該 target 取消請求,并設置占位圖如果有設置 placeholder
  if (!data.hasImage()) {
    picasso.cancelRequest(target);
    if (setPlaceholder) {
      setPlaceholder(target, getPlaceholderDrawable());
    }
    return;
  }
  // 創(chuàng)建 Request 實例
  Request request = createRequest(started);
  // 為當前 Request 生成一個 requestKey抠藕,用來標記 Request
  String requestKey = createKey(request);
  // 如果當前的 memoryPolicy 允許從緩存中讀取圖片
  // 從 Cache 中獲取 requestKey 對應的 Bitmap饿肺,如果該 Bitmap 存在
  // 則取消當前請求蒋困,直接為 target 設置該 Bitmap
  if (shouldReadFromMemoryCache(memoryPolicy)) {
    Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
    if (bitmap != null) {
      picasso.cancelRequest(target);
      setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
      if (callback != null) {
        callback.onSuccess();
      }
      return;
    }
  }

  // 前面緩存中沒有查找到圖片盾似,從這里開始請求
  // 先設置 placeholder 如果有配置的話 
  if (setPlaceholder) {
    setPlaceholder(target, getPlaceholderDrawable());
  }

  // 創(chuàng)建一個 ImageViewAction 的實例
  Action action = new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
          errorDrawable, requestKey, tag, callback, noFade);
  // 向 picasso 提交該 Action 實例
  picasso.enqueueAndSubmit(action);
}

這里省略了部分與 Picasso.get().load(uri).into(imageView) 調(diào)用不相關的代碼,代碼中的注釋只是大致邏輯流程雪标,接下來按 into 方法的代碼邏輯對一些細節(jié)進行分析零院。

4.1 設置占位圖 (placeholder) 的實現(xiàn)

從 into 方法中可以看到,有兩處需要設置占位圖村刨,設置占位圖的邏輯是:

if (setPlaceholder) {
  setPlaceholder(target, getPlaceholderDrawable());
}

看一下 getPlaceholderDrawable 方法:

private Drawable getPlaceholderDrawable() {
  if (placeholderResId != 0) {
    if (Build.VERSION.SDK_INT >= 21) {
      return picasso.context.getDrawable(placeholderResId);
    } else if (Build.VERSION.SDK_INT >= 16) {
      return picasso.context.getResources().getDrawable(placeholderResId);
    } else {
      TypedValue value = new TypedValue();
      picasso.context.getResources().getValue(placeholderResId, value, true);
      return picasso.context.getResources().getDrawable(value.resourceId);
    }
  } else {
    return placeholderDrawable;
  }
}

該方法根據(jù)當前運行的 Android SDK 版本進行不同的方法調(diào)用來通過 placeholderResId 獲取到一個 Drawable 實例告抄。

獲取到占位圖 Drawable 后接下來就可以進行設置了,setPlaceholder 方法為 PicassoDrawable 類中的靜態(tài)方法嵌牺,調(diào)用該方法后 ImageView 就可以顯示占位圖了打洼。

static void setPlaceholder(ImageView target, Drawable placeholderDrawable) {
  target.setImageDrawable(placeholderDrawable);
  if (target.getDrawable() instanceof Animatable) {
    ((Animatable) target.getDrawable()).start();
  }
}

4.2 Request 實例的創(chuàng)建

Request 對象是通過 createRequest 方法創(chuàng)建的:

private static final AtomicInteger nextId = new AtomicInteger();

private Request createRequest(long started) {
  // 為 Request 實例分配下一個 id
  int id = nextId.getAndIncrement();
  // 創(chuàng)建 Request 實例
  Request request = data.build();
  request.id = id;
  request.started = started;
  return request;
}

Request 實例通過調(diào)用 Request.Builder 類的 build 方法創(chuàng)建:

public Request build() {
  // 先驗證當前的配置參數(shù)是否合法
  // centerInside 和 centerCrop 方法不能同時用
  if (centerInside && centerCrop) {
    throw new IllegalStateException("Center crop and center inside can not be used together.");
  }
  // centerCrop 方法需要與 resize 方法同用
  if (centerCrop && (targetWidth == 0 && targetHeight == 0)) {
    throw new IllegalStateException(
        "Center crop requires calling resize with positive width and height.");
  }
  // centerInside 方法需要與 resize 方法同用
  if (centerInside && (targetWidth == 0 && targetHeight == 0)) {
    throw new IllegalStateException(
        "Center inside requires calling resize with positive width and height.");
  }
  // 設置 priority
  if (priority == null) {
    priority = Priority.NORMAL;
  }
  // 創(chuàng)建 Request 實例
  return new Request(uri, resourceId, stableKey, transformations, targetWidth, targetHeight,
      centerCrop, centerInside, centerCropGravity, onlyScaleDown, rotationDegrees,
      rotationPivotX, rotationPivotY, hasRotationPivot, purgeable, config, priority);
}

該方法在最后一步創(chuàng)建了 Request 實例,看一下 Request 類的構造函數(shù):

public final Uri uri;
public final int resourceId;
public final String stableKey;
public final List<Transformation> transformations;
public final int targetWidth;
public final int targetHeight;
public final boolean centerCrop;
public final int centerCropGravity;
public final boolean centerInside;
public final boolean onlyScaleDown;
public final float rotationDegrees;
public final float rotationPivotX;
public final float rotationPivotY;
public final boolean hasRotationPivot;
public final boolean purgeable;
public final Bitmap.Config config;
public final Priority priority;

private Request(Uri uri, int resourceId, String stableKey, List<Transformation> transformations,
    int targetWidth, int targetHeight, boolean centerCrop, boolean centerInside,
    int centerCropGravity, boolean onlyScaleDown, float rotationDegrees,
    float rotationPivotX, float rotationPivotY, boolean hasRotationPivot,
    boolean purgeable, Bitmap.Config config, Priority priority) {
  // 圖片的 Uri逆粹,與 resourceId 不能共存
  this.uri = uri;
  // 圖片的 resourceId募疮,與 Uri 不能共存
  this.resourceId = resourceId;
  this.stableKey = stableKey;
  // 用來對 Bitmap 進行轉換的一系列 Transformation
  if (transformations == null) {
    this.transformations = null;
  } else {
    this.transformations = unmodifiableList(transformations);
  }
  // resize 方法設置的圖片寬度和高度
  this.targetWidth = targetWidth;
  this.targetHeight = targetHeight;
  // 圖片 scaleType 是否為 centerCrop,與 centerInside 不共存
  this.centerCrop = centerCrop;
  // 圖片 scaleType 是否為 centerInside僻弹,與 centerCrop 不共存
  this.centerInside = centerInside;
  // 如果設置了 centerCrop阿浓,centerCropGravity 用來設置中心的偏移量
  this.centerCropGravity = centerCropGravity;
  this.onlyScaleDown = onlyScaleDown;
  // 圖片旋轉的度數(shù)
  this.rotationDegrees = rotationDegrees;
  this.rotationPivotX = rotationPivotX;
  this.rotationPivotY = rotationPivotY;
  this.hasRotationPivot = hasRotationPivot;
  this.purgeable = purgeable;
  this.config = config;
  // 當前請求的優(yōu)先級
  this.priority = priority;
}

Request 實例創(chuàng)建完畢。

4.3 requestKey 的創(chuàng)建

requestKey 用來標識一個 Request蹋绽,requestKey 通過調(diào)用 createKey 方法實現(xiàn):

static final StringBuilder MAIN_THREAD_KEY_BUILDER = new StringBuilder();

static String createKey(Request data) {
  String result = createKey(data, MAIN_THREAD_KEY_BUILDER);
  MAIN_THREAD_KEY_BUILDER.setLength(0);
  return result;
}
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) {
    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();
}

該方法根據(jù)當前 Request 配置的參數(shù)來生成對應的 requestKey芭毙。

4.4 MemoryPolicy 的實現(xiàn)

into 方法通過調(diào)用 shouldReadFromMemoryCache 方法來判斷是否應該從 Cache 中讀取當前 requestKey 對應的 Bitmap。

shouldReadFromMemoryCache 方法是枚舉類型 MemoryPolicy 中的一個靜態(tài)方法卸耘,MemoryPolicy 源碼如下:

public enum MemoryPolicy {

  NO_CACHE(1 << 0),
  NO_STORE(1 << 1);

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

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

  final int index;

  MemoryPolicy(int index) {
    this.index = index;
  }
    
}

可以看到 MemoryPolicy 用到了一些位操作退敦。

MemoryPolicy 共兩種枚舉類型,NO_CACHE 和 NO_STORE蚣抗,NO_CACHE 的 index 為 1 (二進制為 1)侈百,NO_STORE 的 index 為 2 (二進制為 10)。

shouldReadFromMemoryCache 方法返回 true 如果 (memoryPolicy & MemoryPolicy.NO_CACHE.index) == 0 ,即 memoryPolicy 為 0 设哗。返回 true 表示當前 memoryPolicy 允許從 Cache 中讀取圖片唱捣。

shouldWriteToMemoryCache 方法在滿足 (memoryPolicy & MemoryPolicy.NO_STORE.index) == 0 的條件下返回 true,即 memoryPolicy 為 0网梢。返回 true 表示當前 memoryPolicy 允許向 Cache 中寫入圖片震缭。

而在沒有配置 memoryPolicy 的情況下,memoryPolicy 默認為 0战虏,因此這兩個方法這里都會返回 true拣宰。

4.5 Action 實例的創(chuàng)建

Picasso 將一次圖片獲取活動封裝成一個 Action 實例。

Action 為抽象類烦感,包含兩個必須實現(xiàn)的抽象方法巡社,complete 方法和 error 方法,分別表示該次圖片獲取活動完成或出錯手趣。

abstract void complete(Bitmap result, Picasso.LoadedFrom from);
abstract void error(Exception e);

Picasso 提供了不同的 Action 子類來對應不同的圖片獲取活動晌该。

我們這里用到的是 ImageViewAction,ImageViewAction 用于將獲取到的 Bitmap 加載到 ImageView 中:

class ImageViewAction extends Action<ImageView> {

  Callback callback;

  ImageViewAction(Picasso picasso, ImageView imageView, Request data, int memoryPolicy,
      int networkPolicy, int errorResId, Drawable errorDrawable, String key, Object tag,
      Callback callback, boolean noFade) {
    super(picasso, imageView, data, memoryPolicy, networkPolicy, errorResId, errorDrawable, key,
        tag, noFade);
    this.callback = callback;
  }

  @Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
    // 獲取要加載圖片進去的 ImageView
    ImageView target = this.target.get();
    if (target == null) return;
    Context context = picasso.context;
    boolean indicatorsEnabled = picasso.indicatorsEnabled;
    // 為 target 設置 Bitmap
    PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);
    // 回調(diào) callback
    if (callback != null) callback.onSuccess();
  }

  @Override public void error(Exception e) {
    ImageView target = this.target.get();
    if (target == null) return;
    // 獲取占位圖 Drawable 實例绿渣,如果該 Drawable 實現(xiàn)了 Animatable 接口
    // 這時候就應該停止該 Animatable
    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);
  }

  @Override void cancel() {
    super.cancel();
    if (callback != null) callback = null;
  }
}

可以看到 ImageViewAction 類繼承自 Action朝群,泛型參數(shù)為 ImageView 作為該 Action 的 target。

ImageViewAction 實現(xiàn)了 complete 方法和 error 方法并重寫了 cancel 方法中符。

4.6 Action 實例的提交

Action 實例創(chuàng)建完畢后姜胖,就可以調(diào)用 Picasso 類中的 enqueueAndSubmit 方法提交該 Action 實例了,然后從這里開始一次圖片獲取活動淀散,由于這部分代碼過于龐大右莱,從 enqueueAndSubmit 方法開始放在下一節(jié)分析。

5. Picasso 處理 Action 的實現(xiàn)

Picasso 處理 Action 從 Picasso 類的 enqueueAndSubmit 方法開始:

void enqueueAndSubmit(Action action) {
  // 省略部分代碼...
  // 調(diào)用 submit 方法提交該 Action
  submit(action);
}
void submit(Action action) {
  dispatcher.dispatchSubmit(action);
}

submit 方法又調(diào)用了 Dispatcher 類的 dispatchSubmit 方法档插,Picasso 類這時候就將此 Action 交接給 Dispatcher 類進行處理慢蜓。

5.1 Dispatcher 類提交 Action 的實現(xiàn)

Picasso 通過調(diào)用 Dispatcher 類的 dispatchSubmit 方法開始提交該 Action 實例:

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

該方法使用 handler 發(fā)送了一條 REQUEST_SUBMIT 信息,我們在 2.3 小節(jié)中可以看到阀捅,該 handler 即 DispatcherHandler 實例胀瞪,發(fā)送該消息后會在工作線程回調(diào)到 DispatcherHandler 中的 handleMessage 方法,然后從該方法接著會調(diào)用 Dispatcher 類的 performSubmit 方法:

void performSubmit(Action action) {
  performSubmit(action, true);
}
void performSubmit(Action action, boolean dismissFailed) {
  // 創(chuàng)建 BitmapHunter 實例
  BitmapHunter hunter = forRequest(action.getPicasso(), this, cache, stats, action);
  // 使用配置好的 PicassoExecutorService 提交該 BitmapHunter 實例
  hunter.future = service.submit(hunter);
}

上面的代碼做了一些簡化饲鄙,刪除了與當前邏輯無關的代碼凄诞。

該方法通過調(diào)用 forRequest 方法來創(chuàng)建 BitmapHunter 實例,而 forRequest 方法是 BitmapHunter 類中的靜態(tài)方法忍级。

從上面的代碼可以看到帆谍,接下來的工作就交給 BitmapHunter 來完成了。

5.2 BitmapHunter 類的實現(xiàn)

BitmapHunter 類的主要職責是結合 Action 和 RequestHandler 來獲取 Bitmap 實例轴咱。

BitmapHunter 提供了靜態(tài)方法 forRequest 來創(chuàng)建實例:

static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,
    Action action) {
  // 獲取 Action 中的 Request 對象
  Request request = action.getRequest();
  // 獲取 Picasso 配置的全部 RequestHandler
  List<RequestHandler> requestHandlers = picasso.getRequestHandlers();
  // 從下標 0 開始迭代全部 RequestHandler汛蝙,如果該 RequestHandler 能
  // 處理該 Request烈涮,則用該 RequestHandler 創(chuàng)建 BitmapHunter 實例并返回。
  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);
    }
  }
  // 沒有 RequestHandler 能處理該 Request窖剑,傳入 ERRORING_HANDLER
  return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
}

這里的 requestHandlers 部分用到了責任鏈模式 (Chains of Responsibility)坚洽。

接著看下 BitmapHunter 的構造函數(shù):

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;
Bitmap result;
Future<?> future;
Picasso.LoadedFrom loadedFrom;
Exception exception;
int exifOrientation;
int retryCount;
Priority priority;

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

BitmapHunter 類實現(xiàn)了 Runnable 接口,因此當前面調(diào)用 service.submit(hunter) 西土,BitmapHunter 類中的 run 方法會在線程池中運行:

@Override public void run() {
  try {
    result = hunt();
    if (result == null) {
      dispatcher.dispatchFailed(this);
    } else {
      dispatcher.dispatchComplete(this);
    }
  } catch (Exception e) {
    // 處理一堆異常...
  }
}

run 方法主要通過調(diào)用 hunt 方法獲取返回的 Bitmap 并賦值給 result讶舰,如果 result 不為 null,則調(diào)用 Dispatcher 類的 dispatchComplete 方法需了,否則調(diào)用 dispatchFailed 方法跳昼。

因此獲取 Bitmap 的具體邏輯就在 hunt 方法中完成:

Bitmap hunt() throws IOException {
  Bitmap bitmap = null;
  // 先從緩存中查找 key 對應的 Bitmap,該 key 即之前創(chuàng)建的 requestKey
  if (shouldReadFromMemoryCache(memoryPolicy)) {
    bitmap = cache.get(key);
    if (bitmap != null) {
      stats.dispatchCacheHit();
      loadedFrom = MEMORY;
      return bitmap;
    }
  }
  // 調(diào)用 RequestHandler 的 load 方法獲取 RequestHandler.Result 實例
  // Picasso 將 RequestHandler 加載的結果封裝成一個 Result 對象
  // 我們這里調(diào)用的是 NetworkRequestHandler 類中的 load 方法
  RequestHandler.Result result = requestHandler.load(data, networkPolicy);
  if (result != null) {
    loadedFrom = result.getLoadedFrom();
    exifOrientation = result.getExifOrientation();
    bitmap = result.getBitmap();
    // result 中的 bitmap 為 null肋乍,將 source 中的字節(jié)流編碼成 Bitmap
    if (bitmap == null) {
      Source source = result.getSource();
      try {
        bitmap = decodeStream(source, data);
      } finally {
        try {
          source.close();
        } catch (IOException ignored) {
        }
      }
    }
  }
  return bitmap;
}

hunt 方法結束鹅颊,返回獲取到的 Bitmap 實例,回到 BitmapHunter 的 run 方法墓造,這時候如果返回的 Bitmap 實例不為null堪伍,就調(diào)用 Dispatcher 類中的 dispatchComplete 方法,這樣剩下的工作又交接給了 Dispatcher滔岳。

5.3 Dispatcher 類請求完成的實現(xiàn)

BitmapHunter 獲取到不為 null 的 Bitmap 實例后杠娱,調(diào)用 Dispatcher 類中的 dispatchComplete 方法分發(fā)其完成事件:

void dispatchComplete(BitmapHunter hunter) {
  handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
}

handler 發(fā)送該消息后挽牢,接著又在工作線程中回調(diào)到了 Dispatcher 類的 performComplete 方法:

void performComplete(BitmapHunter hunter) {
  if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
    cache.set(hunter.getKey(), hunter.getResult());
  }
  batch(hunter);
}

這時候 Picasso 將 Bitmap 存入到了緩存中谱煤,然后調(diào)用 batch 方法:

private void batch(BitmapHunter hunter) {
  if (hunter.isCancelled()) {
    return;
  }
  if (hunter.result != null) {
    hunter.result.prepareToDraw();
  }
  batch.add(hunter);
  if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
    handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
  }
}

handler 最后發(fā)送了 HUNTER_DELAY_NEXT_BATCH 消息,發(fā)送該消息后又回調(diào)到了 Dispatcher 類的 performBatchComplete 方法:

void performBatchComplete() {
  batch.clear();  mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
}

這時候發(fā)送消息的是 mainThreadHandler禽拔,切換到主線程來了刘离,發(fā)送的消息為 HUNTER_BATCH_COMPLETE,該消息回調(diào)到了 Picasso 類中的 HANDLER 實例中:

static final Handler HANDLER = new Handler(Looper.getMainLooper()) {
  @Override public void handleMessage(Message msg) {
    switch (msg.what) {
      case HUNTER_BATCH_COMPLETE: {
        List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;
        for (int i = 0, n = batch.size(); i < n; i++) {
          BitmapHunter hunter = batch.get(i);
          hunter.picasso.complete(hunter);
        }
        break;
      }
      default:
        throw new AssertionError("Unknown handler message received: " + msg.what);
    }
  }
};

對每個 BitmapHunter 實例調(diào)用 Picasso 類中的 complete 方法:

void complete(BitmapHunter hunter) {
  Action single = hunter.getAction();
  if (single == null) return;

  Uri uri = hunter.getData().uri;
  Exception exception = hunter.getException();
    
  Bitmap result = hunter.getResult();
  LoadedFrom from = hunter.getLoadedFrom();
  
  deliverAction(result, from, single, exception);

  if (listener != null && exception != null) {
    listener.onImageLoadFailed(this, uri, exception);
  }
}
private void deliverAction(Bitmap result, LoadedFrom from, Action action, Exception e) {
  if (action.isCancelled()) {
    return;
  }
  if (result != null) {
    if (from == null) {
      throw new AssertionError("LoadedFrom cannot be null.");
    }
    action.complete(result, from);
    }
  } else {
    action.error(e);
  }
}

我們之前提交的是 ImageViewAction 實例睹栖,因此這時候會回調(diào)到 ImageViewAction 類的 complete 方法:

@Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
  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);

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

最后 setBitmap:

static void setBitmap(ImageView target, Context context, Bitmap bitmap,
    Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {
  Drawable placeholder = target.getDrawable();
  if (placeholder instanceof Animatable) {
    ((Animatable) placeholder).stop();
  }
  PicassoDrawable drawable =
      new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);
  target.setImageDrawable(drawable);
}

結束該方法后硫惕,這時候圖片終于成功在界面顯示了,整個調(diào)用流程到此也終于結束野来。

6. 參考

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末恼除,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子曼氛,更是在濱河造成了極大的恐慌豁辉,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舀患,死亡現(xiàn)場離奇詭異徽级,居然都是意外死亡,警方通過查閱死者的電腦和手機聊浅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門餐抢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來现使,“玉大人,你說我怎么就攤上這事旷痕√夹猓” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵欺抗,是天一觀的道長殴胧。 經(jīng)常有香客問我,道長佩迟,這世上最難降的妖魔是什么团滥? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮报强,結果婚禮上灸姊,老公的妹妹穿的比我還像新娘。我一直安慰自己秉溉,他們只是感情好力惯,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著召嘶,像睡著了一般父晶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上弄跌,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天甲喝,我揣著相機與錄音,去河邊找鬼铛只。 笑死埠胖,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的淳玩。 我是一名探鬼主播直撤,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蜕着!你這毒婦竟也來了谋竖?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤承匣,失蹤者是張志新(化名)和其女友劉穎蓖乘,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悄雅,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡驱敲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了宽闲。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片众眨。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡握牧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出娩梨,到底是詐尸還是另有隱情沿腰,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布狈定,位于F島的核電站颂龙,受9級特大地震影響,放射性物質發(fā)生泄漏纽什。R本人自食惡果不足惜措嵌,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望芦缰。 院中可真熱鬧企巢,春花似錦、人聲如沸让蕾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽探孝。三九已至笋婿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間顿颅,已是汗流浹背缸濒。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留元镀,地道東北人绍填。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像栖疑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子滔驶,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 概述 在Android開發(fā)界遇革,大家都知到square出品必屬精品,圖片加載庫Picasso自然也是揭糕。本文就從源碼角...
    朔野閱讀 672評論 0 7
  • 前一篇文章講了Picasso的詳細用法萝快,Picasso 是一個強大的圖片加載緩存框架,一個非常優(yōu)秀的開源庫著角,學習一...
    依然范特稀西閱讀 4,620評論 13 24
  • 一. 概述 Picasso是Square出品的一個非常精簡的圖片加載及緩存庫揪漩,其主要特點包括: 易寫易讀的流式編程...
    SparkInLee閱讀 1,095評論 2 11
  • 她知道奄容,只要說“我需要你”冰更,好朋友就永遠會在。她和她昂勒,超過三十年蜀细。 我和Z小姐沒有那么煽情,沒有那份篤...
    非衣青粥閱讀 528評論 0 2
  • 今天開始女兒要在家戈盈。 早上吃完飯后和女兒商量每天怎樣安排時間奠衔?我們把剩下的作業(yè)整理了一下,因為上輔導班課上又不...
    金慧恩媽媽閱讀 120評論 0 4