圖片加載庫-Picasso源碼分析

我們使用 Picasso 十分的簡單,只需調(diào)用下面一句就可以使用他了焊夸。

Picasso.with(this).load("http://4493bz.1985t.com/uploads/allimg/160513/3-160513102334.jpg")
            .into(mIvNormal);

那么分析也從這里開始入手

Picasso對象的創(chuàng)建

Picasso.java

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

在With方法中,很明顯singleton 是 Picasso 的單例對象 辕狰, 而這個單例對象是用 Picasso 類中的一個靜態(tài)內(nèi)部類 Builder 來實現(xiàn)的勋眯。 那么接下來就看看 build 方法里面做了什么吧

public Picasso build() {
  Context context = this.context;
    // 注 1
  if (downloader == null) {
    downloader = Utils.createDefaultDownloader(context);
  }
    // 注 2
  if (cache == null) {
    cache = new LruCache(context);
  }
    // 注 3
  if (service == null) {
    service = new PicassoExecutorService();
  }
    // 注 4
  if (transformer == null) {
    transformer = RequestTransformer.IDENTITY;
  }
    // 注 5
  Stats stats = new Stats(cache);
    // 注 6
  Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
    // 注 7
  return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
      defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}

因為我們使用 Picasso 是使用默認的 Build 對象來創(chuàng)建這個單例的 , 所以會默認配置一些所需的對象穗椅。

注 1 :downloader 是下載器辨绊,使用的是默認,如果網(wǎng)絡請求是使用的是 OkHttp 的話匹表,那么就使用 OkHttp 來進來加載圖片 门坷,否則使用 HttpURLConnection 。

注 2 : cache 使用 LruCache 緩存

注 3 : service 使用 PicassoExecutorService , service其實就是 線程池 袍镀,默認的線程數(shù)是 3 默蚌,里面實現(xiàn)它的是 PriorityBlockingQueue --- 優(yōu)先隊列

注 4 : transformer 他的作用類似于攔截器,在他提交每個請求之前立即調(diào)用的變換器苇羡。 這可以用來修改關于請求的任何信息绸吸。

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

默認不做任何處理

注 5 : stats 狀態(tài)控制類。 里面有個 HandlerThread 和一個 Handler 。 內(nèi)部其實就是 Handler 發(fā)送信息 锦茁,statsThread 來處理信息攘轩。

注 6 : dispatcher , 分發(fā)器码俩,用來分發(fā)任務度帮。

注 7 : 最后就是創(chuàng)建 一個 Picasso 了。 除了使用過默認的 Picasso 稿存,我們也可以使用 Build 來自定義自己的 Picasso 笨篷。

load 加載 URL

創(chuàng)建了 Picasso 對象 ,那么就會使用 load 來加載 URL 了挠铲。

public RequestCreator load(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));
}

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

load 方法 最后創(chuàng)建的是 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;
    // 創(chuàng)建 Request 對象 拂苹,把配置信息放置在 Request 對象中
    this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}

在 RequestCreator 對象里 安聘,保存著 一個 Request 的對象 ,而這個對象就封裝了 圖片加載的請求信息瓢棒。 比如上面創(chuàng)建了一個新的 Request 對象 浴韭,并把 uri-請求地址,resourceId-所要展示控件ID脯宿,與picasso.defaultBitmapConfig -圖片的配置(這些配置我們都可以在創(chuàng)建Picasso 時的 Build 中配置)

Picasso.with(this)
            .load("http://4493bz.1985t.com/uploads/allimg/160513/3-160513102334.jpg")
            .resize(500, 500)
            .centerCrop()
            .into(mIvResize);

在我們使用的時候念颈,會為 RequestCreator 設置其他 , 例如上面調(diào)用了 resize 方法和 centerCrop 方法连霉,其實最后就是對 Request 對象設置榴芳。

把圖片加載到 ImageView

RequestCreator配置好了,既Request 對象的參數(shù)都設置好了跺撼,現(xiàn)在就到了 into 方法了窟感。

  public void into(ImageView target, Callback callback) {
long started = System.nanoTime();
//線程檢查 , 必須在主線程
checkMain();
// 沒有 url 時拋出異常
if (target == null) {
  throw new IllegalArgumentException("Target must not be null.");
}
// 沒有 ImageView顯示圖片 時拋出異常
if (!data.hasImage()) {
  picasso.cancelRequest(target);
  if (setPlaceholder) {
    setPlaceholder(target, getPlaceholderDrawable());
  }
  return;
}
//僅有fit()方法會修改該flag為true歉井,但是該方法只能由開發(fā)者顯式調(diào)用柿祈,因此下面的代碼默認是不會執(zhí)行的
if (deferred) {
  if (data.hasSize()) {
    throw new IllegalStateException("Fit cannot be used with resize.");
  }
// 獲取 ImageView 控件的寬高
  int width = target.getWidth();
  int height = target.getHeight();
//如果 寬或高 有個為 0 ,那么加載 默認圖 就返回
  if (width == 0 || height == 0) {
    //設置了默認圖就加載
    if (setPlaceholder) {
      setPlaceholder(target, getPlaceholderDrawable());
    }
    //記錄
    picasso.defer(target, new DeferredRequestCreator(this, target, callback));
    return;
  }
  data.resize(width, height);
}
// 注 1
Request request = createRequest(started);
String requestKey = createKey(request);
// 注 2
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);
    }
    //回調(diào)
    if (callback != null) {
      callback.onSuccess();
    }
    return;
  }
}
//設置默認圖
if (setPlaceholder) {
  setPlaceholder(target, getPlaceholderDrawable());
}
// 創(chuàng)建一個 ImageViewAction 哩至,提供給 enqueueAndSubmit 方法
Action action =
    new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
        errorDrawable, requestKey, tag, callback, noFade);

//注 3
picasso.enqueueAndSubmit(action);

}

注 1 : 通過 createRequest 來創(chuàng)建 Request 對象

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

    // 獲取 request 的對象
    Request request = data.build();

    ......

    //對 request 對象進行 轉變躏嚎,因為使用默認的,所以并沒有改變request對象
    Request transformed = picasso.transformRequest(request);
    if (transformed != request) {
      //判斷如果請求已更改菩貌,請復制原始ID和時間戳卢佣。
      transformed.id = id;
      transformed.started = started;

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

}

因為在 調(diào)用 Picasso build 方法的時候,會設置 transformer --- 上文所說的 類似攔截器的 箭阶,會對 request 進行修改并返回一個新的的珠漂, 但是因為使用 的是默認的晚缩,所以并沒有改變 request 對象。

注 2 : 緩存策略媳危,是否應該從內(nèi)存中讀取荞彼。如果使用的話,就從內(nèi)存中快速讀取待笑。如果bitmap存在就直接把圖片設置給ImageView 鸣皂。

注 3 : 提交請求,進行網(wǎng)絡訪問暮蹂。

void enqueueAndSubmit(Action action) {
Object target = action.getTarget();
if (target != null && targetToAction.get(target) != action) {
  cancelExistingRequest(target);
  targetToAction.put(target, action);
}
submit(action);

}

targetToAction 是一個 map 寞缝,key是imageView,value是Action 仰泻。提交時做判斷荆陆,如果targetToAction里面已經(jīng)有了當前imageView,那么判斷當前Action與之前的Action是否相同集侯,如果不相同則取消之前的任務并將新的key-value加入到map 被啼。 最后提交 Action -- 任務

Dispatcher 任務分發(fā)器

Dispatcher 會通過Handler來提交任務,然后交由Handler的handleMessage方法來執(zhí)行棠枉,最后就會調(diào)用到Dispatcher的 performSubmit 方法浓体。

void performSubmit(Action action, boolean dismissFailed) {
    ......
    
    //注 1
    BitmapHunter hunter = hunterMap.get(action.getKey());
    if (hunter != null) {
      hunter.attach(action);
      return;
    }

    if (service.isShutdown()) {
      ....
      return;
    }

    // 注 2
    hunter = forRequest(action.getPicasso(), this, cache, stats, action);
    // 注 3
    hunter.future = service.submit(hunter);
    // 將 BitmapHunter 緩存在 map 中
    hunterMap.put(action.getKey(), hunter);
    if (dismissFailed) {
      failedActions.remove(action.getTarget());
    }

    if (action.getPicasso().loggingEnabled) {
      log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
    }
}

注 1 : 是一個Runnable的子類,用來進行Bitmap的獲取(網(wǎng)絡辈讶,硬盤命浴,內(nèi)存等) 。查看是否有對應url的緩存的hunter贱除,如果有就把當前 調(diào)用 BitmapHunter 的 attach 方法生闲。

注 2 : 創(chuàng)建一個新的 BitmapHunter 對象

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

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

    return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
}

picasso.getRequestHandlers();是獲取請求處理的方式。requestHandlers里面默認保存了一個集合月幌,里面存儲了每一類圖片的加載碍讯。這個集合是在 Picasso 的構造函數(shù)里面添加的。有處理Asset飞醉、File冲茸、Network等...

Picasso( 參數(shù) .....) {
.....
List<RequestHandler> allRequestHandlers =
    new ArrayList<RequestHandler>(builtInHandlers + extraCount);

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

}

注 3 : 把 BitmapHunter 提交任務到線程池
因為 BitmapHunter 實現(xiàn)了 Runnable 屯阀,那么在線程池中就會被調(diào)用 run 方法缅帘。

@Override public void run() {
    try {
      updateThreadName(data);

      ....
      // 注 1 
      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) {
        // 處理 OOM 
      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 : hunt 方法 就是對bitmap的獲取,從內(nèi)存难衰、硬盤钦无、網(wǎng)絡獲取。

Bitmap hunt() throws IOException {
    Bitmap bitmap = null;
    // 是否從內(nèi)存獲取
    if (shouldReadFromMemoryCache(memoryPolicy)) {
      bitmap = cache.get(key);
      if (bitmap != null) {
        //發(fā)送一個內(nèi)存緩存中查找成功的消息
        stats.dispatchCacheHit();
        loadedFrom = MEMORY;
        ...
        return bitmap;
      }
    }
    
    //NO_CACHE:跳過檢查硬盤緩存,強制從網(wǎng)絡獲取
    //NO_STORE:不存儲到本地硬盤
    //OFFLINE:只從本地硬盤獲取,不走網(wǎng)絡
    // 注 1 
    data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
    // 注 2
    RequestHandler.Result result = requestHandler.load(data, networkPolicy);
    if (result != null) {
      //獲取加載途徑(硬盤盖袭,內(nèi)存失暂,網(wǎng)絡) 
      loadedFrom = result.getLoadedFrom();
      exifRotation = result.getExifOrientation();

      bitmap = result.getBitmap();

      // 返回的resuslt中包括了bitmap和inputstream彼宠,如果bitmap為null則需要自己從stream中讀取
      if (bitmap == null) {
        InputStream is = result.getStream();
        try {
          bitmap = decodeStream(is, data);
        } finally {
          Utils.closeQuietly(is);
        }
      }
    }

    if (bitmap != null) {
      ....
    //修改stats中記錄的圖片的個數(shù)(originalBitmapCount)和占用的內(nèi)存總大小(totalOriginalBitmapSize)以及平均內(nèi)存占用量(averageOriginalBitmapSize)
      stats.dispatchBitmapDecoded(bitmap);
      if (data.needsTransformation() || exifRotation != 0) {
        synchronized (DECODE_LOCK) {
        //圖片是否需要旋轉或者其他操作處理
          if (data.needsMatrixTransform() || exifRotation != 0) {
            bitmap = transformResult(data, bitmap, exifRotation);
            ....
          }
        //自定義的圖片處理。 這個設置是在 RequestCreator 類中 弟塞,通過 transform 方法為他設置凭峡。
          if (data.hasCustomTransformations()) {
            //在里面會讀取 transformations ,對結果進行轉換
            bitmap = applyCustomTransformations(data.transformations, bitmap);
            ...
          }
        }
        // 記錄下圖片處理的次數(shù)(transformedBitmapCount)以及處理后的占用的總內(nèi)存大小(totalTransformedBitmapSize)以及每張圖片的平均內(nèi)存占用量(averageTransformedBitmapSize)
        if (bitmap != null) {
          stats.dispatchBitmapTransformed(bitmap);
        }
      }
    }
    
    return bitmap;
}

注 1 :

this.retryCount = requestHandler.getRetryCount();

在 RequestHandler.java 里

int getRetryCount() {
    return 0;
}

從各個 RequestHandler 這個抽象類看的出返回的是零决记,而 NetworkRequestHandler -- 從網(wǎng)絡加載 摧冀,則重寫了這個方法 ,返回不再是 0

static final int RETRY_COUNT = 2;
@Override int getRetryCount() {
    return RETRY_COUNT;
}

判斷 retryCount 是否為 0 系宫,如果為 0 索昂,則走本地讀取,否則認為這個是網(wǎng)絡加載扩借。

注 2 : 那么加載圖片的邏輯全部放在了 requestHandler 里的 load 里面了椒惨。

根據(jù)不同的 requestHandler 子類來實現(xiàn)不同來源圖片的加載。

讀取 硬盤文件 潮罪, 那么使用的就是 FileRequestHandler

@Override public Result load(Request request, int networkPolicy) throws IOException {
    return new Result(null, getInputStream(request), DISK, getFileExifRotation(request.uri));
}

getInputStream 方法 就是對本地文件的讀取康谆。

讀取 網(wǎng)絡圖片 , 使用就是 NetworkRequestHandler

@Override public Result load(Request request, int networkPolicy) throws IOException {
    // 注 1
    Response response = downloader.load(request.uri, request.networkPolicy);
    if (response == null) {
      return null;
    }
    // 判斷從 哪里加載的 (緩存還是網(wǎng)絡)
    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;
    }
    //本地讀取
    if (loadedFrom == DISK && response.getContentLength() == 0) {
      Utils.closeQuietly(is);
      throw new ContentLengthException("Received response with 0 content-length header.");
    }
    //網(wǎng)絡讀取
    if (loadedFrom == NETWORK && response.getContentLength() > 0) {
    // 記錄下圖片網(wǎng)絡下載的次數(shù)(downloadCount)以及總共下載的大小(totalDownloadSize)以及每張圖片的平均下載的大小(averageDownloadSize)
      stats.dispatchDownloadFinished(response.getContentLength());
    }
    return new Result(is, loadedFrom);
}

注 1 : 因為 downloader 是一個接口 實現(xiàn)他的有兩個類 (UrlConnectionDownloader 错洁、 OkHttpDownloader)秉宿,如果 OKhttp 可以使用 , 那么downloader 就是 OkHttpDownloader

下面以 OkHttpDownloader 來看

OkHttpDownloader.java

@Override public Response load(Uri uri, int networkPolicy) throws IOException {
    CacheControl cacheControl = null;
    if (networkPolicy != 0) {
        //只走本地緩存
      if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
        cacheControl = CacheControl.FORCE_CACHE;
      } else {
        //自定義緩存策略
        CacheControl.Builder builder = new CacheControl.Builder();
        if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {    
        //不從硬盤讀
          builder.noCache();
        }
        if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
        //不寫入硬盤
          builder.noStore();
        }
        cacheControl = builder.build();
      }
    }
    
    Request.Builder builder = new Request.Builder().url(uri.toString());
    if (cacheControl != null) {
      builder.cacheControl(cacheControl);
    }
    // 最后就調(diào)用了 OKhttp 請求來進行網(wǎng)絡訪問了屯碴。 其網(wǎng)絡的加載描睦,網(wǎng)絡的緩存全部交給了 Okhttp 來做。
    com.squareup.okhttp.Response response = client.newCall(builder.build()).execute();
    .....
    
    boolean fromCache = response.cacheResponse() != null;
    
    ResponseBody responseBody = response.body();
    return new Response(responseBody.byteStream(), fromCache, responseBody.contentLength());
}

現(xiàn)在就回到 BitmapHunter 里面的 run 方法吧导而,獲得了 bitmap 后忱叭,會根據(jù)是否加載成功調(diào)用相對應的方法。

  1. dispatchComplete 成功
  2. dispatchFailed 失敗
  3. dispatchRetry 重試

那么就看看處理bitmap成功獲取后的邏輯

Dispatcher.java

發(fā)送信息

void dispatchComplete(BitmapHunter hunter) {
    handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
}

處理信息

void performComplete(BitmapHunter hunter) {
    //是否能寫入內(nèi)存今艺,如果能就寫入
    if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
      cache.set(hunter.getKey(), hunter.getResult());
    }
    // hunterMap 處理完就移除
    hunterMap.remove(hunter.getKey());
    //再次發(fā)送信息韵丑,最后執(zhí)行 performBatchComplete 方法
    batch(hunter);
    ....
}

void performBatchComplete() {
    List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);
    batch.clear();
    // mainThreadHandler 發(fā)送信息。
    mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
    logBatch(copy);
}

而這個 mainThreadHandler 就要回調(diào) 虚缎, Picasso 類的 build方法里面

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

HANDLER 就是 mainThreadHandler 撵彻。那么就看看 HANDLER 做了什么。

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

最后就會 回調(diào) 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();
    // 單個 Action
    if (single != null) {
      deliverAction(result, from, single);
    }
    // 有多個 Action
    if (hasMultiple) {
      //noinspection ForLoopReplaceableByForEach
      for (int i = 0, n = joined.size(); i < n; i++) {
        Action join = joined.get(i);
        deliverAction(result, from, join);
      }
    }
    // listener 回調(diào) 实牡,因為沒有設置 陌僵,默認 為 null
    if (listener != null && exception != null) {
      listener.onImageLoadFailed(this, uri, exception);
    }
}

那么接下來就是執(zhí)行 deliverAction 方法了 。 hunter.getAction(); 所獲取的Action其實就是 在 RequestCreator 類中 的 into 方法 中 設置的 ImageViewAction 创坞。

private void deliverAction(Bitmap result, LoadedFrom from, Action action) {
    .....
      action.complete(result, from);
    .....
}

那么調(diào)用的就是 ImageViewAction 的 complete 方法碗短。

ImageViewAction.java

@Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
    if (result == null) {

    ....
    
    Context context = picasso.context;
    //是否展示來源的標簽,默認不展示题涨。
    boolean indicatorsEnabled = picasso.indicatorsEnabled;
    //注 1
    PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);
    // callback 不為空 就直接回調(diào) callback 的 onSuccess 方法
    if (callback != null) {
    callback.onSuccess();
    }
}

注 1 :

static void setBitmap(ImageView target, Context context, Bitmap bitmap,
  Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {
    Drawable placeholder = target.getDrawable();
    if (placeholder instanceof AnimationDrawable) {
      ((AnimationDrawable) placeholder).stop();
    }
    PicassoDrawable drawable =
        new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);
    target.setImageDrawable(drawable);
}

最終扔到ImageView上現(xiàn)實的的是PicassoDrawable. 最后圖片就加載了偎谁。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末总滩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子巡雨,更是在濱河造成了極大的恐慌闰渔,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铐望,死亡現(xiàn)場離奇詭異澜建,居然都是意外死亡,警方通過查閱死者的電腦和手機蝌以,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門炕舵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人跟畅,你說我怎么就攤上這事咽筋。” “怎么了徊件?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵奸攻,是天一觀的道長。 經(jīng)常有香客問我虱痕,道長睹耐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任部翘,我火速辦了婚禮硝训,結果婚禮上,老公的妹妹穿的比我還像新娘新思。我一直安慰自己窖梁,他們只是感情好,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布夹囚。 她就那樣靜靜地躺著纵刘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪荸哟。 梳的紋絲不亂的頭發(fā)上假哎,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天,我揣著相機與錄音鞍历,去河邊找鬼舵抹。 笑死,一個胖子當著我的面吹牛堰燎,可吹牛的內(nèi)容都是我干的掏父。 我是一名探鬼主播笋轨,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼秆剪,長吁一口氣:“原來是場噩夢啊……” “哼赊淑!你這毒婦竟也來了?” 一聲冷哼從身側響起仅讽,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤陶缺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后洁灵,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體饱岸,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年徽千,在試婚紗的時候發(fā)現(xiàn)自己被綠了苫费。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡双抽,死狀恐怖百框,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情牍汹,我是刑警寧澤铐维,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站慎菲,受9級特大地震影響嫁蛇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜露该,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一睬棚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧解幼,春花似錦闸拿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至台汇,卻和暖如春苛骨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背苟呐。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工痒芝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人牵素。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓严衬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親笆呆。 傳聞我的和親對象是個殘疾皇子请琳,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351

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