Picasso 源碼分析(1)

前言

大家好,我是小黑(linheimx),一位快樂的2b程序員。

一段簡潔的代碼

    Picasso
        .with(context) 
        .load(url) 
        .placeholder(R.drawable.placeholder) 
        .error(R.drawable.error) 
        .fit() 
        .tag(context) 
        .into(view);

這段代碼將 異步的 加載url指定的資源到 imageview 上。

接下來,我們將以這段代碼作為 切入點 ,來分析Picasso


1. with()

public static Picasso with(@NonNull Context context) {
    // 1.check
    if (context == null) {
      throw new IllegalArgumentException("context == null");
    }
    // 2. sync code
    if (singleton == null) {
      synchronized (Picasso.class) {
        if (singleton == null) {
          // 構造
          singleton = new Builder(context).build();
        }
      }
    }
    return singleton;
  } 

這段代碼是為了獲取 Picasso 實例。
有以下關鍵點:

  1. synchronized

當兩個并發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程得到執行。另一個線程必須等待當前線程執行完這個代碼塊以后才能執行該代碼塊。

  1. builder

builder構造模式,建造Picasso所需要的信息。

細看Builder

builder 可以構建的信息有:

public static class Builder {
    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;

    private boolean indicatorsEnabled;
    private boolean loggingEnabled;

build() 方法

 /** Create the {@link Picasso} instance. */
    public Picasso build() {
      Context context = this.context;

      if (downloader == null) {
        downloader = Utils.createDefaultDownloader(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);
    }

注意:

  1. Downloader downloader.

load:從外部存儲,緩存或是網絡來加載圖片資源。

  1. Cache cache

內存緩存:存儲最近使用最多的圖片。

  1. ExecutorService service

異步線程并發執行器

以上3個小東西,構造了 Dispatcher。說明這個 Dispatcher 比較重要。(暫不分析)

2. load()

load 指定加載資源路徑

1. load(@Nullable String path) // 可以是 url,file資源(file:),content資源(content:),android資源(resource:)
2. load(@NonNull File file) // image file
3. load(@DrawableRes int resourceId) // drawable resource ID

最終執行如下方法:

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

我們可以看到 load的最終目的是,構造一個 RequestCreator
RequestCreator 從名字可以看出 它 可以構建Request,它的一切字段,方法都是為Request來服務的。

我們大致看一下 RequestCreator:

  1. 字段:
    // 靜態原子 int
    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 Drawable placeholderDrawable;
    private Drawable errorDrawable;
    private int memoryPolicy;
    private int networkPolicy;

    private Object tag;
  1. 方法

字段是私有的,很多方法是為字段服務的。在方法里面可做相關的非法檢查等。

noPlaceholder()
placeholder(@DrawableRes int placeholderResId)
placeholder(@NonNull Drawable placeholderDrawable)

error(@DrawableRes int errorResId) 
error(@NonNull Drawable errorDrawable)

tag(@NonNull Object tag) 
fit() // Attempt to resize the image to fit exactly into the target {@link ImageView}'s bounds. This will result in delayed execution of the request until the {@link ImageView} has been laid out.
unfit() 

resizeDimen(int targetWidthResId, int targetHeightResId)
RequestCreator resize(int targetWidth, int targetHeight) // Resize the image to the specified size in pixels.
centerCrop()
centerInside()

等等。。。。。

注意:這里很多方法 都有return this--->便于鏈式的方法調用(寫出來的代碼方便直觀!)
注意:這些方法很多都是為request服務的(Request 需要構建,Request.Builder data提供構造信息)

3. into()

RequestCreator 中很重要的方法!!!
into有幾個重載:

  1. into(@NonNull Target target)
  2. into( @NonNull RemoteViews remoteViews, @IdRes int viewId, int notificationId, @NonNull Notification notification)
  3. into(ImageView target)
  4. into(ImageView target, Callback callback)

下面主要分析: into(ImageView target, Callback callback)

public void into(ImageView target, Callback callback) {
        long started = System.nanoTime();
        checkMain();

        if (target == null) {
            throw new IllegalArgumentException("Target must not be null.");
        }

        if (!data.hasImage()) {
            picasso.cancelRequest(target);
            if (setPlaceholder) {
                setPlaceholder(target, getPlaceholderDrawable());
            }
            return;
        }

        if (deferred) {
            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 || target.isLayoutRequested()) {
                if (setPlaceholder) {
                    setPlaceholder(target, getPlaceholderDrawable());
                }
                picasso.defer(target, new DeferredRequestCreator(this, target, callback));
                return;
            }
            data.resize(width, height);
        }

        Request request = createRequest(started);
        String requestKey = createKey(request);

        if (shouldReadFromMemoryCache(memoryPolicy)) {
            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;
            }
        }

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

        Action action =
                new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
                        errorDrawable, requestKey, tag, callback, noFade);

        picasso.enqueueAndSubmit(action);
    }

簡單的畫了下流程圖,如下:

Sample Flowchart Template.png

簡單直接的說幾個關鍵點:

a. 從內存緩存中讀取數據

if (shouldReadFromMemoryCache(memoryPolicy)) {
            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;
            }
 }
  1. shouldReadFromMemoryCache() 此處方法
    檢查 圖片的內存策略

被定義在 enum MemoryPolicy中,枚舉類 MemoryPolicy負責定義,負責do some job,比較有意思。

  1. Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);

Picasso 是個大管家。持有cache,方便對內存中的bitmap進行管理。

  1. picasso.cancelRequest(target);

Picasso 是個大管家。還可以cancel請求呢。

  1. setbitmap,calback
    有了bitmap,就可以顯示啦。

setbitmap()這個方法很有意思,后面會聊它的(// todo analysis)

b. 再深入一點(亞麻跌):

這個Picasso對 tagert的請求管理。
picasso.cancelRequest(target): 取消與target對象相關的所有請求

 public void cancelRequest(@NonNull ImageView view) {
    // checkMain() is called from cancelExistingRequest()
    if (view == null) {
      throw new IllegalArgumentException("view cannot be null.");
    }
    cancelExistingRequest(view);// go
  }

private void cancelExistingRequest(Object target) {
    checkMain();
    Action action = targetToAction.remove(target);// 注意:targetToAction
    if (action != null) {
      /////////////////////// true cancel //////////////////////
      action.cancel();
      dispatcher.dispatchCancel(action);
       /////////////////////// true cancel //////////////////////
    }

    
    if (target instanceof ImageView) {
      ImageView targetImageView = (ImageView) target;

      ////////////////////////////  defer cancel   ///////////////////////////
      DeferredRequestCreator deferredRequestCreator =
          targetToDeferredRequestCreator.remove(targetImageView);
      if (deferredRequestCreator != null) {
        deferredRequestCreator.cancel();
      }
      ////////////////////////////  defer cancel   ///////////////////////////
    }
  }

我們著重看一下:targetToAction

final Map<Object, Action> targetToAction;

他是一個map,so有了map這個容器,我們來管理 target 這個key 對應的所有 action 豈不是非常容易!
當然相關的 任務 的執行是由 final Dispatcher dispatcher; 來管理的。這個下面細說。
再次判斷,該target若是 imgeview,取消相關的其延遲請求,這個延遲(defer),后面說。

c. defer

defer: 延遲,延期。
為什么會有這個概念?
看下 RequestCreator中的這個方法

/** 
Attempt to resize the image to fit exactly into the target ImageView's bounds. 
This will result in delayed execution of the request until the ImageView has been laid out.
Note: This method works only when your target is an ImageView.
*/
        
        
public RequestCreator fit() {
        deferred = true;

        //////////////////////////////  return this  ///////////////////////////
        return this;
 }

你下載下來的 bitmap的大小和 imageview的大小不匹配。為了讓bitmap適應 imageview的大小。
等你imageview被布局完畢后(imagview的大小固定了),這個延遲的請求才會被執行。這樣他們就fit了。

Picasso 這個大管家。接手了 imagview 這個target的 延遲請求:

picasso.defer(target, new DeferredRequestCreator(this, target, callback));
---------------
 void defer(ImageView view, DeferredRequestCreator request) {
    // If there is already a deferred request, cancel it.
    if (targetToDeferredRequestCreator.containsKey(view)) {
      cancelExistingRequest(view);
    }
    targetToDeferredRequestCreator.put(view, request);
  }
---------------------------------
final Map<ImageView, DeferredRequestCreator> targetToDeferredRequestCreator;// 是個map哦。

d. 創建 Request 和 request key

  1. 創建request
  /**
     * Create the request optionally passing it through the request transformer.
     */
    private Request createRequest(long started) {
        int id = nextId.getAndIncrement();

        Request request = data.build();
        request.id = id;// 一個request,一個id
        request.started = started;

        boolean loggingEnabled = picasso.loggingEnabled;
        if (loggingEnabled) {
            log(OWNER_MAIN, VERB_CREATED, request.plainId(), request.toString());
        }

        Request transformed = picasso.transformRequest(request);
        if (transformed != request) {
            // If the request was changed, copy over the id and timestamp from the original.
            transformed.id = id;
            transformed.started = started;

            if (loggingEnabled) {
                log(OWNER_MAIN, VERB_CHANGED, transformed.logId(), "into " + transformed);
            }
        }

        return transformed;
    }

builder 創建了request(request需要好多信息啊!)

  /** Create the immutable {@link Request} object. */
    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, onlyScaleDown, rotationDegrees, rotationPivotX, rotationPivotY,
          hasRotationPivot, purgeable, config, priority);
    }
  }

Request 有著唯一的 key(String類型),key的生成規則:

 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) {

    //-------------------------------------> 1
    if (data.stableKey != null) {// 1. stable key
      builder.ensureCapacity(data.stableKey.length() + KEY_PADDING);
      builder.append(data.stableKey);
    } else if (data.uri != null) {// 2. uri
      String path = data.uri.toString();
      builder.ensureCapacity(path.length() + KEY_PADDING);
      builder.append(path);
    } else {// 3. resource id
      builder.ensureCapacity(KEY_PADDING);
      builder.append(data.resourceId);
    }

    builder.append(KEY_SEPARATOR);

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

    //---------------------------------------> 3
    if (data.hasSize()) {
      builder.append("resize:").append(data.targetWidth).append('x').append(data.targetHeight);
      builder.append(KEY_SEPARATOR);
    }

    //---------------------------------------> 4
    if (data.centerCrop) {
      builder.append("centerCrop").append(KEY_SEPARATOR);
    } else if (data.centerInside) {
      builder.append("centerInside").append(KEY_SEPARATOR);
    }

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

e. Action

一個request被包裝成了 action 被執行。

Action action =
                new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
                        errorDrawable, requestKey, tag, callback, noFade);

picasso.enqueueAndSubmit(action);

現在問題來了:

  1. action 是什么?
  2. action 是什么?
  3. action 是什么?

看一下Action這個類:

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

  boolean willReplay;
  boolean cancelled;

包裹里這么多信息,他到底想干嘛?
再看下他定義的方法:

///////////////////////////////////////////////////////////////
  abstract void complete(Bitmap result, Picasso.LoadedFrom from);
  abstract void error();
  void cancel() {
    cancelled = true;
  }
///////////////////////////////////////////////////////////////

  Request getRequest() {
    return request;
  }

  T getTarget() {
    return target == null ? null : target.get();
  }

  String getKey() {
    return key;
  }

  boolean isCancelled() {
    return cancelled;
  }

  boolean willReplay() {
    return willReplay;
  }

  int getMemoryPolicy() {
    return memoryPolicy;
  }

  int getNetworkPolicy() {
    return networkPolicy;
  }

  Picasso getPicasso() {
    return picasso;
  }

  Priority getPriority() {
    return request.priority;
  }

從以上方法猜測,action關注:

他包裹的請求被 處理完成后(complete),或是失敗后的動作處理(error),或是 cancel的處理

再看action的子類:

Paste_Image.png

我們關注 ImageViewAction,看下他怎么處理 complete,error,cancel:

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

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

----------------
 @Override public void error() {
    ImageView target = this.target.get();
    if (target == null) {
      return;
    }
    Drawable placeholder = target.getDrawable();
    if (placeholder instanceof AnimationDrawable) {
      ((AnimationDrawable) placeholder).stop();
    }
    if (errorResId != 0) {
      target.setImageResource(errorResId);
    } else if (errorDrawable != null) {
      target.setImageDrawable(errorDrawable);
    }

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

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

接下來我們來關注,這個action由誰來操作的。

f. 管理action

你猜是誰?

Picasso 這個大管家

picasso.enqueueAndSubmit(action);
void enqueueAndSubmit(Action action) {
    Object target = action.getTarget();
    if (target != null && targetToAction.get(target) != action) {
      // This will also check we are on the main thread.
      cancelExistingRequest(target);
      targetToAction.put(target, action); // action與 target相關聯
    }
    submit(action);
  }
void submit(Action action) {  dispatcher.dispatchSubmit(action);}

dispatcher 來處理這個action。

dispatcher 由Picasso持有,它的構建在Picasso 的builer下構建(在build下我也提到過~)

那么問題來了:
Dispatcher是什么?

留待下文分解。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容