picasso框架

weixin_34353714發表於2018-03-14

picasso 是一個強大的圖片載入快取框架

1.首先看下picasso 如何使用:

Picasso.with(this)
.load("url")
.placeholder(R.drawable.leak_canary_icon)
.error(R.drawable.leak_canary_icon)
.resize(480,800)
.centerCrop()
.rotate(360)
.priority(Picasso.Priority.HIGH)
.tag("picasso listview")
.memoryPolicy(MemoryPolicy.NO_CACHE)
.networkPolicy(NetworkPolicy.NO_CACHE)
.into(imageView);

Picasso和Glide相似都是使用鏈式呼叫 我們可以把Picasso類視為一個管理類,管理圖片的載入,轉換,快取的工作.
.with()獲取Picasso的單例,引數是上下文.
.load()方法完成載入圖片的方法.引數是url或file路徑
.resize()方法 引數單位畫素 因為在專案中要考慮網路頻寬,手機的記憶體使用,下載速度等情況.結合考慮.這時候有一種場景伺服器給我們的圖片的寬和高與我們實際的imageview的寬和高是不一致的.伺服器給我們一些奇奇怪怪的圖片,這時我們就要呼叫resize方法.對圖片的寬高進行設定.
.centerCrop() 將整個圖片充滿imageview邊界 裁減掉多餘部分
.rotate() 對圖片載入進行旋轉設定.旋轉點座標0.0
.priority(Picasso.Priority.HIGH) 設定圖片載入優先順序 不是100%保證 因為picasso內部只是向高的優先順序靠攏.但並不會保證
.tag() Picasso 允許我們為每個請求設定一個tag 為什麼用到tag呢 比如listview 快速滑動這樣的場景,如果不去設定響應事件就會載入所有的圖片,浪費效能,造成體驗不好.UI卡頓,而我們為每一個載入事件做標記tag,監聽響應事件.做出處理操作.

mlsitview.setOnScrollStateChanged(AbsListView view,int scrollState){
final Picasso picasso = Picasso.with(MainActivity.this);
if(scrollState == SCROLL_STATE_IDLE){//停止狀態載入圖片
picasso.resumeTag("picasso listview");
}else{
picasso.pauseTag("picasso listview");
}
}

.memoryPolicy(MemoryPolicy.NO_CACHE) picasso的快取策略 記憶體快取
.networkPolicy(NetworkPolicy.NO_CACHE)檔案快取

2.picasso原始碼分析

1)with方法:記憶體快取Lrucache和執行緒池排程

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

.build()方法,生成Picasso物件,返回該物件例項,按照需求配置下載器,快取,執行緒池,轉換器等.

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

.build()方法.-->downloader = Utils.createDefaultDownloader(context);
-->

static Downloader createDefaultDownloader(Context context) {
    try {
      Class.forName("com.squareup.okhttp.OkHttpClient");//通過反射查詢是否引用了okhttp,如果引用了採用okhttp進行網路連線
      return OkHttpLoaderCreator.create(context);
    } catch (ClassNotFoundException ignored) {
    }
    return new UrlConnectionDownloader(context);//沒有引用,採用httpurlConnection進行網路請求
  }

繼續看build方法中

 if (cache == null) {
        cache = new LruCache(context);//lruCache主要採用linkedhashmap 將圖片新增到連結串列尾部//( final LinkedHashMap<String, Bitmap> map;)
      }
      if (service == null) {
        service = new PicassoExecutorService();
      }

接著來看PicassoExecutorService
PicassoExecutorService 是一個執行緒併發池
PicassoExecutorService extends ThreadPoolExecutor
ThreadPoolExecutor是java併發庫中的內容 無論是圖片庫還是網路庫都離不開這個執行緒池

public ThreadPoolExecutor(int corePoolSize, //核心執行緒數目
                              int maximumPoolSize,//執行緒池中允許的最大執行緒數
                              long keepAliveTime,//執行緒池中執行緒數目比核心執行緒數目多的時候,超過這個時間,就會將多餘的執行緒回收
                              TimeUnit unit,//時間單位
                              BlockingQueue<Runnable> workQueue,//阻塞佇列 執行緒安全佇列
                              ThreadFactory threadFactory,//執行緒工廠 產生執行緒
                              RejectedExecutionHandler handler) {}//當執行緒池 由於執行緒數目和佇列導致任務阻塞時 ,執行緒池的處理方式

1.如果執行緒池中的執行緒數目少於corePoolSize , 這時候執行緒池是會重新建立執行緒的,直到執行緒數目達到corePoolSize.
2.如果執行緒池中執行緒數目大於或者等於corePoolSize,但是工作佇列workQueue沒有滿, 這時新的任務還是會放進佇列中的,按照先進先出的原則來進行執行.
3.如果執行緒池中的執行緒數目大於等於corePoolSize,並且工作佇列workQueue滿了,但是匯流排程數目小於maximumPoolSzie 這時候還是可以直接建立執行緒 來處理新增的任務
4.如果工作佇列滿了,並且執行緒池中執行緒的數目達到了最大數目maximumPoolSize 這時就會由RejectedExecutionHandler來處理
預設的處理方式就是 丟棄到任務,同時丟擲異常
繼續看
非常重要的 Dispatcher 是來管理非常重要的執行緒之間的切換.
dispatcher如何完成執行緒切換
首先我們進入Dispatcher這個類,Dispatcher類中首先開啟執行緒DispatcherThread

final DispatcherThread dispatcherThread;
this.dispatcherThread = new DispatcherThread();
this.dispatcherThread.start();

首先DispatcherThread extends HandlerThread

static class DispatcherThread extends HandlerThread {
    DispatcherThread() {
      super(Utils.THREAD_PREFIX + DISPATCHER_THREAD_NAME, THREAD_PRIORITY_BACKGROUND);
    }
  }

所以先分析下HandlerThread
HandlerThread extends Thread 註釋中這樣寫到他會開啟一個新的執行緒,執行緒內部會有一個Looper物件,通過這個Looper物件可以去迴圈遍歷我們的訊息佇列.
看下run()方法
run方法的作用就是完成Looper的建立 通過Looper的迴圈方法構造一個迴圈的執行緒.HandlerThread也要呼叫start方法,完成執行緒的執行的.

@Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();//完成Looper物件的建立
        synchronized (this) {//通過同步鎖機制鎖住當前物件
            mLooper = Looper.myLooper();//mLooper在我們的同步程式碼塊中
            notifyAll();//喚醒等待執行緒 和notifyAll()同步出現的就是wait()方法.
        }
        Process.setThreadPriority(mPriority);//設定執行緒優先順序
        onLooperPrepared();//內部是個空實現 留給我們自己去重寫
        Looper.loop();//完成整個的執行緒訊息啟動工作
        mTid = -1;
    }

//我們回到同步程式碼塊中 我們為什麼要通過同步程式碼塊中的方法去喚醒等待執行緒呢?有在哪裡去wait的操作呢?
在getLooper方法中

 public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {//這裡同樣用同步鎖機制鎖住當前物件
            while (isAlive() && mLooper == null) {//判斷執行緒存貨且不為空的情況下
                try {
                    wait();//執行緒等待
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

繼續看Dispatcher構造方法中的其他引數

Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler,
      Downloader downloader, Cache cache, Stats stats) {
    this.dispatcherThread = new DispatcherThread();
    this.dispatcherThread.start();
    Utils.flushStackLocalLeaks(dispatcherThread.getLooper());
    this.context = context;
    this.service = service;
    this.hunterMap = new LinkedHashMap<String, BitmapHunter>();
    this.failedActions = new WeakHashMap<Object, Action>();
    this.pausedActions = new WeakHashMap<Object, Action>();
    this.pausedTags = new HashSet<Object>();
    this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);//dispatherThread執行緒內部的handler(因為dispatcherThread.getLooper()引數)
    this.downloader = downloader;
    this.mainThreadHandler = mainThreadHandler;//主執行緒的handler
    this.cache = cache;
    this.stats = stats;
    this.batch = new ArrayList<BitmapHunter>(4);
    this.airplaneMode = Utils.isAirplaneModeOn(this.context);
    this.scansNetworkChanges = hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE);
    this.receiver = new NetworkBroadcastReceiver(this);//廣播接收者,主要用於監聽網路變化的操作的,這也是比較重要的
    receiver.register();
  }

回到picasso的build()方法中
Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
HANDLER是picasso內部定義的
static final Handler HANDLER = new Handler(Looper.getMainLooper()){//getMainLooper是主執行緒
}//定義為靜態內部類,就是靜態內部類不持有外部類的引用

3.NetworkRequestHandler處理圖片請求與回撥

繼續看build()方法
build()方法最後 還是返回一個picasso物件
return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
我們進入這個方法看一下

  Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
      RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
      Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
  this.context = context;
    this.dispatcher = dispatcher;
    this.cache = cache;
    this.listener = listener;
    this.requestTransformer = requestTransformer;
    this.defaultBitmapConfig = defaultBitmapConfig;

    int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.
    int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
    List<RequestHandler> allRequestHandlers =
        new ArrayList<RequestHandler>(builtInHandlers + extraCount);

    // ResourceRequestHandler needs to be the first in the list to avoid
    // forcing other RequestHandlers to perform null checks on request.uri
    // to cover the (request.resourceId != 0) case.
    allRequestHandlers.add(new ResourceRequestHandler(context));
    if (extraRequestHandlers != null) {
      allRequestHandlers.addAll(extraRequestHandlers);//將extraRequestHandlers集合加入到allRequesthandlers集合中,看下
allRequestHandler這個ArrayList集合.

    }
    allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
    allRequestHandlers.add(new MediaStoreRequestHandler(context));
    allRequestHandlers.add(new ContentStreamRequestHandler(context));//集合中加入處理contentprovider中圖片的handler
    allRequestHandlers.add(new AssetRequestHandler(context));//處理asset中圖片
    allRequestHandlers.add(new FileRequestHandler(context));//處理file中圖片
    allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));//處理網路請求圖片的handler
    requestHandlers = Collections.unmodifiableList(allRequestHandlers);

    this.stats = stats;
    this.targetToAction = new WeakHashMap<Object, Action>();
    this.targetToDeferredRequestCreator = new WeakHashMap<ImageView, DeferredRequestCreator>();
    this.indicatorsEnabled = indicatorsEnabled;
    this.loggingEnabled = loggingEnabled;
    this.referenceQueue = new ReferenceQueue<Object>();
    this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);
    this.cleanupThread.start();
}

我們主要看下處理網路請求圖片的handler:NetworkRequestHandler.主要看其中的load()方法:

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

    Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;

    Bitmap bitmap = response.getBitmap();
    if (bitmap != null) {
      return new Result(bitmap, loadedFrom);
    }

    InputStream is = response.getInputStream();
    if (is == null) {
      return null;
    }
    // Sometimes response content length is zero when requests are being replayed. Haven't found
    // root cause to this but retrying the request seems safe to do so.
    if (loadedFrom == DISK && response.getContentLength() == 0) {
      Utils.closeQuietly(is);
      throw new ContentLengthException("Received response with 0 content-length header.");
    }
    if (loadedFrom == NETWORK && response.getContentLength() > 0) {//請求響應長度>0
      stats.dispatchDownloadFinished(response.getContentLength());//stats派發響應完成結果
    }
    return new Result(is, loadedFrom);
  }

看下stats的dispatchDownloadFinished方法:

void dispatchDownloadFinished(long size) {
    handler.sendMessage(handler.obtainMessage(DOWNLOAD_FINISHED, size));
  }

發現了熟悉的sendMessage()方法
其中的handler是StatsHandler 看其中handleMessage()方法:

 @Override public void handleMessage(final Message msg) {
      switch (msg.what) {
        case CACHE_HIT:
          stats.performCacheHit();
          break;
        case CACHE_MISS:
          stats.performCacheMiss();
          break;
        case BITMAP_DECODE_FINISHED:
          stats.performBitmapDecoded(msg.arg1);
          break;
        case BITMAP_TRANSFORMED_FINISHED:
          stats.performBitmapTransformed(msg.arg1);
          break;
        case DOWNLOAD_FINISHED:
          stats.performDownloadFinished((Long) msg.obj);

          break;
        default:
          Picasso.HANDLER.post(new Runnable() {
            @Override public void run() {
              throw new AssertionError("Unhandled stats message." + msg.what);
            }
          });
      }
    }

Stats中的performDownloadFinished()方法

  void performDownloadFinished(Long size) {
    downloadCount++;
    totalDownloadSize += size;
    averageDownloadSize = getAverage(downloadCount, totalDownloadSize);
  }

4.picasso原始碼load方法

load 方法return load(Uri.parse(path)).
該函式有四種過載方法,其中Uri,String,File最終都轉化為Uri進行請求,而int則是app內部的資源訪問。
load()函式返回RequestCreator物件,

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

再看RequestCreator();
RequestCreator的成員變數裡有一個重要的物件是Request.Builder(其有一個內部類Builder),RequestCreator裡很多函式都是橋 接到該Builder。

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

看最後一行是建立了 Request.Builder物件,通過Request.Builder物件我們可以配置更多物件.

picasso原始碼into方法:Action&BitmapHunter

with()方法完成了Picasso物件的建立,內部通過單例的形式進行構建,這樣就保證了在app使用Picasso物件的時候,只會存在一個
picasso物件,同時他也是我們整個Picasso載入的入口.同時with()方法還做了一些基礎的配置工作.比如說Dispatcher這個類,在picasso框架中主要完成執行緒的切換工作,其內部實現原理是handler.
load()方法主要完成了RequsetCreate()方法的建立.同時獲取到了我們要載入的一些資源的路徑
into()方法完成圖片的載入工作.

DeferredRequestCreator類
當我們建立請求的時候,我們新建一個圖片載入請求,但是我們還不能獲取到當imageview的寬高,這時我們會建立DeferredRequestCreator,通過這個類會我們的imageview的target去進行監聽,直到獲取到當前imageview的寬高,這時候我們重新執行我們的請求建立.所以刪除請求的時候,同時我們還應該刪除DeferredRequestCreator對target的監聽事件.

public void into(Target target) {
    long started = System.nanoTime();
//執行緒檢查
    checkMain();

    if (target == null) {
      throw new IllegalArgumentException("Target must not be null.");
    }
    if (deferred) {
      throw new IllegalStateException("Fit cannot be used with a Target.");
    }
//沒設定url以及resId則取消請求
    if (!data.hasImage()) {
      picasso.cancelRequest(target);//取消請求
      target.onPrepareLoad(setPlaceholder ? getPlaceholderDrawable() : null);//取消後設定佔位符.
      return;
    }

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

    if (shouldReadFromMemoryCache(memoryPolicy)) {
      Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
      if (bitmap != null) {
        picasso.cancelRequest(target);
        target.onBitmapLoaded(bitmap, MEMORY);
        return;
      }
    }

    target.onPrepareLoad(setPlaceholder ? getPlaceholderDrawable() : null);

    Action action =
        new TargetAction(picasso, target, request, memoryPolicy, networkPolicy, errorDrawable,
            requestKey, tag, errorResId);
//提交請求,Picasso內部維護了一個map,key是imageView,value是Action
//提交時做判斷,如果當前imageView已經在任務佇列裡了。判斷當前任務與之前的任務是否相同,
//如果不相同則取消之前的任務並將新的key-value加入到map
    picasso.enqueueAndSubmit(action);
}

RequestCreator最重要的一個方法是into(),在into()方法呼叫之前所呼叫的一切配置函式都只是把配置資訊儲 存起來,而沒有做網路請求,當呼叫into()函式後才開始網路請求。into()函式有五個過載方法,用於把請求結果存 放或顯示到指定位置。

submit()函式:用於把RequestCreator提交上來的Action新增到佇列裡,該函式其實是把提交任務交給Dispatcher

3995903-c13e0e36996d5b09
image

而Dispatcher最後則通過handler把任務切換到Dispatcher所在的執行緒(後臺執行緒,因為要進行網路訪問).

RequestCreator最重要的一個方法是into(),在into()方法呼叫之前所呼叫的一切配置函式都只是把配置資訊儲 存起來,而沒有做網路請求,當呼叫into()函式後才開始網路請求。into()函式有五個過載方法,用於把請求結果存 放或顯示到指定位置。

3995903-ec4b553f414e9da4
image

這裡分析into到ImageView裡,該函式首先會檢視快取裡是否有請求的Bitmap,如果有那最好,都不用進行網路請求, 直接把Bitmap顯示到ImageView裡。如果快取裡沒有,則會把請求加入到請求佇列裡,之後進行網路請求。

到這裡,又把控制權交給Picasso類。

3995903-0e5527563940ce1b
image

底層分析:
Action 類:是一個request請求類包裝類,最終把包裝好的類,交給執行緒還執行.(一個沒有Set的Bean,包含各種動作資訊,如網路請求策略,記憶體策略,請求配置等。)
BitmapHuntyer 就是一個runnable,是一個開啟子執行緒的工具.是一個Runnable的子類,用來進行Bitmap的獲取(網路,硬碟,記憶體等),處理(角度,大小等),然後執行分發器(dispatcher)的回撥處理

picasso原始碼into方法:執行緒池&PicassoFutureTask

Stats:對請求整個過程的一個記錄,如命中(hit)快取的次數,不命中(miss)快取的次數,Bitmap下載解析次數, 下載完成次數等。
ImageViewAction:Action的子類,內部有complete()和error()函式,用於把請求結果顯示到ImageView上
其實Picasso裡用來在指定目標上顯示結果都是通過PicassoDrawable類來實現的。

再次 強調下最重要的類

RequestHandler

//抽象類,由不同的子類來實現不同來源的圖片的獲取與載入,比如:
//AssetRequestHandler:載入asset裡的圖片
//FileRequestHandler:載入硬碟裡的圖片
//ResourceRequestHandler:載入資源圖片
//NetworkRequestHandler:載入網路圖片

BitmapHunter

//是一個Runnable的子類,用來進行Bitmap的獲取(網路,硬碟,記憶體等),處理(角度,大小等),
//然後執行分發器(dispatcher)的回撥處理

PicassoDrawable

//實現了引入圖片漸變功能和debug狀態的標識的Drawable,用來在最後bitmap轉換成PicassoDrawable
//然後設定給ImageView,根據圖片的來源可以在圖片的左上角顯示一個不同顏色的三角形色塊
MEMORY(Color.GREEN) //記憶體載入
DISK(Color.BLUE) //本地載入
NETWORK(Color.RED) //網路載入

DeferredRequestCreator

//ViewTreeObserver.OnPreDrawListener的實現類,即將繪製檢視樹時執行的回撥函式。
//這時所有的檢視都測量完成並確定了框架。 客戶端可以使用該方法來調整滾動邊框,
//甚至可以在繪製之前請求新的佈局,這裡用來實現當開發者修改圖片尺寸時的邏輯

Action

//Action代表了一個具體的載入任務,主要用於圖片載入後的結果回撥,有兩個抽象方法,complete和error,
//並儲存了每個請求的各種資訊(),具體的實現類如下
//GetAction:同步執行請求時使用。
//FetchAction:當不需要ImageView來安置bitmap時的非同步請求,通常用來預熱快取
//RemoteViewsAction:用來更新遠端圖片(notification等)的抽象類。
//TargetAction:一般在View(不只是ImageView)或者ViewHolder中用來載入圖片,需要實現Target介面
//ImageViewAction:最常用的Action,主要用來給ImageView載入圖片

相關文章