Android每週一輪子:Picasso

前言

最開始接觸到Picasso框架仍是在大三實現的時候,已經很是久遠了,Picasso是Android一個遠古時代的框架了,同時代的Volley早已被各家棄用,可是該框架實現較爲簡單適合做爲初學者對圖片加載庫源碼學習使用,對於瞭解圖片加載框架的實現原理仍是挺有幫助的。緩存

基礎使用

Picasso.get().load("https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=2205936453,824698011&fm=26&gp=0.jpg").into(binding.iv);
複製代碼

Picasso的使用比較簡單,設置一些參數,提供一個url,而後設置加載在哪個圖形控件上,剩下的就能夠交給框架去完成了,固然咱們也能夠給圖片設置一些變化操做信息。同時在其內部也幫咱們將圖片的縮放相關操做完成了。bash

實現原理

在分析其實現原理以前,咱們不妨先構思一個圖片庫應該如何去實現,它應該具有哪一些功能?網絡

首先下載是必須的,其次是緩存,下載完成以後要對其進行緩存,否則每一次都要從新下載,緩存又要涉及到內存緩存和磁盤緩存,同時對於加載的圖片也要進行一些縮放等一系列操做,而這過程又都是比較耗時的,所以這些過程都應該在子線程中執行,爲了提供線程的利用率,咱們則要利用線程池。當圖片下載完成,咱們須要將圖片設置到咱們的View上,而這個操做必需要在主線程發生,所以內部提供了一個持有主線程Looper的Handler來負責進行調度。帶着這些構思,咱們來跟進看一下源碼的具體實現。數據結構

這裏咱們根據上述的測試代碼中爲ImageView設置一個來自於網絡的圖片來作分析。框架

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

Load方法返回了一個RequestCreator是對於請求Request的一些包裝,再來看一下它的into方法。oop

public void into(ImageView target) {
    into(target, null);
}
複製代碼

內部的into方法的實現是其核心。下面咱們截取一部分代碼來進行分析。學習

//建立一個請求,同時根據請求信息生成key值
Request request = createRequest(started);
String requestKey = createKey(request);
//判斷是否要走緩存
if (shouldReadFromMemoryCache(memoryPolicy)) {
    //從緩存中查找是否存在
  Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
  if (bitmap != null) {
    picasso.cancelRequest(target);
    //存在則將該Bitmap設置上
    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());
}
//緩存沒有匹配到則建立一個ImageViewAction,加入到請求隊列
Action action =
    new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
        errorDrawable, requestKey, tag, callback, noFade);

picasso.enqueueAndSubmit(action);
複製代碼

對於上述步驟接下來將進行逐一的分析。測試

  • Key值的生成

這裏的Key值再也不單純的只是請求的url,而是其配置的一系列對於圖片上的信息,包括寬高等信息。ui

  • 緩存查找

根據Key從緩存中查找的過程是如何呢?接下來咱們一塊兒來看一下this

Bitmap quickMemoryCacheCheck(String key) {
  Bitmap cached = cache.get(key);
  if (cached != null) {
    stats.dispatchCacheHit();
  } else {
    stats.dispatchCacheMiss();
  }
  return cached;
}
複製代碼

從Picasso的cache中進行查找,而Picasso中的cache的實現是怎麼樣呢?其內部仍是經過一個LruCache來實現的。內部存儲的緩存數據結構

LruCache<String, LruCache.BitmapAndSize>
複製代碼

BitMapAndSize存在每個圖片和其大小

static final class BitmapAndSize {
  final Bitmap bitmap;
  final int byteCount;

  BitmapAndSize(Bitmap bitmap, int byteCount) {
    this.bitmap = bitmap;
    this.byteCount = byteCount;
  }
}
複製代碼

其最大的把內存空間是最大內存的大概1/7

static int calculateMemoryCacheSize(Context context) {
  ActivityManager am = getService(context, ACTIVITY_SERVICE);
  boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0;
  int memoryClass = largeHeap ? am.getLargeMemoryClass() : am.getMemoryClass();
  // Target ~15% of the available heap.
  return (int) (1024L * 1024L * memoryClass / 7);
}
複製代碼

對於LruCache內部的實現是經過LinkedHashMap來實現的,經過其來實現最近最少未使用的替換規則,LruCache的實現,須要了其SizeOf方法來返回每個對象的大小,LruCache根據這個來進行相應的回收操做,每次加入對象的時候都要進行判斷是否超過了最大大小,若是超過了,則要進行trimToSize來不斷的減小來刪除其中的最近微使用的,同時提供了entryRemoved的回調,每次有被刪除的,咱們均可以進行監聽而後進行一些操做。

  • LinkedHashMap的實現

其內部LinkedHashMapEntry是繼承自HashMap的Node來實現的具有一個before和after指針。在調用其get和put方法以後都會調用afterNodeAccess方法,經過對於其先後節點的操做來實現一個轉化。

  • 內存查找不到時如何請求

ImageViewAction繼承自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);
}
複製代碼

至此,其將會經過Dispatcher來進行執行。Dispatcher內部的消息執行全是經過Handler的方式來實現的,經過Handler傳遞消息,而後接收到相應的消息以後調用相應的方法。

hunter = forRequest(action.getPicasso(), this, cache, stats, action);
hunter.future = service.submit(hunter);
hunterMap.put(action.getKey(), hunter);
複製代碼

將其添加到線程池之中就行執行。這裏對於線程池的實現對網絡進行了監聽,經過對於網絡不通類型的監聽來實現對於線程池中核心線程數目的調度,不通的網絡狀況下開啓的線程數目是不同的。

corePoolSize:核心線程數。

maximumPoolSize:線程池所能容納的最大線程數,當活動線程達到這個數值以後,任務就會阻塞。

keepAliveTime:非核心線程閒置超時時長,超過這個時長就會被回收,當allowCoreThreadTimeOut設置爲true的時候,其對於核心線程也是一樣生效的。

unit:KeepLiveTime參數的時間單位

workQueue:線程池中的任務隊列

threadFactory:線程工廠用來建立新的線程。

線程池是經過BlockingQueue來管理相應的執行任務的。

  • BitmapHunter

BitmapHunter顧名思義,就是用來找Bitmap的,調用其Hunt方法,hunt方法中會先進行緩存中的查詢,而後再進行網絡請求,若是獲取不到Bitmap則從返回的流中進行decode出一個Bitmap來。具體請求的執行是經過RequestHandler來進行的,對於不一樣類型資源的加載擁有不一樣的Handler。內部線程的調度也是藉助於主線程的Handler來實現,BitmapHunter其實是一個Runnable,對於圖片的下載,緩存和處理相關邏輯都封裝在其中。

  • DownloadManager

對於磁盤緩存的實現,Picasso直接藉助了OKhttp自身的實現,對於NetworkRequestHandler的實現其中的load方法會調用OkHttp的下載實現來進行下載,最後經過new Result(body.source(), loadedFrom)來返回,而後在BitmapHunter中對其進行解碼,創造Bitmap出來。

Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);
複製代碼

最後根據加載回來的圖片,應用上相應的配置以後,建立一個Bitmap出來,建立完成以後,開始應用上一些變化的操做。最後返回一個處理好的Bitmap,返回的方式是經過Dispatcher的線程調度策略,經過調度以後,還進行了一個Bitmap的預加載操做。

在進行圖片的縮放的時候,在計算的時候,咱們不須要先將圖片進行加載,只須要測量其寬高,計算好加載比例以後,咱們才須要將其裝載到內存之中。經過設置其inJustDecodeBounds爲true,咱們能夠先獲取其寬高,而後計算比如例以後,再進行解碼加載。

if (hunter.result != null) {
  hunter.result.prepareToDraw();
}
複製代碼

在進行加載以前,調用prepareToDraw方法將Bitmap先提早加載到GPU上,提高後面的繪製效率,而後在調用相應的ImageView設置ImageDrawable的方法。

設計亮點

  • 網絡類型切換的自適應調整
case NETWORK_STATE_CHANGE: {
  NetworkInfo info = (NetworkInfo) msg.obj;
  dispatcher.performNetworkStateChange(info);
  break;
}
複製代碼

當網絡類型發生變化的時候,會自動進行線程池核心線程數的變化,網絡類型越差,線程池中核心線程數量越少。

總結

Picasso的實現上,經過BitmapHunter來進行數據的獲取和處理,經過Cache內的LruCache來實現內存中的存儲,經過Dispatcher來實現線程間的調度,最上層的Picasso來實現對應用層接口的暴露。

相關文章
相關標籤/搜索