簡單說說我最常用的圖片加載庫Picasso

簡單說說我最常用的圖片加載庫Picasso

對于一個需要展示很多圖片或較多圖片的App來說鸿捧,一個好的圖片加載框架是必不可少的陋气。在我剛開始學(xué)習(xí)Android的時候,什么都想自己寫赴魁,我以前做過一個看漫畫的App吝镣,里面的圖片加載,緩存等都是自己寫的泉蝌,但是效果并不理想歇万,當(dāng)時利用了LruCache來做圖片的內(nèi)存緩存,用DiskLruCache做磁盤緩存勋陪,加載圖片根據(jù)Key先在內(nèi)存中查詢贪磺,如果沒有就再去磁盤中查詢,若果再沒有在去網(wǎng)絡(luò)上加載诅愚。雖然是個邏輯上非常清楚的事寒锚,但是要真的做好還是有一定的難度的。后來我認(rèn)識到了Picasso,一個優(yōu)雅违孝、強(qiáng)大的圖片加載框架刹前,我使用它制作了許多的項目。雖然Picasso不是最強(qiáng)大的雌桑,現(xiàn)在有Glide喇喉、Fresco這些更強(qiáng)大的圖片加載庫,但在我眼里Picasso絕對是最優(yōu)雅的校坑,從它的API拣技,設(shè)計方法千诬,源碼看,Picasso集輕量膏斤、實用于一身徐绑。

第一眼

認(rèn)識它的第一眼肯定是它超級簡短的使用方法,只需要一行代碼掸绞,鏈?zhǔn)脚渲梦覀円虞d的圖片和一些加載配置和指定的加載Target泵三,我們的圖片就正確的加載并顯示在了界面上,不可謂不優(yōu)雅衔掸。

Picasso.with(context).load(url).into(imageView);

我們還可以很方便的配置各種加載選項烫幕,比如設(shè)置占位圖,設(shè)置錯誤圖敞映,設(shè)置是否需要對圖片進(jìn)行變換较曼,圖片顯示是否需要淡入效果,對圖片的大小進(jìn)行調(diào)整等常用的配置振愿,這些都只需要一行代碼就可以解決捷犹。

那么它是怎么做到的呢?作為一個開發(fā)者冕末,首先要學(xué)會使用一個庫萍歉,在學(xué)會了使用后,我們要去了解它档桃,了解它的設(shè)計枪孩,了解它的實現(xiàn),從中學(xué)習(xí)優(yōu)秀的設(shè)計思想和編碼技巧藻肄。

掌握一切的男人——Picasso(畢加索)

從使用上看蔑舞,Picasso類無疑是掌握著一切的類,作為一個全局單例類嘹屯,它掌握著各種配置(內(nèi)存攻询、磁盤、BitmapConfig州弟、線程池等)钧栖,提供各種簡單有用的方法并隱藏了內(nèi)部的各種實現(xiàn),是我們使用上的超級核心類婆翔。Picasso的構(gòu)造方法提供包級別的訪問權(quán)限桐经,我們不能直接new出來,但我們可以很簡單的通過

Picasso.with(context)

來獲取這個全局單例對象浙滤,來看一下with()方法

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

一個很經(jīng)典的單例實現(xiàn)阴挣,內(nèi)部使用建造者模式對Picasso對象進(jìn)行構(gòu)造。對于大多數(shù)簡單的應(yīng)用纺腊,只需要使用默認(rèn)的配置就好了畔咧。但是對于一些應(yīng)用茎芭,我們也可以通過Picasso.Builder來進(jìn)行自定義的配置。我們可以根據(jù)類似下面的代碼進(jìn)行Picasso的配置

Picasso picasso = new Picasso.Builder(this)
        .loggingEnabled(true)
        .defaultBitmapConfig(Bitmap.Config.RGB_565)
        .build();
Picasso.setSingletonInstance(picasso);

由于Picasso是一個非常復(fù)雜的對象誓沸,根據(jù)不同的配置會產(chǎn)生不同的表現(xiàn)梅桩,這里通過建造者模式來構(gòu)建對象很好的將構(gòu)建和其表現(xiàn)分離。

RequestCreator

通過Picasso對象拜隧,我們可以load各種圖片資源宿百,這個資源可以字符路徑、Uri洪添、資源路徑垦页,文件等形式提供,之后Picasso會發(fā)揮一個RequestCreator對象干奢,它可以根據(jù)我們需求以及圖片資源生成相應(yīng)的Request對象痊焊。之后Request會生成Action*對象,之后會分析這一系列的過程忿峻。由于RequestCreator的配置方法都返回自身薄啥,于是我們可以很方便的鏈?zhǔn)秸{(diào)用。

RequestCreator requestCreator = Picasso.with(this)
        .load("http://image.com")
        .config(Bitmap.Config.RGB_565)
        .centerInside()
        .fit()
        .memoryPolicy(MemoryPolicy.NO_CACHE)
        .noPlaceholder()
        .noFade();

into target——談加載過程

requestCreator.into(new ImageView(this));

生成了RequestCreator后逛尚,我們可以調(diào)用其into()方法垄惧,該方法接受可以接收一個ImageView或者RemoteView或者Target對象,當(dāng)然最后他們都會變?yōu)橄鄳?yīng)的Target對象來進(jìn)行內(nèi)部的圖片加載绰寞。

接下來我們就來分析一下這被隱藏起來的加載過程到逊。這里以最常用的into(imageView)進(jìn)行分析。

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()) {
    picasso.cancelRequest(target);
    if (setPlaceholder) {
      setPlaceholder(target, getPlaceholderDrawable());
    }
    return;
  }

  if (deferred) {
    if (data.hasSize()) {
      throw new IllegalStateException("Fit cannot be used with resize.");
    }
    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 = createRequest(started);
  String requestKey = createKey(request);

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

  if (setPlaceholder) {
    setPlaceholder(target, getPlaceholderDrawable());
  }

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

  picasso.enqueueAndSubmit(action);
}

這個方法有點長克握,我們一點一點看,首先它檢查了當(dāng)前是否為主線程枷踏,若為主線程就拋出異常菩暗。

static void checkMain() {
  if (!isMain()) {
    throw new IllegalStateException("Method call should happen from the main thread.");
  }
}

之后判斷我們給的圖片的資源路徑是否合法,如果不合法會取消生成請求旭蠕,并直接顯示占位圖

if (!data.hasImage()) {
  picasso.cancelRequest(target);
  if (setPlaceholder) {
    setPlaceholder(target, getPlaceholderDrawable());
  }
  return;
}

若給的圖片資源路徑不為空停团,會檢查一個defer字段的真假,只有當(dāng)我們設(shè)置fit()時掏熬,defer才為true佑稠,因為fit()方法會讓加載的圖片以適應(yīng)我們ImageView,所以只有當(dāng)我們的ImageView laid out后才會去進(jìn)行圖片的獲取并剪裁以適應(yīng)旗芬。這里就不具體分析了舌胶,接下來往下看。

Request request = createRequest(started);

private Request createRequest(long started) {
    int id = nextId.getAndIncrement();

    Request request = data.build();
    request.id = id;
    request.started = started;

    boolean loggingEnabled = picasso.loggingEnabled;
    if (loggingEnabled) {
      log(OWNER_MAIN, VERB_CREATED, request.plainId(), request.toString());
    }

    Request transformed = picasso.transformRequest(request);
    if (transformed != request) {
      // If the request was changed, copy over the id and timestamp from the original.
      transformed.id = id;
      transformed.started = started;

      if (loggingEnabled) {
        log(OWNER_MAIN, VERB_CHANGED, transformed.logId(), "into " + transformed);
      }
    }

    return transformed;
  }

接下來疮丛,終于生成了我們的Request對象幔嫂,同Http請求的Request對象相同辆它,這個Request對象包含了許多我們需要的信息已獲得準(zhǔn)確的回應(yīng)。這里注意履恩,我們的Request對象是可以通過RequestTransformer經(jīng)過變換的锰茉,默認(rèn)Picasso中內(nèi)置的是一個什么也不變的RequestTransformer

默認(rèn)的RequestTransformer

RequestTransformer IDENTITY = new RequestTransformer() {
  @Override public Request transformRequest(Request request) {
    return request;
  }
};

生成了Request對象后切心,這里來到了我們熟悉的一個步驟飒筑,就是檢查內(nèi)存中是否已經(jīng)有圖片的緩存了,當(dāng)然它還更具我們設(shè)置的內(nèi)存策略進(jìn)行了是否需要進(jìn)行內(nèi)存檢查绽昏。比如我們在查看大圖的時候协屡,一般會選擇不讓大圖在內(nèi)存緩存中存在,而是每次都去請求而涉。

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è)置占位設(shè)置一下展位圖,然后生成一個Action對象并使其加入隊列發(fā)出啼县。

if (setPlaceholder) {
  setPlaceholder(target, getPlaceholderDrawable());
}

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

picasso.enqueueAndSubmit(action);

這個Action對象包含了我們的目標(biāo)Target和請求數(shù)據(jù)Request以及相應(yīng)的回調(diào)Callback等信息材原。

到這里我們還是沒有真正的進(jìn)行加載,最多只在內(nèi)存中進(jìn)行了緩存查詢季眷。

Go Action

通過上面的代碼調(diào)用余蟹,我們了解到Picasso發(fā)出了一個Action,就像一個導(dǎo)演一樣子刮,說Action的時候就開始拍戲了威酒,我們的Picasso在發(fā)出Action后就開始正式加載圖片了。

Action最終會被Dispatcher給分發(fā)出去挺峡。

void submit(Action action) {
  dispatcher.dispatchSubmit(action);
}

Dispatcher隨著Picasso的初始化而生成葵孤,正如其名,它負(fù)責(zé)所有Action的分發(fā)并將收到的結(jié)果也進(jìn)行分發(fā)橱赠,分發(fā)給Picasso對象尤仍。我們來分析一下Dispatcher的工作過程。

Dispatcher內(nèi)部是通過Handler進(jìn)行工作的狭姨,這里插下嘴宰啦,Handler真是Android中超級強(qiáng)大的類,幫助我們輕松的實現(xiàn)線程之間的通信饼拍、切換赡模,有許多有名的開源庫都利用了Handler來實現(xiàn)功能,比如Google自家的響應(yīng)式框架Agera师抄,內(nèi)部真是通過Handler實現(xiàn)的漓柑。所以我們要好好的掌握這個強(qiáng)大的類。

不多說了,我們繼續(xù)看代碼

void dispatchSubmit(Action action) {
  handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}

Dispatcher通過內(nèi)部的Handler發(fā)送了一個消息欺缘,并將Action對象傳遞了出去栋豫,那么我們就要找到這個Handler對象的具體實現(xiàn),根據(jù)收到的消息類型它做了哪些事情谚殊。

private static class DispatcherHandler extends Handler {
  private final Dispatcher dispatcher;

  public DispatcherHandler(Looper looper, Dispatcher dispatcher) {
    super(looper);
    this.dispatcher = dispatcher;
  }

  @Override public void handleMessage(final Message msg) {
    switch (msg.what) {
      case REQUEST_SUBMIT: {
        Action action = (Action) msg.obj;
        dispatcher.performSubmit(action);
        break;
      }
        ……
    }
  }
}

很容易找到了丧鸯,就是執(zhí)行了performSubmit(action)方法,繼續(xù)來看下去嫩絮,這里還沒有具體的加載動作丛肢。

void performSubmit(Action action, boolean dismissFailed) {
  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 = 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());
  }
}

又是一段比較長的代碼,但是還是很容易看懂的剿干。主要就是生成了BitmapHunter對象蜂怎,并將其發(fā)出。

hunter = forRequest(action.getPicasso(), this, cache, stats, action);
hunter.future = service.submit(hunter);

通過查看代碼置尔,我們知道這個service對象是一個ExecutorService杠步,也就我們常用的線程池,這里Picasso默認(rèn)使用的是PicassoExecutorService榜轿,在Dispatcher內(nèi)部有一個監(jiān)聽網(wǎng)絡(luò)變化廣播的Receiver幽歼,Picasso會根據(jù)我們不同的網(wǎng)絡(luò)變化(Wifi,4G,3G,2G)智能的切換線程池中該線程的數(shù)量,以幫助我們更好的節(jié)省流量谬盐。

既然線程池將BitmapHunter對象發(fā)出了甸私,說明這個BitmapHunter對象一定是Runable的子類,從名字上來分析飞傀,我們也可以知道它肯定承擔(dān)了Bitmap的獲取生成皇型。點進(jìn)去一看源碼,果然是這樣的砸烦。哈哈弃鸦。

線程池submit了一個Runable對象后,一定會調(diào)用其run()方法幢痘,我們就來看看它是不是加載了Bitmap吧~

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

代碼雖長唬格,核心只有hunt()一句,捕獵開始雪隧!BitmapHunter這個獵人開始狩獵Bitmap了西轩。

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

這個方法很長员舵,最終返回我們所期望的Bitmap對象脑沿,說明這一個方法正是真正從各個渠道獲取Bitmap對象的方法。我們看到马僻,這里又進(jìn)行了一次內(nèi)存緩存的檢查庄拇,避免兩次請求相同圖片的重復(fù)加載,沒有的話,利用RequestHandler對象進(jìn)行加載措近。RequestHandler是一個抽象類溶弟,load()方法抽象,因為根據(jù)不同圖片的加載要執(zhí)行不同的操作瞭郑,比如從網(wǎng)絡(luò)中獲取辜御,從resource中獲取,從聯(lián)系人中獲取屈张,從MediaStore獲取等擒权,這里運(yùn)用了模板方法的模式,將一些方法將邏輯相同的方法公用阁谆,具體的加載細(xì)節(jié)子類決定碳抄,最終都返回Result對象。

根據(jù)不同的RequestHandler實例场绿,我們可能可以直接獲取一個Bitmap對象剖效,也可能只獲取一段流InputStream,比如網(wǎng)絡(luò)加載圖片時焰盗。如果結(jié)果以流的形式提供璧尸,那么Picasso會自動幫我們將流解析成Bitmap對象。之后根據(jù)我們之前通過RequestCreatorRequest的配置姨谷,決定是否對Bitmap對象進(jìn)行變換逗宁,具體實現(xiàn)都大同小異,利用強(qiáng)大的Matrix梦湘,最后返回Bitmap瞎颗。

之后我們再回到run()方法,假設(shè)這里我們成功獲取到了Bitmap捌议,那么是怎么傳遞的呢哼拔?這里還是利用了Dispatcher

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

和之前一樣,Dispatcher內(nèi)部通過Handler發(fā)送了一個特定的消息瓣颅,然后執(zhí)行倦逐。

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

上面就是它具體做的事,這里著重看batch()方法宫补,相信這個方法一定是通過某種方式將獵人狩獵的Bitmap給上交給國家了檬姥。

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

咦?這里還是沒有將Bitmap發(fā)出粉怕,但是通過handler發(fā)出了一個消息健民,恩恩,這都是套路了贫贝。這里有個設(shè)計很棒的地方秉犹,這里發(fā)送消息用了延時蛉谜,為什么呢?為什么不馬上發(fā)送呢崇堵?Picasso是將圖片一批批的送出去的型诚,每次發(fā)送的間隔為200ms,間隔不長也不短鸳劳,這樣有什么好處呢狰贯,好處就是如果我們看到一部分圖片是一起加載出來的,而不是一張一張加載出來的赏廓。這個設(shè)計好貼心暮现。前面根據(jù)網(wǎng)絡(luò)情況決定線程池大小的做法也好貼心,Jake大大真是超棒楚昭!

找到了這個消息收到后具體執(zhí)行的方法了

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

這里就體現(xiàn)了Handler線程通信與切換的強(qiáng)大之處了栖袋,這里通過將一批Bitmap對象交給了處理主線程消息的Handler處理,這樣我們獲取到Bitmap并加載到ImageView上就是在主線程了操作的了抚太,我們?nèi)タ纯催@個Handler做了什么塘幅。這個Handler是在Dispatcher構(gòu)造時傳進(jìn)來的,在Picasso類中定義尿贫。哈哈电媳,正不虧是掌握一切權(quán)與利的對象,最苦最累的活讓別人去做庆亡,自己做最風(fēng)光體面的事妄帘。

static final Handler HANDLER = new Handler(Looper.getMainLooper()) {
  @Override public void handleMessage(Message msg) {
    switch (msg.what) {
      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;
      }
        ……
    }
};

不多說了搏予,我們來看看代碼岂贩,恩妈踊,很清楚的針對每個BitmapHunter執(zhí)行了complete()方法,那就去看看嘍彰亥。

void complete(BitmapHunter hunter) {
  Action single = hunter.getAction();
  List<Action> joined = hunter.getActions();

  boolean hasMultiple = joined != null && !joined.isEmpty();
  boolean shouldDeliver = single != null || hasMultiple;

  if (!shouldDeliver) {
    return;
  }

  Uri uri = hunter.getData().uri;
  Exception exception = hunter.getException();
  Bitmap result = hunter.getResult();
  LoadedFrom from = hunter.getLoadedFrom();

  if (single != null) {
    deliverAction(result, from, single);
  }

  if (hasMultiple) {
    //noinspection ForLoopReplaceableByForEach
    for (int i = 0, n = joined.size(); i < n; i++) {
      Action join = joined.get(i);
      deliverAction(result, from, join);
    }
  }

  if (listener != null && exception != null) {
    listener.onImageLoadFailed(this, uri, exception);
  }
}

這個方法從BitmapHunter中獲取Action對象咧七,若這個Action成功完成,會執(zhí)行Action的complete()方法任斋,并將結(jié)果傳入继阻,很明顯這是一個回調(diào)方法。Action也是一個抽象類废酷,根據(jù)我們的Target不同會生成不同的Action對象瘟檩,比如into(target)方法會生成TargetAction對象,into(imageView)會生成ImageViewAction對象澈蟆,調(diào)用fetch()方法會產(chǎn)生FetchAction對象墨辛。這里由于我們是以將圖片加載到ImageView中來分析的,所以我們只分析一下ImageViewAction的代碼丰介。

每個Action的子類要實現(xiàn)兩個回調(diào)方法背蟆,一個error(),在圖片加載失敗時觸發(fā)哮幢,一個complete()带膀,在圖片成功加載時調(diào)用。

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

@Override public void error() {
  ImageView target = this.target.get();
  if (target == null) {
    return;
  }
  Drawable placeholder = target.getDrawable();
  if (placeholder instanceof AnimationDrawable) {
    ((AnimationDrawable) placeholder).stop();
  }
  if (errorResId != 0) {
    target.setImageResource(errorResId);
  } else if (errorDrawable != null) {
    target.setImageDrawable(errorDrawable);
  }

  if (callback != null) {
    callback.onError();
  }
}

恩橙垢,很清楚的看到成功時就正確顯示圖片垛叨,失敗時則根據(jù)是否設(shè)置了錯誤圖來選擇是否加載錯誤圖。這里用到了一個PicassoDrawable的類柜某,這個類繼承自BitmapDrawable類嗽元,通過它,可以很方便的實現(xiàn)淡入淡出效果喂击。

總結(jié)

到這里剂癌,我們分析完了一次成功的Picasso圖片加載過程,當(dāng)然我們不可能每次都成功翰绊,也會發(fā)生各種錯誤佩谷,或者我們暫停了加載又繼續(xù)加載等,這些Picasso都作出了不同的處理监嗜,線程之間通過Handler進(jìn)行通信谐檀。這里就不一一詳述了。相信大家自己會去看的裁奇。

不得不說桐猬,Picasso真是一個優(yōu)雅的圖片加載庫,用極少的代碼完成了了不起的事刽肠,從API的設(shè)計到代碼的編寫溃肪,無不體現(xiàn)了作者的深思熟慮和貼心,運(yùn)用多用設(shè)計模式巧妙地提供極其簡單的調(diào)用接口音五,隱藏背后復(fù)雜的實現(xiàn)乍惊。通過對Picasso的使用與源碼分析,我學(xué)到了很多放仗,希望我以后也可以設(shè)計出這么優(yōu)雅實用的庫或框架润绎。

天道酬勤,我要加油诞挨!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末莉撇,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子惶傻,更是在濱河造成了極大的恐慌棍郎,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件银室,死亡現(xiàn)場離奇詭異涂佃,居然都是意外死亡励翼,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門辜荠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來汽抚,“玉大人,你說我怎么就攤上這事伯病≡焖福” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵午笛,是天一觀的道長惭蟋。 經(jīng)常有香客問我,道長药磺,這世上最難降的妖魔是什么告组? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮癌佩,結(jié)果婚禮上惹谐,老公的妹妹穿的比我還像新娘。我一直安慰自己驼卖,他們只是感情好氨肌,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著酌畜,像睡著了一般怎囚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上桥胞,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天恳守,我揣著相機(jī)與錄音,去河邊找鬼贩虾。 笑死催烘,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的缎罢。 我是一名探鬼主播伊群,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼策精!你這毒婦竟也來了舰始?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤咽袜,失蹤者是張志新(化名)和其女友劉穎丸卷,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體询刹,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡谜嫉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年萎坷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沐兰。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡哆档,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出僧鲁,到底是詐尸還是另有隱情,我是刑警寧澤象泵,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布寞秃,位于F島的核電站,受9級特大地震影響偶惠,放射性物質(zhì)發(fā)生泄漏春寿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一忽孽、第九天 我趴在偏房一處隱蔽的房頂上張望绑改。 院中可真熱鬧,春花似錦兄一、人聲如沸厘线。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽造壮。三九已至,卻和暖如春骂束,著一層夾襖步出監(jiān)牢的瞬間耳璧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工展箱, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留旨枯,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓混驰,卻偏偏與公主長得像攀隔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子栖榨,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容