Picasso-源碼解析(一)

前言

使用的是picasso最新版本 github地址:https://github.com/square/picasso 版本:2.71828java

簡單例子

image.png

代碼
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Picasso.get().setIndicatorsEnabled(true)
        Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(ivTest)
        Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(ivTest2)
    }
複製代碼

代碼很簡單,令你們比較驚訝的應該是左上角的藍三角,其實原圖是沒有的,因爲我加入了Picasso.get().setIndicatorsEnabled(true),打開了指示標誌。git

這裏先直接說明下表明的意思,後面咱們再慢慢深刻。 Picasso.javagithub

public enum LoadedFrom {
    //內存加載,綠色
    MEMORY(Color.GREEN),
    //磁盤加載,藍色
    DISK(Color.BLUE),
    //網絡加載,紅色
    NETWORK(Color.RED);

    final int debugColor;

    LoadedFrom(int debugColor) {
      this.debugColor = debugColor;
    }
  }
複製代碼

通常來講,絕大多數的圖片框架都是三級緩存,Picasso也不例外。 Glide,Fresco我還未深刻了解,可是Picasso這個標識仍是頗有用的。很容易讓咱們可以明白是哪一種加載方式。canvas

先簡單的說明下這是如何去實現的。緩存

PicassoDrawable.java網絡

@Override public void draw(Canvas canvas) {
    if (!animating) {
      super.draw(canvas);
    } else {
      float normalized = (SystemClock.uptimeMillis() - startTimeMillis) / FADE_DURATION;
      if (normalized >= 1f) {
        animating = false;
        placeholder = null;
        super.draw(canvas);
      } else {
        if (placeholder != null) {
          placeholder.draw(canvas);
        }

        // setAlpha will call invalidateSelf and drive the animation.
        int partialAlpha = (int) (alpha * normalized);
        super.setAlpha(partialAlpha);
        super.draw(canvas);
        super.setAlpha(alpha);
      }
    }

  //前面都是繪製原圖的
    if (debugging) {
      //這裏判斷下,繪製下標識
      drawDebugIndicator(canvas);
    }
  }

 private void drawDebugIndicator(Canvas canvas) {
    DEBUG_PAINT.setColor(WHITE);
    Path path = getTrianglePath(0, 0, (int) (16 * density));
    canvas.drawPath(path, DEBUG_PAINT);
    //根據加載方式
    DEBUG_PAINT.setColor(loadedFrom.debugColor);
    path = getTrianglePath(0, 0, (int) (15 * density));
    canvas.drawPath(path, DEBUG_PAINT);
  }
複製代碼

源碼解析

前面只是簡單的介紹了一下Picasso的一個小功能,下面仍是經過上面那個簡單的加載圖片代碼,一步步跟入源碼,來介紹下是如何實現圖片加載的,如何作到三級緩存的。app

Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(ivTest)
複製代碼
  1. get Picasso.java
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;
  }
public Picasso build() {
      Context context = this.context;

      if (downloader == null) {
        downloader = new OkHttp3Downloader(context);
      }
      if (cache == null) {
        cache = new LruCache(context);
      }
      if (service == null) {
        service = new PicassoExecutorService();
      }
      if (transformer == null) {
        transformer = RequestTransformer.IDENTITY;
      }

      Stats stats = new Stats(cache);

      Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);

      return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
          defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
    }
複製代碼

很是簡單的一個單例模式,和建造者模式。單例模式就不過多說了,這裏主要介紹下建造者模式,通常來講對於參數比較多的構造方法,使用建造者模式,就能夠直接使用鏈式的方式,來配置對象。框架

這裏直接使用Picasso.get實際上是獲取了默認的一個Picasso對象,而後幫你默認的配置了LruCache,PicassoExecutorService,RequestTransformer,OkHttp3Downloader,Stats,Dispatcheride

很顯然,通常來講,確定是會提供一個自定義的方式,否則就太low了。佈局

public static void setSingletonInstance(@NonNull Picasso picasso) {
    if (picasso == null) {
      throw new IllegalArgumentException("Picasso must not be null.");
    }
    synchronized (Picasso.class) {
      if (singleton != null) {
        throw new IllegalStateException("Singleton instance already exists.");
      }
      singleton = picasso;
    }
  }
複製代碼

你可使用Picasso.Builder先本身構建一個Picasso對象,而後再調用這個方法,接下來就可使用Picasso.get()來獲取本身的配置的單例了。

  1. load load方法有不少重載,這裏仍是以String爲例子。
public RequestCreator load(@Nullable String path) {
    if (path == null) {
      return new RequestCreator(this, null, 0);
    }
    if (path.trim().length() == 0) {
      throw new IllegalArgumentException("Path must not be empty.");
    }
    return load(Uri.parse(path));
  }
public RequestCreator load(@Nullable Uri uri) {
    return new RequestCreator(this, uri, 0);
  }
複製代碼

很顯然,load方法只是爲了獲取一個RequestCreator對象。

RequestCreator(Picasso picasso, Uri uri, int resourceId) {
    if (picasso.shutdown) {
      throw new IllegalStateException(
          "Picasso instance already shut down. Cannot submit new requests.");
    }
    this.picasso = picasso;
    this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
  }
複製代碼

RequestCreator裏面最重要的,其實就是data也就是一個Request.Builder,從這裏其實咱們很明顯的能夠看出,RequestCreator,顧名思義,就是爲了建立一個Request,最終的Request確定是由data.build生成的。可是目前只是new了一個Request.Builder對象,並無調用。這是由於後面咱們還須要往Request.Builder塞入不少不一樣的參數。

image.png

由上圖其實咱們能夠發現,咱們經常使用的一些鏈式方法,如centerCrop等,其實就是調用了Request.Builder對象的方法,只是爲了構建一個Request.

  1. into 這裏纔是真正發起請求的地方。
public void into(ImageView target) {
    into(target, null);
  }
public void into(ImageView target, Callback callback) {
    long started = System.nanoTime();
    //判斷下是否爲主線程
    checkMain();

    if (target == null) {
      throw new IllegalArgumentException("Target must not be null.");
    }
    //若是uri爲空或者resId爲0,則直接取消請求,設置爲placeholder圖片
    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) {
        if (setPlaceholder) {
          setPlaceholder(target, getPlaceholderDrawable());
        }
        picasso.defer(target, new DeferredRequestCreator(this, target, callback));
        return;
      }
      data.resize(width, height);
    }

    //簡單理解,就是調用了data.build(),生成一個Request
    Request request = createRequest(started);
    //這裏經過request生成一個String,用來後面key-value保存圖片在LruCache中
    String requestKey = createKey(request);
    
    if (shouldReadFromMemoryCache(memoryPolicy)) {
      //若是前面請求過了,會緩存到內存,這邊再請求,仍是會生成了相同的key,直接從cache中獲取到了Bitmap
      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;
      }
    }
    //沒有從內存中獲取到緩存,先設置placeholder圖片
    if (setPlaceholder) {
      setPlaceholder(target, getPlaceholderDrawable());
    }
    //建立一個action
    Action action =
        new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
            errorDrawable, requestKey, tag, callback, noFade);
     //提交一個action
    picasso.enqueueAndSubmit(action);
  }
複製代碼

這裏其實很是簡單的分析了下。

這裏面有2步單獨拿出來講。

  1. deferred的做用
public RequestCreator fit() {
    deferred = true;
    return this;
  }

  /** Internal use only. Used by {@link DeferredRequestCreator}. */
  RequestCreator unfit() {
    deferred = false;
    return this;
  }

....
if (deferred) {
      if (data.hasSize()) {
        throw new IllegalStateException("Fit cannot be used with resize.");
      }
      int width = target.getWidth();
      int height = target.getHeight();
      //若是說imageview自己已經能夠獲取到寬高了,都不是0,那麼就直接resize一下圖片,若是說有一個是0,說明這個Imageview可能尚未佈局完成,尚未本身的寬高,那麼就在原來的`RequestCreator`外面再包了一層`DeferredRequestCreator `
      if (width == 0 || height == 0) {
        if (setPlaceholder) {
          setPlaceholder(target, getPlaceholderDrawable());
        }
        picasso.defer(target, new DeferredRequestCreator(this, target, callback));
        return;
      }
      data.resize(width, height);
    }
複製代碼

用過了fit方法的人應該知道,調用後能夠適配ImageView的尺寸,這裏就是實現方式

下面咱們來看看DeferredRequestCreator是如何實現的

DeferredRequestCreator(RequestCreator creator, ImageView target, Callback callback) {
    this.creator = creator;
    this.target = new WeakReference<>(target);
    this.callback = callback;
    //實現很簡單,就是給ImageView設置下監聽
    target.addOnAttachStateChangeListener(this);
    if (target.getWindowToken() != null) {
      onViewAttachedToWindow(target);
    }
  }

  @Override public void onViewAttachedToWindow(View view) {
    view.getViewTreeObserver().addOnPreDrawListener(this);
  }


//這裏纔是最關鍵的部分
 @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();
    //獲取到了ImageView的寬高後,調用resize從新設置了下寬高。
    this.creator.unfit().resize(width, height).into(target, callback);
    return true;
  }
複製代碼
  1. 真正去加載圖片的地方
public void into(ImageView target, Callback callback) {
    ...
    Action action =
        new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
            errorDrawable, requestKey, tag, callback, noFade);

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

void submit(Action action) {
    dispatcher.dispatchSubmit(action);
  }

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

 @Override public void handleMessage(final Message msg) {
      switch (msg.what) {
        case REQUEST_SUBMIT: {
          Action action = (Action) msg.obj;
          dispatcher.performSubmit(action);
          break;
        }
    ...

 void performSubmit(Action action) {
    performSubmit(action, true);
  }


複製代碼

前面其實講到了這裏,咱們再日後繼續。從enqueueAndSubmit一步步往下,雖然調用了不少方法,可是最終,其實就是調用Dispatcher中的performSubmit方法。下面咱們來具體分析下這個方法。

void performSubmit(Action action, boolean dismissFailed) {
   ...
   
    BitmapHunter hunter = hunterMap.get(action.getKey());
    if (hunter != null) {
      hunter.attach(action);
      return;
    }

    ...
   //一開始hunterMap確定不包含action的key,因此會建立一個BitmapHunter
    hunter = forRequest(action.getPicasso(), this, cache, stats, action);
   //其實咱們會發現BitmapHunter是一個Runnable,service是ExecutorService,能夠理解爲一個線程池,這裏就直接執行一個Runnable
    hunter.future = service.submit(hunter);
    hunterMap.put(action.getKey(), hunter);
  ...
  }


//經過傳入的參數生成一個BitmapHunter
static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action) {
    Request request = action.getRequest();
    List<RequestHandler> requestHandlers = picasso.getRequestHandlers();

    for (int i = 0, count = requestHandlers.size(); i < count; i++) {
      RequestHandler requestHandler = requestHandlers.get(i);
        //最重要的地方在這裏,遍歷全部的requestHandler,看哪一個requestHandler可以處理request,後面再詳細介紹
      if (requestHandler.canHandleRequest(request)) {
        return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
      }
    }

    return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
  }
複製代碼

爲了讓後面咱們能夠更好的理解,咱們先回過頭來,看一下requestHandlers是什麼東西,爲何要先找出可以處理當前RequestRequestHandler.

一直往前找發現是在Picasso的構造方法裏面初始化的

Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
      RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
      Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
  ...
    List<RequestHandler> allRequestHandlers = new ArrayList<>(builtInHandlers + extraCount);
    //resource圖片處理,好比R.drawable這種
    allRequestHandlers.add(new ResourceRequestHandler(context));
    if (extraRequestHandlers != null) {
        //自定義處理
      allRequestHandlers.addAll(extraRequestHandlers);
    }
  //聯繫人圖片處理
    allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
    //媒體資源處理
    allRequestHandlers.add(new MediaStoreRequestHandler(context));
  //流資源處理
    allRequestHandlers.add(new ContentStreamRequestHandler(context));
  //asset資源處理
    allRequestHandlers.add(new AssetRequestHandler(context));
  //文件資源處理
    allRequestHandlers.add(new FileRequestHandler(context));
  //網絡資源處理
    allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
  ...
  }
複製代碼

都是經過load方法以後的參數來判斷的。咱們這裏以NetworkRequestHandler爲例

@Override public boolean canHandleRequest(Request data) {
    String scheme = data.uri.getScheme();
    return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
  }
複製代碼

若是說uri是http或者https就能夠由NetworkRequestHandler來處理。

那麼咱們繼續回到剛纔那個地方

void performSubmit(Action action, boolean dismissFailed) {
...
    hunter = forRequest(action.getPicasso(), this, cache, stats, action);
   //其實咱們會發現BitmapHunter是一個Runnable,service是ExecutorService,能夠理解爲一個線程池,這裏就直接執行一個Runnable
    hunter.future = service.submit(hunter);
    hunterMap.put(action.getKey(), hunter);
  ...
  }
複製代碼

獲取到能夠處理RequestHandler以後建立了一個BitmapHunter,而後調用service.submit最終實際上是調用Runnablerun方法,咱們繼續跟入。

@Override public void run() {
    try {
      ...
      result = hunt();
     ...
    }
  }

Bitmap hunt() throws IOException {
    Bitmap bitmap = null;
    //在真正發起請求以前,再次判斷下,是否從內存中獲取圖片
    if (shouldReadFromMemoryCache(memoryPolicy)) {
      bitmap = cache.get(key);
      if (bitmap != null) {
        stats.dispatchCacheHit();
        loadedFrom = MEMORY;
        if (picasso.loggingEnabled) {
          log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache");
        }
        return bitmap;
      }
    }
  
    networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;

    //前面其實咱們已經分析過,requestHandler實際上是NetworkRequestHandler,等下單獨提出load方法來說
    RequestHandler.Result result = requestHandler.load(data, networkPolicy);
    if (result != null) {
      loadedFrom = result.getLoadedFrom();
      //獲取下exif格式信息,通常狀況用不到,這裏不深刻
      exifOrientation = result.getExifOrientation();
      //獲取到真正的bitmap
      bitmap = result.getBitmap();
      if (bitmap == null) {
        Source source = result.getSource();
        try {
          bitmap = decodeStream(source, data);
        } finally {
          try {
            source.close();
          } catch (IOException ignored) {
          }
        }
      }
    }

    //下面一大串實際上是對原來的圖片進行一些變換,這裏先不深刻
    if (bitmap != null) {
     ...
      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()) {
            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;

複製代碼

下面咱們仍是具體再看看NetworkRequestHandler

@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);
    }
    //從這裏能夠看出磁盤緩存
    Picasso.LoadedFrom loadedFrom = response.cacheResponse() == null ? NETWORK : DISK;
    if (loadedFrom == DISK && body.contentLength() == 0) {
      body.close();
      throw new ContentLengthException("Received response with 0 content-length header.");
    }
    if (loadedFrom == NETWORK && body.contentLength() > 0) {
      stats.dispatchDownloadFinished(body.contentLength());
    }
    return new Result(body.source(), loadedFrom);
  }
複製代碼

其實這裏最關鍵的部分就是response.cacheResponse()這一句代碼。由於以前我一直覺得Picasso使用的是DiskLruCache來進行磁盤緩存。可是一直找不到實現的地方。一直找到這裏才恍然大悟,Picasso的磁盤緩存是利用http協議中的cache-control去實現的。 而後使用的實際上是Okhttp3實現了http協議,其中磁盤緩存確實也是用DiskLruCache來實現的。

總結

後面還會繼續深刻。

相關文章
相關標籤/搜索