Picasso源碼淺析

<img src="http://upload-images.jianshu.io/upload_images/599109-ff6e7d6a4f6baeb1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="Picasso結構圖" width="200" />

幾年前開始接觸Android的時候问顷,比較麻煩的就是圖片加載和顯示,雖然說本身沒什么難度禀梳,但是你得關心網(wǎng)絡,緩存肠骆,流量算途,內存泄漏等等,正在這個時候蚀腿,Picasso出現(xiàn)了嘴瓤,名字很有藝術感有木有扫外,它的出現(xiàn),讓我突然間意識到所謂的面向對象中封裝的重要性和牛逼之處廓脆,簡簡單單的接口筛谚,不用關心內部實現(xiàn),那么停忿,用了這么久驾讲,總得了解了解它的實現(xiàn)吧,雖然不用重復的造輪子席赂,但是你得知道輪子是怎么轉起來呀??????

Picasso.java

首先吮铭,我們看看Picasso的基本用法,從它的使用方法入手颅停,一步一步的逼近真相谓晌。

Picasso
    .with(context)
    .load("http://i.imgur.com/DvpvklR.png")
    .into(imageView);

WTF,這么簡單癞揉?這尼瑪離真相還有十萬八千九百公里呢纸肉,請注意,是公里不是里喊熟,但是呢柏肪,F(xiàn)uck歸Fuck,前面的路還是要自己走啊小同志逊移,咳咳预吆。

1.Picasso.with()

這個就是傳說中的單例模式,我們先看看Picasso.with(context)胳泉,這個方法干啥了:

  // Picasso是一個全局靜態(tài)屬性拐叉,
  static volatile Picasso singleton = null;
  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;
  }

先假裝解釋一波這個volatile,注意扇商,我要開始裝逼了??凤瘦,我們都知道,在多線程訪問同一個變量的時候案铺,都是將這個變量拷貝到自己的線程蔬芥,在使用和修改以后,再賦值給原來的線程控汉,這個過程由于不是一步完成的笔诵,不是原子操作,如果多個線程同時訪問這個變量的時候姑子,可能會導致最終的數(shù)值不是我們期望的乎婿,而volatile修飾的成員變量在每次被線程訪問的時候,都強迫從共享內存中重讀該成員變量街佑,而在變量發(fā)生變化時谢翎,強迫線程將變化值回寫到共享內存捍靠,這樣就能最大程度的保證兩個不同的線程看到的成員變量是同一個值,這里要注意的一點是森逮,盡管它最大程度的保證了數(shù)據(jù)的一致性榨婆,但是由于其任然不是原子操作,也沒有使用鎖的機制褒侧,所以在某些情況下良风,還是可能會出現(xiàn)錯誤的...

“停!A选拖吼!” -- 導演喊道

“導員,再讓我說兩句罢馕恰吊档?” -- 我

“再不停,今晚雞腿??沒了” -- 導演

“/(ㄒoㄒ)/~~”

那好吧唾糯,裝逼結束怠硼,如果大家想進一步了解,請移步這里移怯,Java并發(fā)編程:volatile關鍵字解析香璃,看看這位大神給你完美的解惑。

然后我們看看為什么with方法要這么寫舟误?

首先葡秒,這是個單例,老以前嵌溢,我會這么寫單例(錯誤示范):

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

在單線程沒啥問題眯牧,但是如果在多線程,這代碼就問題大大滴有赖草,它會導致重復創(chuàng)建多個Picasso学少,額,那你會說秧骑,那改進一下吧版确,加一個鎖:

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

請先容老夫吐槽一哈哈,這個sync...synx...syndkfddf... f**k 這玩意怎么寫啊乎折,這么長绒疗,又沒有規(guī)律,你設計java的人有沒有考慮我們這種從小寫象形文字人的感受骂澄,這玩意讓人怎么記忌堂,你直接用sync會死啊??

言歸正傳,這樣不就“完美”解決了多線程訪問的問題了酗洒,是嗎士修?是解決了,但是不夠完美樱衷,單例只需要被初始化一次棋嘲,而因為同步鎖對系統(tǒng)的開銷是比較大的,我們獲取單例的使用頻率那是相當?shù)母咄劬毓穑热缥矣肞icasso沸移,有一千張圖片需要顯示,那豈不是要鎖一千次侄榴?這雹锣?...I don't like this

好吧,那我們作為有代碼潔癖+強迫癥的程序員癞蚕,怎么能接收這樣的事情呢蕊爵?于是乎繼續(xù)改進:

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

OK,現(xiàn)在我們用到了帥氣的DoubleCheck桦山,總該滿意了吧攒射?這可是經典的單例實現(xiàn)方式啊,The answer is no. 啥恒水? 還 no会放? O__O "… why?

然而,國外的大神還真的說這玩意在Java中不適用钉凌,為啥呢咧最?且聽我細細道來。

首先御雕,我們假設有兩個線程同時調用了with方法

  • Thread-1.with()

  • Thread-2.with()

  • 此時矢沿,他們都發(fā)現(xiàn)singleton == null這個慘痛的事實,于是乎饮笛,Thread-1首先獲得鑰匙咨察,進入了同步鎖,而且判斷if(singleton ==null)還是個事實福青,所以它就執(zhí)行singleton = new Builder(context).build()摄狱,沒毛病老鐵,然后它把這個值賦值給主內存无午,

  • 接著Thread-2進入這個同步鎖媒役,也判斷if(singleton ==null)發(fā)現(xiàn)這還是個慘痛的事實

  • “為啥呢?”

  • “誰問的宪迟?前面唾沫星子噴了這么多白說了酣衷?”

  • 因為Thread-1和Thread-2中的singleton都是主內存中的拷貝,雖然Thread-1將內存賦值給了主內存穿仪,但是在Thread-2中只锻,這個singleton還是null啊笤昨,扎心吧老鐵

  • 所以這個時候Thread還是會執(zhí)行singleton = new Builder(context).build(),這個時候就會重復的創(chuàng)建單例捺僻,這么說來陵像,這個做法還不如上面那個慢點的呢,雖然人家慢,但是好歹不會出問題是吧

艾亞馬,太扎心了挺庞,這尼瑪,放棄嗎然走?還是用回上面那種方法嗎?

No! 一定還有辦法解決這個問題,正在你抓耳撓腮的時候码泛,一道閃光從腦中閃過齐莲,上面提到的volatile不就剛好解決這個問題嗎?這樣我每次判斷if(singleton == null)都從主內存拷貝出來呜叫,簡直就是量體裁衣凯砍,啊呸惠昔,量身定做的啊,于是乎钳榨,我們繼續(xù)改進為如下代碼:

  static volatile Picasso singleton = null;
  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;
  }

這不就是人家Picasso源碼中的寫法嗎...

2.Picasso.load()

還是老規(guī)矩舰罚,先看看這個fucking code:

  public RequestCreator load(@Nullable String path) {
    if (path == null) {
      return new RequestCreator(this, null, 0);
    }
    if (path.trim().length() == 0) {
      throw new IllegalArgumentException("Path must not be empty.");
    }
    return load(Uri.parse(path));
  }

這里面首先做了一些判斷,path是否為null薛耻,path是否為""营罢,如果是空字符串就給我崩潰,這里要小說一下,好多人總是習慣在可能發(fā)生問題的地方用try catch的方式包裹起來饲漾,尤其是寫library的時候蝙搔,其實這樣是很不好的寫法,發(fā)現(xiàn)問題考传,我們就要把它暴露出來吃型,讓它暴露在陽光下,告訴使用我們的人僚楞,我這里崩潰了勤晚,怎么樣?我驕傲了嗎泉褐?如果你喜歡把它try catch起來赐写,很容易把問題掩蓋起來。

好了膜赃,繼續(xù)往前走挺邀,不回頭

  /**
   * Start an image request using the specified URI.
   * <p>
   * Passing {@code null} as a {@code uri} will not trigger any request but will set a placeholder,
   * if one is specified.
   *
   * @see #load(File)
   * @see #load(String)
   * @see #load(int)
   */
  public RequestCreator load(@Nullable Uri uri) {
    return new RequestCreator(this, uri, 0);
  }

看看,它最后還是會創(chuàng)建一個叫RequestCreator類跳座,程序員起名字還是比較實在端铛,一般叫啥就是干啥的,不像人名疲眷,一點都不誠實禾蚕,比如名字帶 “帥”的人,不一定帥咪橙,像我名字里帶 “飛” 夕膀,過山車我都不敢坐...??

所以,我們還是去看看這個類吧:

public class RequestCreator {
  private static final AtomicInteger nextId = new AtomicInteger();

  private final Picasso picasso;
  // Request.Builder里面持有一些參數(shù)的配置
  private final Request.Builder data;
  // 底下是一些配置的屬性
  // 是否平緩的過度
  private boolean noFade;
  private boolean deferred;
  // 是否設置過占位圖
  private boolean setPlaceholder = true;
  // 占位圖ID
  private int placeholderResId;
  // 加載錯誤要顯示的圖片
  private int errorResId;
  // 內存
  private int memoryPolicy;
  // 網(wǎng)絡
  private int networkPolicy;
  // 占位圖Drawable
  private Drawable placeholderDrawable;
  // 錯誤圖片Drawable
  private Drawable errorDrawable;
  // 用戶設置的一個Tag美侦,可以存放任何東西
  private Object tag;
  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);
  }
}

我們看它的屬性产舞,大概就能知道它能干什么,如菠剩,它可以設置占位圖易猫,加載錯誤的占位圖,是否顯示圖片切換時候的過度動畫等等具壮,所以准颓,它在Picasso從加載圖片到顯示的過程中發(fā)揮的作用,應該是準備階段棺妓,同時給ImageView設置一個加載中的占位圖攘已,那,同時呢怜跑,它還持有一個叫Request.Builder的東西样勃,這位兄弟看名字就知道是一個使用Builder模式的類吠勘,里面肯定也有一大堆配置的屬性,看看吧:

  public static final class Builder {
    private Uri uri;
    private int resourceId;
    private String stableKey;
    private int targetWidth;
    private int targetHeight;
    private boolean centerCrop;
    private int centerCropGravity;
    private boolean centerInside;
    private boolean onlyScaleDown;
    private float rotationDegrees;
    private float rotationPivotX;
    private float rotationPivotY;
    private boolean hasRotationPivot;
    private boolean purgeable;
    private List<Transformation> transformations;
    private Bitmap.Config config;
    private Priority priority;

果然不出老夫所料峡眶,這都是些密密麻麻的配置剧防,老夫就不細說了,圖片相關的配置辫樱,底下肯定跟一堆Setter方法峭拘,至于Builder模式的好處,看看這位童鞋說的吧狮暑,我也懶得講:設計模式之Builder模式

我們繼以RequestCreator里面的一個方法作為一個示例鸡挠,瞅瞅它是怎么做的:

  /** Resize the image to the specified size in pixels. */
  public RequestCreator resize(int targetWidth, int targetHeight) {
    data.resize(targetWidth, targetHeight);
    return this;
  }

假如我想將這種圖片的大小限制為我指定的大小,原圖是1024*1024心例,但是在不同的設備上宵凌,其實根本不需要這么大的圖片,而且會很吃內存止后,所以我要根據(jù)設備實際的大小裁剪圖片,于是我們用到resize()方法溜腐,那我們看译株,其實它是先將這個信息保存到我們前面講到Request.Builder中。

所以講了這么多挺益,都還是在做準備工作歉糜,所有的信息雖然我們都給了Picasso(額,其實很多信息我們都沒有給Picasso望众,比如圖片裁剪方式)匪补,但是它就是沒有發(fā)起網(wǎng)絡請求,仍然只是將信息保存到RequestCreater或是Request.Builder中烂翰,也就是說夯缺,load()方法只是準備工作,而真正的網(wǎng)絡請求是接下來的這個方法觸發(fā)的甘耿。

3.Picasso.into()

Picasso.into() 有8個重載方法踊兜,但是萬變不離其宗,找一個最基本的看看它嘎哈了:

  public void into(ImageView target, Callback callback) {
    long started = System.nanoTime();
    // 檢查一下是否在主線程
    checkMain();

    // ... 這里刪掉一些檢查的代碼
    
    // 是否延期執(zhí)行了佳恬,延期是為了保證ImageView已經布局完成捏境,
    // 既已經有大小了,如果這個狀態(tài)為true毁葱,則Picasso自動將
    // 圖片裁剪為匹配目標大小
    if (deferred) {
      // 如果設置了匹配ImageView大小垫言,則用戶不能自定義大小
      if (data.hasSize()) {
        throw new IllegalStateException("Fit cannot be used with resize.");
      }
      int width = target.getWidth();
      int height = target.getHeight();
      // 如果ImageView寬高有一個為0,則延期執(zhí)行這個方法
      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);
    }
    // 這里調用createRequest()將我們上面提到的那個data
    // 轉換為Request倾剿,其實就是調用了一下build()
    Request request = createRequest(started);
    String requestKey = createKey(request);
    
    // 檢查一下用戶配置筷频,是否從內存中讀取Bitmap
    // 既如果用戶配置了Picasso支持緩存到內存,則
    // 先從內存中讀取數(shù)據(jù),如果內存中讀取到數(shù)據(jù)截驮,則
    // 直接將該Bitmap設置到ImageView并提示加載成功
    // 返回
    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;
      }
    }
    // 如果內存中沒有數(shù)據(jù)笑陈,接著往下走
    // 設置占位圖
    if (setPlaceholder) {
      setPlaceholder(target, getPlaceholderDrawable());
    }

    // 構建一個ImageViewAction,這樣的Action在Picasso中
    // 有多個葵袭,都是繼承自Action涵妥,Action是一個抽象類,它定義
    // 了一些通用的功能坡锡,而需要差異化實現(xiàn)的就有繼承它的類去實現(xiàn)
    // 差異化的操作主要在complete()和error()兩個方法
    // 在此處ImageViewAction需要將做種獲取到的Bitmap顯示
    // 到ImageView上蓬网,而TargetAction則會將獲取到的Bitmap
    // 傳遞出去,這就是繼承的巧妙之處鹉勒,所以別再傻乎乎的為了
    // 復用而繼承了
    Action action =
        new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
            errorDrawable, requestKey, tag, callback, noFade);
    picasso.enqueueAndSubmit(action);
  }

對上面的代碼帆锋,我又一個小小的疑惑,照理來說禽额,Picasso應該有三級緩存呀锯厢,既:內存,本地脯倒,網(wǎng)絡实辑,為啥現(xiàn)在只看到檢查內存,然后就沒有了呢藻丢?這個我們再最后會講到剪撬,我們還是繼續(xù)往下看看

Picasso.enqueueAndSubmit()

  void enqueueAndSubmit(Action action) {
    Object target = action.getTarget();
    // 先還是檢查一下這個對象當前有沒有正在執(zhí)行的Action
    // 如果有的話,先取消一下悠反,然后再把即將進行的請求存起來
    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);
  }
  
  // submit很簡單残黑,通過一個叫dispatcher的東西傳遞出去
  // dispatch這個單詞在觸摸時間那塊有見到過,分發(fā)事件的意思
  // 難道這里也是分發(fā)嗎斋否?
  void submit(Action action) {
    dispatcher.dispatchSubmit(action);
  }

4.Dispatcher

上面的代碼很簡單梨水,唯一比較有意思的應該就是這個dispatcher的東西,看起來如叼,是由它來處理我們的Action冰木,那我們還是看看這個Dispatcher吧

class Dispatcher {
  final DispatcherThread dispatcherThread;
  final Handler handler;
  Dispatcher(...) {
    ...
    // 這是個HandlerThread
    this.dispatcherThread = new DispatcherThread();
    // 這個Handler是一個自定義的Handler
    this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
    // 這個Handler是由Picasso傳遞進來的Handler
    this.mainThreadHandler = mainThreadHandler;
    ...
  }
  
  void dispatchSubmit(Action action) {
    handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
  }
}

我在上面省去很多代碼,我們只看關鍵部分笼恰,

  • 首先踊沸,為啥被修飾為final的屬性可以被重新賦值?額社证,查了查資料逼龟,原來是我讀書少,final的屬性如果沒有被初始化過追葡,可以在構造方法或者代碼塊中初始化一次腺律。

  • 其次奕短,這塊代碼用到了HandlerThread和Handler配合使用。我們都知道Handler被用于線程間通信匀钧,而HandlerThread很顯然是一個異步的線程翎碑,那么就是說,HandlerThread這個線程通過Handler來接收我們的消息之斯,然后在它的線程處理日杈,具體HandlerThread的好處是它可以一直在異步線程接收Handler發(fā)送的消息,而它的用法佑刷,可以看看別人寫好的分析莉擒,我在網(wǎng)上隨便找了一篇,詳解 Android 中的 HandlerThread - 技術小黑屋瘫絮,這種東西一搜一大堆涨冀,當然,如果你能讀懂英文的話麦萤,建議讀官方的介紹文檔是極好的鹿鳖。

  • 那我們看看dispatchSubmit()方法正是發(fā)送一個REQUEST_SUBMIT消息到DispatcherHandler的handleMessage()方法中,注意壮莹,這個handleMessage()所在的線程就是DispatcherThread的線程

  private static class DispatcherHandler extends Handler {
    private final Dispatcher dispatcher;
    @Override public void handleMessage(final Message msg) {
      switch (msg.what) {
        ...
        case REQUEST_SUBMIT: {
          Action action = (Action) msg.obj;
          dispatcher.performSubmit(action);
          break;
        }
        ...
    }
  }

從上面可以看出栓辜,Handler在接收到REQUEST_SUBMIT消息后,執(zhí)行了performSubmit()

  void performSubmit(Action action, boolean dismissFailed) {
    ...
    // BitmapHunter繼承自Runnable
    // 先在hunterMap中檢查是否存在這個BitmapHunter
    BitmapHunter hunter = hunterMap.get(action.getKey());
    if (hunter != null) {
        // 如果存在垛孔,只需將傳入的action替換掉即可
      hunter.attach(action);
      return;
    }
    ...
    // 創(chuàng)建一個BitmapHunter
    hunter = forRequest(action.getPicasso(), this, cache, stats, action);
    // 通過ExecutorService線程池執(zhí)行BitmapHunter
    // 注意,這里沒有調用future.get()施敢,所以它還是異步的
    hunter.future = service.submit(hunter);
    // 緩存hunter
    hunterMap.put(action.getKey(), hunter);
    ...
  }

5.BitmapHunter

一步步走帶這里真不容易周荐,上面一串代碼用到了一個很關鍵的類,那就是BitmapHunter僵娃,去看看吧

  // 首先概作,forRequest是一個靜態(tài)方法,它返回一個BitmapHunter
  static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,
      Action action) {
    Request request = action.getRequest();
    // 這個RequestHandler也是一個基類默怨,實現(xiàn)它的類有:
    // AssetRequestHandler,FileRequestHandler,
    // MediaStoreRequestHandler,NetworkRequestHandler
    // 等等讯榕,有很多,單從名字我們猜測可能是具體的獲取數(shù)據(jù)的類
    // FileRequestHandler是從文件系統(tǒng)獲取匙睹,
    // NetworkRequestHandler是從網(wǎng)絡獲取
    List<RequestHandler> requestHandlers = picasso.getRequestHandlers();

    // for循環(huán)檢查所有的RequestHandler愚屁,直到找到
    // 支持解析(canHandleRequest()) request 的RequestHandler
    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);
      }
    }
    // 如果沒有找到,構造一個默認的BitmapHunter
    return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
  }

其中我們找到初始化所有RequestHandler的地方痕檬,在Picasso的構造方法里霎槐,如下,自己體會

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

RequestHandler的具體實現(xiàn)我們后面再講梦谜,繼續(xù)看BitmapHunter丘跌,由于它是繼承自Runnable袭景,所有肯定有一個run()方法,我們看看它在新的線程中都干了些啥:

  @Override public void run() {
    try {
      ...
      // 調用hunt()方法闭树,并判斷返回值
      result = hunt();
      // 根據(jù)返回值處理回調
      if (result == null) {
        dispatcher.dispatchFailed(this);
      } else {
        dispatcher.dispatchComplete(this);
      }
    }  catch (Exception e) {
      exception = e;
      dispatcher.dispatchFailed(this);
    }
    ...
  }

OK耸棒,那看來一切的處理都被放在hunt()里面了,這里為什么要這么寫呢报辱?完全可以把hunt()中的方法放在run()里面寫呀与殃?我認為有兩點原因:

  1. 首先,如果把hunt()中的實現(xiàn)都放在這里會導致這個方法非常的大捏肢,而且又加了try catch奈籽,會導致代碼讀起來很費勁
  2. 其次,也是最主要的鸵赫,我覺得是為了讓一個方法盡可能的只干一件事件衣屏,方便單元測試
  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;
      }
    }
    
    networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
    // 開始調用對應的RequestHandler加載圖片
    RequestHandler.Result result = requestHandler.load(data, networkPolicy);
    if (result != null) {
      loadedFrom = result.getLoadedFrom();
      // 獲取圖片的擴展信息
      exifOrientation = result.getExifOrientation();
      bitmap = result.getBitmap();

      // If there was no Bitmap then we need to decode it from the stream.
      if (bitmap == null) {
        Source source = result.getSource();
        try {
          bitmap = decodeStream(source, data);
        } finally {
          try {
            //noinspection ConstantConditions If bitmap is null then source is guranteed non-null.
            source.close();
          } catch (IOException ignored) {
          }
        }
      }
    }

    // stats是一個用來統(tǒng)計的類,我們先不管它
    if (bitmap != null) {
      if (picasso.loggingEnabled) {
        log(OWNER_HUNTER, VERB_DECODED, data.logId());
      }
      stats.dispatchBitmapDecoded(bitmap);
      if (data.needsTransformation() || exifOrientation != 0) {
        // 這里加了一個同步鎖辩棒,保證同時只能有一個線程處理Bitmap
        // 最大限度避免了OOM狼忱,同時也降低了對手機性能的影響
        synchronized (DECODE_LOCK) {
          if (data.needsMatrixTransform() || exifOrientation != 0) {
          // 有時候可能需要旋轉或者位移一下圖片
            bitmap = transformResult(data, bitmap, exifOrientation);
            if (picasso.loggingEnabled) {
              log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());
            }
          }
          // 是否有自定義的圖片轉換,比如裁成圓角矩形呀一睁,
          // 這個需要用戶自定義的钻弄,使用起來也很簡單
          // 傳入一個原始的Bitmap,返回一個新的Bitmap
          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;
  }

6.RequestHandler

現(xiàn)在者吁,我們在回過頭來看看RequestHandler窘俺,我們以NetworkRequestHandler為例:

class NetworkRequestHandler extends RequestHandler {
  private static final String SCHEME_HTTP = "http";
  private static final String SCHEME_HTTPS = "https";
  
  @Override public boolean canHandleRequest(Request data) {
    String scheme = data.uri.getScheme();
    return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
  }

  @Override public Result load(Request request, int networkPolicy) throws IOException {
    // 使用OKHttp加載數(shù)據(jù)
    okhttp3.Request downloaderRequest = createRequest(request, networkPolicy);
    Response response = downloader.load(downloaderRequest);
    ResponseBody body = response.body();

    if (!response.isSuccessful()) {
      body.close();
      throw new ResponseException(response.code(), request.networkPolicy);
    }

    // 檢查數(shù)據(jù)來源為網(wǎng)絡還是本地存儲
    Picasso.LoadedFrom loadedFrom = response.cacheResponse() == null ? NETWORK : DISK;
    return new Result(body.source(), loadedFrom);
  }
}

前面我們提到為什么Picasso只有內存和網(wǎng)絡兩種緩存,從這里复凳,我們可以看出瘤泪,其實它也是有本地緩存的,只不過它使用了OkHttp進行本地緩存育八,所有不需要自己再寫一次了对途,這樣做雖然方便,但是如果需要更換一個網(wǎng)絡加載庫的時候髓棋,如果網(wǎng)絡庫不支持緩存实檀,則需要自己手動寫一個了。

總結

通過分析Picasso的源碼呢按声,我們能深入的了解Picasso的工作原理以及它的一些代碼技巧:

  • 單例的正確姿勢
  • volatile 關鍵字的原理和使用場景
  • HandlerThread的使用
  • 線程池ExecutorService的使用以及搭配synchronized關鍵字限制線程消耗
  • 合理的封裝方式
  • 弱引用和緩存的使用
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末膳犹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子儒喊,更是在濱河造成了極大的恐慌镣奋,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怀愧,死亡現(xiàn)場離奇詭異侨颈,居然都是意外死亡余赢,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門哈垢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來妻柒,“玉大人,你說我怎么就攤上這事耘分【偎” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵求泰,是天一觀的道長央渣。 經常有香客問我,道長渴频,這世上最難降的妖魔是什么芽丹? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮卜朗,結果婚禮上拔第,老公的妹妹穿的比我還像新娘。我一直安慰自己场钉,他們只是感情好蚊俺,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著逛万,像睡著了一般泳猬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宇植,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天暂殖,我揣著相機與錄音,去河邊找鬼当纱。 笑死,一個胖子當著我的面吹牛踩窖,可吹牛的內容都是我干的坡氯。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼洋腮,長吁一口氣:“原來是場噩夢啊……” “哼箫柳!你這毒婦竟也來了?” 一聲冷哼從身側響起啥供,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤悯恍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后伙狐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涮毫,經...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡瞬欧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了罢防。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片艘虎。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖咒吐,靈堂內的尸體忽然破棺而出野建,到底是詐尸還是另有隱情,我是刑警寧澤恬叹,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布晚岭,位于F島的核電站,受9級特大地震影響调炬,放射性物質發(fā)生泄漏派草。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一绪励、第九天 我趴在偏房一處隱蔽的房頂上張望肿孵。 院中可真熱鬧,春花似錦疏魏、人聲如沸停做。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蛉腌。三九已至,卻和暖如春只厘,著一層夾襖步出監(jiān)牢的瞬間烙丛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工羔味, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留河咽,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓赋元,卻偏偏與公主長得像忘蟹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子搁凸,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內容

  • 從三月份找實習到現(xiàn)在媚值,面了一些公司,掛了不少护糖,但最終還是拿到小米褥芒、百度、阿里嫡良、京東锰扶、新浪献酗、CVTE、樂視家的研發(fā)崗...
    時芥藍閱讀 42,192評論 11 349
  • 1. Java基礎部分 基礎部分的順序:基本語法少辣,類相關的語法凌摄,內部類的語法,繼承相關的語法漓帅,異常的語法锨亏,線程的語...
    子非魚_t_閱讀 31,587評論 18 399
  • Picasso源碼完全解析(一)--概述 Picasso源碼完全解析(二)--Picasso實例的創(chuàng)建 Picas...
    三木青一閱讀 582評論 0 0
  • “高等調情的理想對象”器予,令人不禁浮想聯(lián)翩的女性形象,比交際花層次高捐迫,比女朋友更曖昧乾翔。直到今天,倘若這樣去說某位女人...
    黃媛閱讀 453評論 0 1
  • 兩人互道 晚安 在這無聲歲月里 相互陪伴 聊天是因為想念 你卻看不到我思念你的臉 夜色明亮著我的雙眼 這么近那么遠...
    林小膽閱讀 464評論 0 0