Picasso原始碼分析(六):BitmapHunter與請求結果的處理

王世暉發表於2016-06-22

Picasso原始碼分析(一):單例模式、建造者模式、面向介面程式設計
Picasso原始碼分析(二):預設的下載器、快取、執行緒池和轉換器
Picasso原始碼分析(三):快照功能實現和HandlerThread的使用
Picasso原始碼分析(四):不變模式、建造者模式和Request的預處理
Picasso原始碼分析(五):into方法追本溯源和責任鏈模式建立BitmapHunter
Picasso原始碼分析(六):BitmapHunter與請求結果的處理

into方法流程回顧

into方法首先檢查了是否在主執行緒,控制元件是否為null,使用者有沒有設定uri或者資源id,是否呼叫了fit方法自適應壓縮。接著根據into方法呼叫時間建立Request請求,併為該請求設定快取用的key。接著判斷是否要讀記憶體快取,可以讀記憶體快取並且命中的話就將快取中的圖片顯示在控制元件然後返回,否則開啟網路請求。網路請求的過程中暫時顯示佔點陣圖。
網路請求的過程就是針對此次請求封裝成一個ImageViewAction的請求動作,通過Picasso將此動作遞交給排程器dispatcher,在dispatcher中進行執行緒切換,在工作執行緒中處理該動作。處理的方法就是為這個動作建立一個BitmapHunter(實現了Runnable介面),然後把這個BitmapHunter交給執行緒池去處理。

BitmapHunter分析

BitmapHunter作為一個Runnable,是要交付給執行緒池執行的,所以重點關注實現的run方法。

  @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 (Downloader.ResponseException e) {
      if (!e.localCacheOnly || e.responseCode != 504) {
        exception = e;
      }
      dispatcher.dispatchFailed(this);
    } catch (NetworkRequestHandler.ContentLengthException e) {
      exception = e;
      dispatcher.dispatchRetry(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);
    }
  }

run方法在設定了執行緒名後,直接呼叫hunt方法獲取請求結果。如果hunt方法返回null,說明請求失敗,呼叫排程器dispatchFailed方法,否則說明請求成功,呼叫排程器的dispatchComplete方法。當然在網路請求的過程中會出現各種異常,run方法均一一捕獲並處理。
在finally語句塊中會重置執行緒名,表示該執行緒任務已經完成。

hunt方法分析

具體的網路請求過程是交給了hunt方法處理,該方法返回請求到的Bitmap,網路請求中出現的異常直接丟擲,交給呼叫者也就是run方法處理。

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

    data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
    RequestHandler.Result result = requestHandler.load(data, networkPolicy);
    if (result != null) {
      loadedFrom = result.getLoadedFrom();
      exifRotation = result.getExifOrientation();

      bitmap = result.getBitmap();

      // If there was no Bitmap then we need to decode it from the stream.
      if (bitmap == null) {
        InputStream is = result.getStream();
        try {
          bitmap = decodeStream(is, data);
        } finally {
          Utils.closeQuietly(is);
        }
      }
    }

    if (bitmap != null) {
      if (picasso.loggingEnabled) {
        log(OWNER_HUNTER, VERB_DECODED, data.logId());
      }
      stats.dispatchBitmapDecoded(bitmap);
      if (data.needsTransformation() || exifRotation != 0) {
        synchronized (DECODE_LOCK) {
          if (data.needsMatrixTransform() || exifRotation != 0) {
            bitmap = transformResult(data, bitmap, exifRotation);
            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;
}

hunt方法首先根據記憶體策略判斷是不是可以讀取快取,可以的話就直接讀快取,快取命中直接返回命中的bitmap,否則需要進行網路請求。
請求前先設定快取策略,retryCount為0的話表示只能讀取快取不能盡情網路請求,否則的話快取策略就用Request的快取策略。

    data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;

接著就會呼叫RequestHandler實現類的load方法進行具體的請求過程。RequestHandler有多個實現類。
RequestHandler的實現類
BitmapHunter使用哪一個RequestHandler具體在建立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);
      }
    }

如果是網路請求的話,實際上是交給了NetworkRequestHandler進行網路請求。NetworkRequestHandler的load方法是直接交給了downloader的load方法,downloader的型別是Downloader型別的介面,Downloader介面有兩個實現類,基於okhttp的OkHttpDownloader和基於HttpURLConnection的UrlConnectionDownloader。
Downloader介面的實現類

 @Override 
 public Result load(Request request, int networkPolicy) throws IOException {
  Response response = downloader.load(request.uri, request.networkPolicy);
...

請求成功的話會對請求到的結果進行處理,如果請求的結果中沒有Bitmap物件,那麼就從流中把Bitmap解析出來。
解析到Bitmap後,還要對Bitmap進行各種變換處理,最終將變換處理後的Bitmap返回。

請求成功後的處理

hunt方法上的請求結果會反饋給排程器

      result = hunt();

      if (result == null) {
        dispatcher.dispatchFailed(this);
      } else {
        dispatcher.dispatchComplete(this);
      }

排程器通過handler切換執行緒在工作執行緒對請求結果進行處理

  void dispatchComplete(BitmapHunter hunter) {
    handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
  }
    @Override public void handleMessage(final Message msg) {
      switch (msg.what) {
        ...
        case HUNTER_COMPLETE: {
          BitmapHunter hunter = (BitmapHunter) msg.obj;
          dispatcher.performComplete(hunter);
          break;
        }
        ...
  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");
    }
  }

performComplete方法先根據快取策略進行快取的更新或儲存,接著從map中刪除此請求。接下來有個問題,此時是在工作執行緒進行的處理,如果要將請求結果顯示在控制元件上,需要切換到UI主執行緒。如果通過handler進行執行緒切換,那麼handler應該和被切換到的執行緒的looper繫結在一起,因此需要一個和主執行緒的looper繫結在一起的handler進行執行緒切換,將結果切換到主執行緒。
接下來看batch方法的處理

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

batch方法給工作執行緒的handler傳送了HUNTER_DELAY_NEXT_BATCH訊息,所以在工作執行緒的handleMessage方法中對此訊息進行處理

        case HUNTER_DELAY_NEXT_BATCH: {
          dispatcher.performBatchComplete();
          break;
        }

那麼應該是在performBatchComplete()方法中完成了執行緒的切換

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

果不其然,這裡通過mainThreadHandler給主執行緒傳送了HUNTER_BATCH_COMPLETE訊息,那麼在主執行緒的handleMessage方法中會處理此訊息

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

而在主執行緒中呼叫的complete方法中,會呼叫deliverAction(result, from, join)方法,deliverAction方法呼叫了action.complete(result, from),將請求到的圖片顯示到了控制元件上。

相關文章