Picasso學習筆記


注:文章轉載自文章轉載自RowandJJ的博客:http://blog.csdn.net/chdjj/article/details/49964901
下面是我在學習Picasso過程中做的筆記.

關於圖片加載庫的思考

我們爲什麼要用圖片庫呢?
圖片庫通常會對圖片加載邏輯進行封裝、優化,比如多級緩存/異步線程調度/圖片壓縮變換,有了這些特性,開發者不再需要關注內存佔用、
OOM、網絡請求等問題,而只需關注業務本身的邏輯,這對提高生產效率還是很有幫助的。

我通過調研一些圖片庫,發現一個圖片加載庫通常有以下模塊:

  1. 請求分發模塊。負責封裝請求,對請求進行優先級排序,並按照類型進行分發。
  2. 緩存模塊。通常包括一個二級的緩存,內存緩存、磁盤緩存。並預置多種緩存策略。
  3. 下載模塊。負責下載網絡圖片。
  4. 監控模塊。負責監控緩存命中率、內存佔用、加載圖片平均耗時等。
  5. 圖片處理模塊。負責對圖片進行壓縮、變換等處理。
  6. 本地資源加載模塊。負責加載本地資源,如assert、drawable、sdcard等。
  7. 顯示模塊。負責將圖片輸出顯示。
    這裏寫圖片描述

Android平臺圖片加載庫現狀

目前社區主流的圖片加載庫有Universal ImageLoader,Picasso,Volley,Fresco,Glide.

Picasso簡介

A powerful image downloading and caching library for Android,developed by Square

wiki:http://square.github.io/picasso/

Picasso的特性

  1. 絕對是最輕量的圖片加載庫,120kb.
  2. 自帶監控功能,可以檢測cache hit/內存大小等等數據
  3. 圖片預加載
  4. 線程併發數依網絡狀態變化而變化、優先級調度
  5. 圖片變換
  6. 圖片壓縮、自適應
  7. 易擴展

Picasso的使用

  • 加載一張網絡圖片到ImageView
Picasso.with(context)
    .load(url)
    .placeholder(R.drawable.user_placeholder)
    .error(R.drawable.user_placeholder_error)
    .into(imageView);//此種策略並不會壓縮圖片
  • 預加載一張圖片
Picasso.with(this).load(URL).fetch();
Picasso.with(this).load(URL).fetch(Callback);
  • 1
  • 2

注意哦,如果你以以下面這種方式加上圖形變換preload的話:

Picasso.with(this).load(URL).rotate(20).fetch();
  • 1

再下面這種方式是取不到preload的圖片的,因爲預緩存的是經過變換後的圖片,它的cachekey會有rotation標識

Picasso.with(this).load(URL).into(imageView);
  • 1

當然我說的是preload到內存中的那份經過旋轉的圖片,http會緩存旋轉前的圖片到磁盤(支持緩存的情況下),所以最終還是可以從磁盤緩存
拿到圖片的。

  • 替換默認的Picasso
Picasso p = new Picasso.Builder(this).executor().downloader(downloader).memoryCache(cache).build();
Picasso.setSingletonInstance(p);
  • 1
  • 2
  • 同步call
 new AsyncTask<Void,Void,Bitmap>(){
        @Override
        protected Bitmap doInBackground(Void... params) {
            try {
                return Picasso.with(PicassoTestActivity.this).load(URL).get();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            if(bitmap != null){
                imageView.setImageBitmap(bitmap);
            }
        }
    }.execute();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

注意,必須在異步線程調用,否則crash,另外,這個結果並不會緩存到內存裏面,所以慎用。

  • 自適應
Picasso.with(TestImageActivity.this).load(url).fit().into(imageview);
  • 1

fit方法的意思是,讓圖片的寬高恰好等於imageView的寬高.前提是你的imageView控件不能設置成wrap_content,也就是必須
有大小才行。另外,如果使用了fit方法,那麼就不能調用resize.

  • 壓縮到指定尺寸
Picasso.with(TestImageActivity.this).load(url).resize(widthPixel,heightPixel).centerInside().into(imageView);
Picasso.with(TestImageActivity.this).load(URL).resizeDimen(R.dimen.width,R.dimen.height).centerCrop().into(iv);
  • 1
  • 2

resize後面通常接centerInside或者centerCrop。注意這跟ImageView的scaleTyoe沒有關係,僅僅指的是圖片的縮放方式。
比如如下代碼,iv不壓縮,iv_2寬高壓縮到40dp並且指定爲centerInside.

<ImageView
        android:id="@+id/iv"
        android:background="#000"
        android:layout_width="100dp"
        android:layout_height="100dp"/>
<ImageView
    android:layout_below="@id/iv"
    android:id="@+id/iv_2"
    android:background="#000"
    android:layout_width="100dp"
    android:layout_height="100dp"/>

Picasso.with(TestImageActivity.this).load(URL).into(iv);
Picasso.with(TestImageActivity.this).load(URL).resizeDimen(R.dimen.width/*40dp*/, R.dimen.height/*40dp*/).centerInside().into(iv_2);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

最終顯示結果如下:

這裏寫圖片描述

可以很明顯看到下面的圖模糊許多,這是因爲圖片被壓縮了,但是顯示的時候又被ImageView拉伸了(默認scaleType是fitCenter),
要想不顯示拉伸的圖,可以給iv_2增加scaleType="centerInside",效果如下:

這裏寫圖片描述

  • 圖形變換
Picasso.with(TestImageActivity.this).load(URL).rotate(20).into(iv);
//自定義變換
 Picasso.with(TestImageActivity.this).load(URL).transform(new Transformation() {
                    @Override
                    public Bitmap transform(Bitmap source) {//從原圖中間裁剪一個正方形
                        int size = Math.min(source.getWidth(), source.getHeight());
                        int x = (source.getWidth() - size) / 2;
                        int y = (source.getHeight() - size) / 2;
                        Bitmap result = Bitmap.createBitmap(source, x, y, size, size);
                        if (result != source) {
                            source.recycle();
                        }
                        return result;
                    }

                    @Override
                    public String key() {
                        return "square()";
                    }
                }).into(iv);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

各種圖形變換:https://github.com/wasabeef/picasso-transformations

  • 暫停/重啓請求任務

通常在滑動列表的時候需要暫停請求


Picasso.with(context).load(URL).tag(context);


public class SampleScrollListener implements AbsListView.OnScrollListener {
  private final Context context;


  public SampleScrollListener(Context context) {
    this.context = context;
  } 


  @Override 
  public void onScrollStateChanged(AbsListView view, int scrollState) {
    final Picasso picasso = Picasso.with(context);
    if (scrollState == SCROLL_STATE_IDLE || scrollState == SCROLL_STATE_TOUCH_SCROLL) {
      picasso.resumeTag(context);
    } else { 
      picasso.pauseTag(context);
    } 
  } 


  @Override 
  public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
                       int totalItemCount) {
    // Do nothing. 
  } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

Picasso源碼分析

整體架構

盜用下Trinea的圖:

整體架構

Picasso中的核心類包括

PicassoDispatcherBitmapHunterRequestHandlerRequestActionCache
等.Picasso類是一個負責圖片下載、變換、緩存的管理器,當它收到一個圖片下載請求的時候,它會創建Request並提交給Dispatcher,
Dispatcher會尋找對應的處理器RequestHandler,並將請求與該處理器一起提交給線程池執行,圖片獲取成功後,最終會交給
PicassoDrawable顯示到Target上。

它將一張圖片的加載過程分爲八步,依次爲:

創建->入隊->執行->解碼->變換->批處理->完成->分發->顯示(可選)

也可以從日誌中看到這個過程:

11-05 10:39:00.942 2952-2952/com.taobao.paimainews D/Picasso: Main        created      [R0] Request{http://ww3.sinaimg.cn/mw600/006g34NHgw1exj5c4hmfvj30hs0qoqff.jpg resize(90,300) centerInside rotation(30.0) ARGB_8888}
11-05 10:39:00.981 2952-3109/com.taobao.paimainews D/Picasso: Dispatcher  enqueued     [R0]+40ms
11-05 10:39:00.993 2952-3193/com.taobao.paimainews D/Picasso: Hunter      executing    [R0]+50ms
11-05 10:39:01.038 2952-3193/com.taobao.paimainews D/Picasso: Hunter      decoded      [R0]+97ms
11-05 10:39:01.041 2952-3193/com.taobao.paimainews D/Picasso: Hunter      transformed  [R0]+100ms
11-05 10:39:01.042 2952-3109/com.taobao.paimainews D/Picasso: Dispatcher  batched      [R0]+101ms for completion
11-05 10:39:01.279 2952-2952/com.taobao.paimainews D/Picasso: Main        completed    [R0]+338ms from DISK
11-05 10:39:01.280 2952-3109/com.taobao.paimainews D/Picasso: Dispatcher  delivered    [R0]+338ms
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

下面是Picasso的類圖:

類圖

代碼分析

版本:2.5.2

Picasso類是整個圖片加載器的入口,負責初始化各個模塊,配置相關參數等等。
Picasso.with()方法用於創建全局唯一的Picasso實例,爲了確保唯一,使用了單例模式

Picasso#with()

 static volatile Picasso singleton = null;

 public static Picasso with(Context context) {
    if (singleton == null) {
      synchronized (Picasso.class) {
        if (singleton == null) {
          singleton = new Builder(context).build();
        }
      }
    }
    return singleton;
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

with方法內部通過Builder模式創建Picasso實例,這樣做的好處是簡潔清晰,通常在構造器參數很多的時候使用。
build方法會最終創建Picasso實例:

Picasso#Builder#build()

 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);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

此方法做了如下基本配置:

  1. 使用默認的緩存策略,內存緩存基於LruCache,磁盤緩存基於http緩存,HttpResponseCache
  2. 創建默認的下載器
  3. 創建默認的線程池(3個worker線程)
  4. 創建默認的Transformer,這個Transformer什麼事情也不幹,只負責轉發請求
  5. 創建默認的監控器(Stats),用於統計緩存命中率、下載時長等等
  6. 創建默認的處理器集合,即RequestHandlers.它們分別會處理不同的加載請求

處理器集合的初始化在Picasso的構造器中:
Picasso構造器

    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));
    allRequestHandlers.add(new AssetRequestHandler(context));
    allRequestHandlers.add(new FileRequestHandler(context));
    allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
    requestHandlers = Collections.unmodifiableList(allRequestHandlers);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

從命名就可以看出來,可以從網絡、file、assert、contactsphoto等地方加載圖片.

另,Picasso支持增加自己的處理器.

load()方法用於從不同地方加載圖片,比如網絡、resource、File等,該方法內部邏輯很簡單,只是創建了一個RequestCreator

Picasso#load()

 public RequestCreator load(Uri uri) {
    return new RequestCreator(this, uri, 0);
  }
  • 1
  • 2
  • 3

RequestCreator從名字就可以知道這是一個封裝請求的類,請求在Picasso中被抽象成RequestRequestCreator類提供了
諸如placeholdertagerrormemoryPolicynetworkPolicy等方法.
由於可配置項太多,所以Request也使用了Builder模式:
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);
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

那麼可想而知into方法一定會去將Request創建,並丟到線程池或者分發器中執行。into方法有多種重載,因爲Picasso不僅僅可以
將圖片加載到ImageView上,還可以加載到Target或者RemoteView上.
這裏選取imageView作爲分析對象,該方法代碼如下:

RequestCreator#into()

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()) {//檢查是否設置uri或者resID
      //如果沒有設置當然取消請求
      picasso.cancelRequest(target);
      if (setPlaceholder) {
        setPlaceholder(target, getPlaceholderDrawable());
      }
      return;
    }

    if (deferred) {//是否調用了fit(),如果是,代表需要將image調整爲ImageView的大小
      if (data.hasSize()) {//不能與resize一起用
        throw new IllegalStateException("Fit cannot be used with resize.");
      }
      //既然要適應ImageView,肯定需要拿到ImageView大小
      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);
    }

    //創建request
    Request request = createRequest(started);
    String requestKey = createKey(request);

    if (shouldReadFromMemoryCache(memoryPolicy)) {//是否需要在緩存裏面先查找
      Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
      if (bitmap != null) {//cache hit
        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;
      }
    }

    //緩存未命中,那就創建Action,將任務交給dispatcher
    if (setPlaceholder) {
      setPlaceholder(target, getPlaceholderDrawable());
    }

    Action action =
        new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
            errorDrawable, requestKey, tag, callback, noFade);

    picasso.enqueueAndSubmit(action);
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64

邏輯註釋寫的很清楚了,into方法會先從緩存裏面查找圖片,如果找不到的話,則會創建Action即一個加載任務,交給Dispatcher執行。
那我們就來看看picasso.enqueueAndSubmit方法做了什麼.
在這之前,先來看下Action是什麼鬼,爲什麼有了Request還要Action.

先看Request有哪些屬性:

  int id;
  long started;
  int networkPolicy;
  public final Uri uri;
  public final int resourceId;
  public final String stableKey;
  public final List<Transformation> transformations;
  public final int targetWidth;
  public final int targetHeight;
  public final boolean centerCrop;
  public final boolean centerInside;
  public final boolean onlyScaleDown;
  public final float rotationDegrees;
  public final float rotationPivotX;
  public final float rotationPivotY;
  public final boolean hasRotationPivot;
  public final Bitmap.Config config;
  public final Priority priority;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

再看Action的屬性:

  final Picasso picasso;
  final Request request;
  final WeakReference<T> target;
  final boolean noFade;
  final int memoryPolicy;
  final int networkPolicy;
  final int errorResId;
  final Drawable errorDrawable;
  final String key;
  final Object tag;
  boolean willReplay;
  boolean cancelled;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Request關注的是請求本身,比如請求的源、id、開始時間、圖片變換配置、優先級等等,而Action則代表的是一個加載任務,所以不僅需要
Request對象的引用,還需要Picasso實例,是否重試加載等等

Action有個需要關注的點,那就是WeakReference<T> target,它持有的是Target(比如ImageView..)的弱引用,這樣可以保證加載時間很長的情況下
也不會影響到Target的回收了.

好的,那回到剛纔的思路,我們開始分析picasso.enqueueAndSubmit方法:

picasso#enqueueAndSubmit()

  final Map<Object, Action> targetToAction;
   ...
  this.targetToAction = new WeakHashMap<Object, 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);
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

它會先從action任務上拿到對應target,也就是imageView,然後從weakHashMap中通過這個imageView索引到對應的action,如果
發現這個action跟傳進來的action不一樣的話,那就取消掉之前的加載任務。最後將當前加載任務提交.

跟進submit發現最終調用的是DispatcherdispatchSubmit(action)方法.這個Dispatcher即任務分發器,它是在
Picasso實例創建的時候初始化的.。

那我們在看dispatchSubmit方法之前,必然得了解下Dispatcher.

Picasso.Builder.build()

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

每一個Dispatcher都需要關聯線程池(service)、下載器(downloader)、主線程的Handler(HANDLER)、緩存(cache)、
監控器(stats).

這裏先看線程池,Picasso默認的線程池叫PicassoExecutorService,它繼承自ThreadPoolExecutor,默認線程數量爲
3.但是PicassoExecutorService的特性是可以根據網絡情況調整線程數量,wifi下是4個線程,而2g網只有一個線程。具體是
通過在Dispatcher中註冊了監聽網絡變化的廣播接收者。

另外,PicassoExecutorService中還有一個很重要的方法叫submit,它會去執行一個runnable.

好的,我們回到Dispatcher,這裏還需要關注的是Dispatcher中有個內部類叫DispatcherHandler,注意哦,
這個handler是Dispatcher自己的,而不是構造器傳進來的。而且,這個handler綁定的是子線程的Looper,爲什麼?請看:

Dispatcher#構造器

    this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
  • 1

dispatcherThread則是一個HandlerThread:

Dispatcher內部類

 static class DispatcherThread extends HandlerThread {
    DispatcherThread() {
      super(Utils.THREAD_PREFIX + DISPATCHER_THREAD_NAME, THREAD_PRIORITY_BACKGROUND);
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5

也就是說,這個handler的消息處理是在子線程進行的!這樣就可以避免阻塞主線程的消息隊列啦!

好的,再回到剛纔的問題,來看下dispatchSubmit方法(不知道大家有沒有看暈。。。。):

Dispatcher#dispatchSubmit

 void dispatchSubmit(Action action) {
    handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
  }
  • 1
  • 2
  • 3

不用看都知道會發消息給handler。而handler收到這個消息之後調用了這個方法:

 dispatcher.performSubmit(action);
  • 1

果斷跟進去:

Dispatcher#performSubmit

 void performSubmit(Action action, boolean dismissFailed) {//注意哦,這裏已經不在主線程了,而是在dispatcher線程(HandlerThread)
    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
    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());
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

首先創建了一個BitmapHunter,它繼承自Runnable,可以被線程池調用。然後判斷線程池有沒有關閉,如果沒有的話,
就會將這個bitmapHunter丟到線程池裏面,即調用剛纔說的submit方法。

我們先看下forRequest方法裏面幹了什麼:

BitmapHunter#forRequest

 static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,
      Action action) {
    Request request = action.getRequest();
    List<RequestHandler> requestHandlers = picasso.getRequestHandlers();

    // Index-based loop to avoid allocating an iterator.
    //noinspection ForLoopReplaceableByForEach
    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);
      }
    }
    //沒有人能處理這個請求,那麼交給ERRORING_HANDLER,它會直接拋異常
    return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

還記得大明湖畔的Picasso麼?在它的構造器中創建了若干RequestHandler,用於處理不同的加載請求,在這裏,它會遍歷
這些requestHandler,看誰可以處理當前請求,如果發現了,那就創建BitmapHandler,並把這個requestHandler傳進去,

線程池在收到BitmapHunter之後,會調用其run方法,那麼我們就來看下:

BitmapHunter#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);
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

核心邏輯是由hunt方法完成的,下面一堆catch語句分別捕捉不同的異常然後上報給dispatcher進行處理。
而hunt方法裏面肯定會調用RequestHandler的load方法:

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

    //緩存沒有命中的話,再調用requestHandler.load
    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
      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;
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

這裏假設是一個網絡請求,那麼最終NetworkRequestHandler會處理請求:

NetworkRequestHandler#load

@Override public Result load(Request request, int networkPolicy) throws IOException {

    //這個downloader也是Dispatcher創建的時候傳進來的
    Response response = downloader.load(request.uri, request.networkPolicy);
    if (response == null) {
      return null;
    }
    //判斷是從緩存還是網絡拿的
    Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;
    //從響應中拿到bitmap
    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) {
      stats.dispatchDownloadFinished(response.getContentLength());
    }
    //將結果封裝返回
    return new Result(is, loadedFrom);
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

現在我們關注下這個downloader的前世今生,如果用戶沒有自定義的話,那將使用默認downloader:

Picasso#Builder#build()

 downloader = Utils.createDefaultDownloader(context);
  • 1

Utils#createDefaultDownloader

static Downloader createDefaultDownloader(Context context) {
    try {
      Class.forName("com.squareup.okhttp.OkHttpClient");
      return OkHttpLoaderCreator.create(context);
    } catch (ClassNotFoundException ignored) {
    }
    return new UrlConnectionDownloader(context);
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

首先反射下,看有沒有依賴okhttp,如果依賴的話,那就使用OkHttpClient嘍,否則就使用默認的HttpUrlConnection了。
注:其實從4.4開始,okhttp已經作爲HttpUrlConnection的實現引擎了。

可以從picasso的pom文件裏面看到,okhttp是optional的:

 <dependency>
      <groupId>com.squareup.okhttp</groupId>
      <artifactId>okhttp</artifactId>
      <optional>true</optional>
    </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

UrlConnectionDownloader爲例,看下它的load方法:

  @Override public Response load(Uri uri, int networkPolicy) throws IOException {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
      installCacheIfNeeded(context);
    }

    HttpURLConnection connection = openConnection(uri);
    connection.setUseCaches(true);

    if (networkPolicy != 0) {
      String headerValue;

      if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
        headerValue = FORCE_CACHE;
      } else {
        StringBuilder builder = CACHE_HEADER_BUILDER.get();
        builder.setLength(0);

        if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
          builder.append("no-cache");
        }
        if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
          if (builder.length() > 0) {
            builder.append(',');
          }
          builder.append("no-store");
        }

        headerValue = builder.toString();
      }

      connection.setRequestProperty("Cache-Control", headerValue);
    }

    int responseCode = connection.getResponseCode();
    if (responseCode >= 300) {
      connection.disconnect();
      throw new ResponseException(responseCode + " " + connection.getResponseMessage(),
          networkPolicy, responseCode);
    }

    long contentLength = connection.getHeaderFieldInt("Content-Length", -1);
    boolean fromCache = parseResponseSourceHeader(connection.getHeaderField(RESPONSE_SOURCE));

    return new Response(connection.getInputStream(), fromCache, contentLength);
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

注意哦,Disk Cache功能是在這裏做掉的,它基於Http語義來判斷是否緩存.
另,返回的是inputStream流,而不是Bitmap對象.

好的,現在我們回到BitmapHunter#run(),它在拿到結果後會將結果交給dispatcher

BitmapHunter#run()

 if (result == null) {
        dispatcher.dispatchFailed(this);
      } else {
        dispatcher.dispatchComplete(this);
      }
  • 1
  • 2
  • 3
  • 4
  • 5

我們看dispatcher.dispatchComplete(this),它會把消息發給自己內部的handler,也就是剛纔說的Looper在子線程
的handler
,handler將做如下處理:

  BitmapHunter hunter = (BitmapHunter) msg.obj;
  dispatcher.performComplete(hunter);
  • 1
  • 2

注意哦,BitmapHunter會持有網絡請求回來的Bitmap引用.來看下performComplete:

Dispatcher#performComplete

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");
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

首先會根據事先設置的緩存策略決定是否將結果加到內存緩存。然後調用batch方法,從名字就可以知道,這個方法會把結果暫存,
然後批量處理(等待200ms),這樣做也是爲了防止短時間大量任務阻塞消息隊列。到時間後,就會執行performBatchComplete,
此方法會將這個批次的所有結果一次性發給主線程的Handler,也就是Picasso中定義的Handler:

Dispatcher#performBatchComplete

  void performBatchComplete() {
    List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);
    batch.clear();
    mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
    logBatch(copy);
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

主線程收到消息後會進行處理:

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);
          }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

對batch中每個BitmapHunter調用complete方法,而complete方法會調用deliverAction方法,最終其實調用的是具體
action的complete方法,如果是ImageView的話,那就是ImageViewActioncomplete方法:

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();
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

注意看這一句,ImageView target = this.target.get(),因爲targetImageView的弱引用,在下載過程中,
ImageView可能已經被銷燬了,所以這裏要做下判斷。

如果沒有被回收,那麼圖片最終通過PicassoDrawable.setBitmap()方法被設置到ImageView上.
這個PicassoDrawable提供了fade動畫.

好了,分析基本完畢,下面是整個流程的時序圖.

時序圖

簡單總結下當我們執行Picasso.with(context).load(url).into(imageview)時,首先會構造Picasso實例,然後會
根據url創建請求,然後請求會被交給Dispatcher,Dispatcher將在子線程對請求任務進行調度,將請求任務交給線程池
執行,執行完畢後,將結果傳給主線程的handler,最後在主線程中將圖片設置到ImageView上.

下面是我在測試的時候截的圖,注意每張圖所在線程都不一樣哦:

1.主線程中調用dispatchSubmit

主線程

2.Dispatcher線程對請求任務進行調度

dispatcher線程

3.線程池中執行請求任務

線程池

其他需要關注的點

  • 關於緩存策略

    Picasso的緩存是內存緩存+磁盤緩存,內存緩存基於LruCache類,可配置替換。磁盤緩存依賴於http緩存,不可配置。
    先看內存緩存.內存緩存比較簡單,是通過LinkedHashMap實現.
    讀緩存時機:生成了請求Request對象,準備創建Action加載任務之前,會先去緩存裏面查找下.

    RequestCreator#into

    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;
          }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    寫緩存時機:圖片從網絡或者其他地方加載成功後,即在BitmapHunter的run方法執行結束的時候.
    Dispatcher#performComplete

    
     if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
          cache.set(hunter.getKey(), hunter.getResult());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意哦,緩存的是經過壓縮之後的圖片(如果你使用了fit或者resize方法的話),
    再看磁盤緩存。
    如果你是使用UrlConnectionDownloader的話,那很不幸,緩存只在Api>14上生效,因爲緩存依賴於HttpResponseCache.
    如果你依賴了okhttp,那麼緩存策略始終是有效的。另外需要說明的是,既然是http緩存,那麼緩存的可用性依賴於http響應是
    否允許緩存,也就是說得看響應中是否攜帶Cache-ControlExpires等字段.對於這塊不瞭解的話,可以參考我的這篇文章:
    HttpCache in android
    還有一點,緩存的路徑是 應用cache目錄/picasso-cache 文件夾.具體代碼參考Utils.createDefaultCacheDir方法

  • 關於預加載

    首先要注意的是Callback是一個強引用,如果你使用帶Callback的重載形式的話,只有當Request結束的時候纔會釋放
    引用,在此期間你的Activity/Fragment等組件引用不會被釋放.因此你需要注意內存泄露的情形.

    怎麼實現?很簡單拉,調fetch的時候創建了FetchAction,然後其他流程上面描述的一樣,最終在Dispatcher.performComplete
    的時候將結果寫入內存緩存,結果回傳到主線程的時候,調用了FetchActioncomplete方法,這裏面不對Bitmap
    任何處理就行拉:

    FetchAction#complete

    ```
     @Override void complete(Bitmap result, Picasso.LoadedFrom from) {
           if (callback != null) {
             callback.onSuccess();
           }
         }
    ```
    
  • 關於圖形變換

    圖形變換在Picasso中被抽象成Transformation接口,具體的變換操作由transform方法實現.Request維護一個
    圖形變換的列表List<Transformation>,當圖片加載成功後,BitmapHunter中將會遍歷這個變換集合,依次進行變換,
    最後返回變換後的bitmap.恩,其實是一個回調的思想,將操作封裝到接口中交給系統,系統在某個特定時機調用你的接口。

    具體代碼:

    BitmapHunter#applyCustomTransformations

       ```
       static Bitmap applyCustomTransformations(List<Transformation> transformations, Bitmap result) {
                 for (int i = 0, count = transformations.size(); i < count; i++) {
                   final Transformation transformation = transformations.get(i);
                   Bitmap newResult;
                   try {
                     newResult = transformation.transform(result);
                   } catch (final RuntimeException e) {
                     Picasso.HANDLER.post(new Runnable() {
                       @Override public void run() {
                         throw new RuntimeException(
                             "Transformation " + transformation.key() + " crashed with exception.", e);
                       }
                     });
                     return null;
                   }
                   ....
                   result = newResult;
                 }
                 return result;
               } 
    
       ```
    
  • 關於CleanupThread

    Picasso類中有一個內部線程叫CleanupThread,這是一個daemon線程,它的工作是找到那些Target(比如說ImageView)已經被回收
    但是所對應的Request請求還在繼續的任務(Action),找到之後,會取消對應的請求,避免資源浪費.

    看下代碼:

    Picasso#CleanupThread

     private static class CleanupThread extends Thread {
        private final ReferenceQueue<Object> referenceQueue;
        private final Handler handler;
    
        CleanupThread(ReferenceQueue<Object> referenceQueue, Handler handler) {//關聯主線程的handler,refreenceQueue
          this.referenceQueue = referenceQueue;
          this.handler = handler;
          setDaemon(true);
          setName(THREAD_PREFIX + "refQueue");
        }
    
        @Override public void run() {
          Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
          while (true) {
            try {
              // Prior to Android 5.0, even when there is no local variable, the result from
              // remove() & obtainMessage() is kept as a stack local variable.
              // We're forcing this reference to be cleared and replaced by looping every second
              // when there is nothing to do.
              // This behavior has been tested and reproduced with heap dumps.
              RequestWeakReference<?> remove =
                  (RequestWeakReference<?>) referenceQueue.remove(THREAD_LEAK_CLEANING_MS);
              Message message = handler.obtainMessage();
              if (remove != null) {
                message.what = REQUEST_GCED;
                message.obj = remove.action;
                handler.sendMessage(message);
              } else {
                message.recycle();
              }
            } catch (InterruptedException e) {
              break;
            } catch (final Exception e) {
              handler.post(new Runnable() {
                @Override public void run() {
                  throw new RuntimeException(e);
                }
              });
              break;
            }
          }
        }
    
        void shutdown() {
          interrupt();
        }
      }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    可以看到它會不斷輪詢ReferenceQueue,找到這樣的reference,就交給handler,handler會從reference中拿到action,
    並取消請求.

     case REQUEST_GCED: {
              Action action = (Action) msg.obj;
              if (action.getPicasso().loggingEnabled) {
                log(OWNER_MAIN, VERB_CANCELED, action.request.logId(), "target got garbage collected");
              }
              action.picasso.cancelExistingRequest(action.getTarget());
              break;
            }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    那麼這個ReferenceQueue又是如何關聯Action的呢?這個可以從Action的構造器中拿到答案:

     this.target =//RequestWeakReference是WeakReference的子類
            target == null ? null : new RequestWeakReference<T>(this/*即Action本身*/, target, picasso.referenceQueue);
    • 1
    • 2

    可以看到兩點:

    1. 每個Action都會關聯Picasso中唯一的referenceQueue實例;
    2. 每個RequestWeakReference都會同時關聯TargetAction.
  • resume/pause

    1. pause

      流程如下。

      這裏寫圖片描述

      可能會有疑問的地方在於Dispatcher#performPauseTag中遍歷所有的hunter,都會調一次cancel,這似乎會取消所有
      的請求。但其實不是這樣的,可以看下BitmapHunter#cancel方法的代碼:

       boolean cancel() {
         return action == null
             && (actions == null || actions.isEmpty())
             && future != null
             && future.cancel(false);
       }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    注意到它會判斷action是否爲空,如果不爲空就不會取消了。而在Dispatcher#performPauseTag中會把tag匹配的
    action與對應的BitmapHunter解綁(detach),讓BitmapHunter的action爲空.所以這並不影響其他任務的執行。

    1. resume 流程如下。

      這裏寫圖片描述

      其實就是遍歷pausedActions,挨個重新交給dispatcher分發。

    作者的提交記錄: https://github.com/square/picasso/pull/665/files#diff-f11286bbae6959a7a5dd74bf99276f1aR229

  • 圖片壓縮

    圖片壓縮的原理通常都是利用BitmapFactory#Options類,先將injustDecodeBounds設置爲true,對Bitmap進行一次
    解碼,拿到outWidth/outHeight,即實際寬高,然後根據期望壓縮到的寬和高算出inSampleSize,最後將injustDecodeBounds設置爲false,
    再對Bitmap進行一次解碼即可。另一種壓縮的方法是設置圖片的顯示效果,比如ARGB_8888等等.Picasso綜合了利用這兩種方案.

    這裏寫圖片描述

    詳細代碼參考BitmapHunter#decodeStreamRequestHandler#createBitmapOptionsRequestHandler#calculateInSampleSize
    這三個方法,有個需要注意的地方,只有當設置圖片的寬高時(調用了fit或者resize)纔會計算smpleSize進行壓縮。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章