Picasso源碼探究


Picasso是Square公司出品的一款非常優(yōu)秀的圖片加載庫(kù),它可以幫我們完成一些android中處理的圖片劈猿,使用最小的內(nèi)存來(lái)完成圖片的過(guò)渡潮孽。
使用的方法如下:

Picasso.with(context).load(“image url”).into(imageView);

源碼剖析

我們就根據(jù)圖片顯示的這一條流程下來(lái),一步步探究仗颈。
先看上面一行代碼

public static Picasso with(@NonNull Context context) {
    if (context == null) {
      throw new IllegalArgumentException("context == null");
    }
    if (singleton == null) {
      synchronized (Picasso.class) {
        if (singleton == null) {
          singleton = new Builder(context).build();
        }
      }
    }
    return singleton;
  }

在這里使用用了懶漢式創(chuàng)建了個(gè)實(shí)例椎例,接著用new Builder(Context).build 初始化了Picasso,主要作用為提供自定義線程池请祖、緩存脖祈、下載器等方法盖高。
有了這個(gè)實(shí)例后,會(huì)接下來(lái)調(diào)用load函數(shù) 其中Picasso重載了幾個(gè)不同的方法或舞,來(lái)適應(yīng)不同場(chǎng)合下加載圖片蒙幻。

 public RequestCreator load(@Nullable Uri uri) {
   .....
  }
 public RequestCreator load(@Nullable String path) {
    .....
  }
 public RequestCreator load(@NonNull File file) {
    .....
  }

可以看到通過(guò)load會(huì)返回一個(gè)RequestCreator對(duì)象,這個(gè)類(lèi)是用來(lái)配置加載參數(shù)的邮破,包括了placeHolder于error圖片,加載圖片的大小/旋轉(zhuǎn)/居中等屬性矫渔。
最后會(huì)調(diào)用into函數(shù)摧莽,將加載到的圖片賦給一個(gè)ImageView控件。大部分實(shí)際工作都是在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.");
    }

    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);
  }
  1. 會(huì)檢查是否在主線程上運(yùn)行
  2. 如果沒(méi)有一個(gè)圖片資源的話并且有設(shè)置placeHolder,那么就會(huì)把我們?cè)O(shè)置的placeholder顯示出來(lái)征懈,并中斷執(zhí)行
  3. 接下來(lái)就是創(chuàng)建了一個(gè)Request對(duì)象,我們?cè)谇懊孀龅靡恍┰O(shè)置都會(huì)被封裝到這個(gè)Request對(duì)象里面鬼悠。
  4. 之后由stableKey亏娜、urirotationDegrees袜啃、resize幸缕、centerCrop晰韵、``centerInside熟妓、transformations`組成key
  5. 檢查我們要顯示的圖片是否可以直接在緩存中獲取起愈,如果有就直接顯示出來(lái)好了
  6. 如果都沒(méi)有最后通過(guò)一個(gè)Action,采用異步下載顯示圖片

為了保證圖片不會(huì)錯(cuò)位,Picasso維護(hù)了Map<ImageView,Action>抬虽,每個(gè)ImageView均只對(duì)應(yīng)一個(gè)Action阐污。

若獲取的圖片Action與ImageView不符合,則丟棄笛辟,等待正確的Action執(zhí)行完手幢。

了解完P(guān)icasso加載的圖片的過(guò)程后,我們要深入到Picasso里面围来,查看圖片加載的具體實(shí)現(xiàn)方式:
之前了解到初始化Picasso监透,調(diào)用了new Builder(context).build()方法

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);
    }
  }
  1. 重點(diǎn)看Dispatcher,在這里起到了一個(gè)調(diào)度器的作用,圖片要不要開(kāi)始下載及下載后Bitmap的返回都是通過(guò)這個(gè)調(diào)度器來(lái)執(zhí)行的牛曹。
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);
  }

通過(guò)上面的分析我們知道醇滥,RequestCreator在into方法的最后會(huì)創(chuàng)建一個(gè)Action實(shí)例黎比,然后調(diào)用Picasso的enqueueAndSubmit方法鸳玩,而最終是調(diào)用了Dispatcher的dispatchSubmit方法不跟,也就是我們前面說(shuō)的,Dispatcher起到了調(diào)度器的作用。在Dispatcher內(nèi)部吕座,Dispatcher定義了DispatcherThread和DispatcherHandler兩個(gè)內(nèi)部類(lèi)瘪板,并在Dispatcher的構(gòu)造函數(shù)中對(duì)他們經(jīng)行了實(shí)例化,所有的調(diào)度也都是通過(guò)handler異步的執(zhí)行的锣枝。

Picasso維護(hù)了Map<ImageView,Action>兰英,每個(gè)ImageView均只對(duì)應(yīng)一個(gè)Action。

看一下Dispatcher內(nèi)部實(shí)現(xiàn)

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

通過(guò)Handler最終調(diào)用了一個(gè)performSubmit()函數(shù)

void performSubmit(Action action, boolean dismissFailed) {
    if (pausedTags.contains(action.getTag())) {
      pausedActions.put(action.getTarget(), action);
      if (action.getPicasso().loggingEnabled) {
        log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),
            "because tag '" + action.getTag() + "' is paused");
      }
      return;
    }

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

    if (service.isShutdown()) {
      if (action.getPicasso().loggingEnabled) {
        log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down");
      }
      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());
    }

    if (action.getPicasso().loggingEnabled) {
      log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
    }
  }

在這里獲得了一個(gè)BitmapHunter ,這其實(shí)是個(gè)Runnable的一個(gè)實(shí)現(xiàn)家制,實(shí)例最終交給另外線程池來(lái)處理泡一。
那最后圖片是怎么呈現(xiàn)出來(lái)的呢鼻忠?進(jìn)到BitmapHunter ,看run()實(shí)現(xiàn)的方法帖蔓。

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

可以看到執(zhí)行了dispatcher.perComplete方法塑娇,這個(gè)方法會(huì)自動(dòng)處理緩存圖片問(wèn)題.

void performComplete(BitmapHunter hunter) {
    if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
      cache.set(hunter.getKey(), hunter.getResult());
    }
    hunterMap.remove(hunter.getKey());
    batch(hunter);
    if (hunter.getPicasso().loggingEnabled) {
      log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), "for completion");
    }
  }

把圖片暫時(shí)放到了cache里,等空閑的時(shí)候再去處理來(lái)

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

將操作放到了一個(gè)list數(shù)組(batch)里哨啃,做了一個(gè)延遲操作處理写妥,發(fā)送了一個(gè)HUNTER_DELAY_NEXT_BATCH,又回到了handler處理.由于一直在非主線程上操作珍特,最后顯示圖片還是要回到主線程上來(lái)。這是接收到消息后的處理

void performBatchComplete() {
    List<BitmapHunter> copy = new ArrayList<>(batch);
    batch.clear();
    mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
    logBatch(copy);
  }

這個(gè)mainThreadHandler是在Dispatcher實(shí)例化時(shí)由外部傳遞進(jìn)來(lái)的,我們?cè)谇懊娴姆治鲋锌吹匠昴罚琍icasso在通過(guò)Builder創(chuàng)建時(shí)會(huì)對(duì)Dispatcher進(jìn)行實(shí)例化宋距,在那個(gè)地方將主線程的handler傳了進(jìn)來(lái),我們回到Picasso這個(gè)類(lèi)淫僻,看到其有一個(gè)靜態(tài)成員變量HANDLER壶唤,這樣我們也就清楚了。

  static final Handler HANDLER = new Handler(Looper.getMainLooper()) {
    @Override public void handleMessage(Message msg) {
      switch (msg.what) {
        case HUNTER_BATCH_COMPLETE: {
          @SuppressWarnings("unchecked") List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;
          //noinspection ForLoopReplaceableByForEach
          for (int i = 0, n = batch.size(); i < n; i++) {
            BitmapHunter hunter = batch.get(i);
            hunter.picasso.complete(hunter);
          }
          break;
        }
    }
  };

通過(guò)Hnadler回到了Picasso里悯辙,最終調(diào)用picasso.complete方法.

void complete(BitmapHunter hunter) {
    Action single = hunter.getAction();
    List<Action> joined = hunter.getActions();

    boolean hasMultiple = joined != null && !joined.isEmpty();
    boolean shouldDeliver = single != null || hasMultiple;

    if (!shouldDeliver) {
      return;
    }

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

    if (single != null) {
      deliverAction(result, from, single, exception);
    }

    if (hasMultiple) {
      //noinspection ForLoopReplaceableByForEach
      for (int i = 0, n = joined.size(); i < n; i++) {
        Action join = joined.get(i);
        deliverAction(result, from, join, exception);
      }
    }

    if (listener != null && exception != null) {
      listener.onImageLoadFailed(this, uri, exception);
    }
  }

Picasso使用了ImageViewAction來(lái)進(jìn)行處理躲撰,也就是在ImageViewAction中的complete方法完成了最后的圖片渲染工作击费。

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

到這里就把這個(gè)圖片的渲染過(guò)程講完了

參考:

  1. Picasso源碼解析
  2. Android圖片加載庫(kù)Picasso源碼分析
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蔫巩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子圆仔,更是在濱河造成了極大的恐慌,老刑警劉巖个从,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件歪沃,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡意推,警方通過(guò)查閱死者的電腦和手機(jī)珊蟀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)外驱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)昵宇,“玉大人儿子,你說(shuō)我怎么就攤上這事∪岜疲” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵犯助,是天一觀的道長(zhǎng)维咸。 經(jīng)常有香客問(wèn)我,道長(zhǎng)瞬哼,這世上最難降的妖魔是什么租副? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上两残,老公的妹妹穿的比我還像新娘。我一直安慰自己沼死,他們只是感情好崔赌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著县钥,像睡著了一般慈迈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谴麦,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音舷蟀,去河邊找鬼面哼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛精绎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播旬牲,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼搁吓,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了擂橘?” 一聲冷哼從身側(cè)響起摩骨,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎昌罩,沒(méi)想到半個(gè)月后灾馒,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡轨功,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年容达,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蒿褂。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖娄帖,靈堂內(nèi)的尸體忽然破棺而出昙楚,到底是詐尸還是另有隱情,我是刑警寧澤堪旧,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布淳梦,位于F島的核電站,受9級(jí)特大地震影響爆袍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜弦疮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一蜘醋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧啸罢,春花似錦胎食、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)谤专。三九已至,卻和暖如春置侍,著一層夾襖步出監(jiān)牢的瞬間拦焚,已是汗流浹背杠输。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留僵刮,地道東北人鹦牛。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像窍仰,于是被迫代替她去往敵國(guó)和親礼殊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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