1. 程式人生 > >Picasso的使用和原始碼解析

Picasso的使用和原始碼解析

一、基本介紹

picasso是Square公司開源的一個Android圖片下載快取庫,github地址https://github.com/square/picasso,可以實現圖片下載和快取功能。

Picassso的特點有:

自動將影象快取在本地,自帶記憶體和硬碟二級快取功能
通過圖片壓縮轉換以減少記憶體消耗
自動處理了ImageView的回收,自動取消不在視野範圍內的ImageView檢視資源的載入

支援網路圖片,drawable資源,asset資源,本地圖片等多種資源載入

支援除錯,呼叫函式 Picasso.setIndicatorsEnabled(true) 可以在載入的圖片左上角顯示一個三角形,,不同的顏色代表不同的載入來源。

二、使用方法

1、gradle 配置

compile 'com.squareup.picasso:picasso:2.5.2'

2、普通圖片載入方式

只需要一行程式碼就能完全實現圖片的非同步載入:

Picasso.with(context).load("http://img.my.csdn.net/uploads/201605/08/1462674108_9582.jpg").into(imageView);

3、ADAPTER 中載入圖片:Adapter的重用會被自動檢測到,Picasso會取消上次的載入

@Override 
public void getView(int position, View convertView, ViewGroup parent) {
  Imageview view = (ImageView) convertView;
  if (view == null) {
    view = new ImageView(context);
  }
  String url = getItem(position);
  Picasso.with(context).load(url).into(view);
}

 4、圖片轉換:轉換圖片以適應佈局大小並減少記憶體佔用

 Picasso.with(context)
  .load(url)
  .resize(50, 50)
  .centerCrop()
  .into(imageView);

5、Place holders-空白或者錯誤佔位圖片:

picasso提供了兩種佔位圖片,未載入完成或者載入發生錯誤的時需要一張圖片作為提示。

Picasso.with(context)
  .load(url)
  .placeholder(R.drawable.placeholder)
  .error(R.drawable.placeholder_error)
.into(imageView);

6、多種資原始檔的載入:

除了載入網路圖片picasso還支援載入Resources, assets, files, content providers中的資原始檔。

Picasso.with(context).load(R.drawable.landing_screen).into(imageview);
Picasso.with(context).load(new File(...)).into(imageview);
Picasso.with(context).load("file:///android_asset/robert.png").into(imageview);

三、原始碼解析

1、構建方式

Picasso使用Builder建立物件,我們一般使用public static Picasso with(Context context)方法

public static Picasso with(Context context) {
     if (singleton == null) {
         synchronized (Picasso.class) {
             if (singleton == null) {
                 singleton = new Builder(context).build();
             }
         }
     }
     return singleton;
 }
Picasso的with方法返回Picasso的單例,但是有Builderg構造器,Picasso不是嚴格意義上的單例模式。
多次build還是可以建立多個例項的。

使用build構建可以自定義執行緒池、快取、下載器等方法。

2、資源載入方式

實現RequestHandler介面就可以定義資源載入方式,預設有7種

allRequestHandlers.add(new ResourceRequestHandler(context));//drawable資源圖
allRequestHandlers.add(new ContactsPhotoRequestHandler(context));//通訊錄圖片
allRequestHandlers.add(new MediaStoreRequestHandler(context));//多麼媒體資源庫圖片
allRequestHandlers.add(new ContentStreamRequestHandler(context));//Provider圖片
allRequestHandlers.add(new AssetRequestHandler(context));//asset中的圖片
allRequestHandlers.add(new FileRequestHandler(context));//儲存裝置中的圖片
allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));//網路圖片
可以通過extraRequestHandlers新增新的支援方法,定義新的RequestHandler,需要實現兩個方法
public abstract boolean canHandleRequest(Request data);
public abstract Result load(Request request, int networkPolicy) throws IOException;
canHandleRequest定義了在什麼情況下用這種方式
load定義了載入具體載入方式
具體看一下NetworkRequestHandler的具體實現,相關解析直接加註釋
class NetworkRequestHandler extends RequestHandler {
  static final int RETRY_COUNT = 2;//重試次數

  private static final String SCHEME_HTTP = "http";//識別的scheme
  private static final String SCHEME_HTTPS = "https";//識別的scheme

  private final Downloader downloader;//下載器
  private final Stats stats;//狀態
 /**
  * 構造方法
  */
  public NetworkRequestHandler(Downloader downloader, Stats stats) {
    this.downloader = downloader;
    this.stats = stats;
  }

  @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 {
    Response response = downloader.load(request.uri, request.networkPolicy);
    if (response == null) {
      return null;
    }
    Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;
    Bitmap bitmap = response.getBitmap();
    if (bitmap != null) {
      return new Result(bitmap, loadedFrom);
    }

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

  @Override int getRetryCount() {
    return RETRY_COUNT;
  }

  @Override boolean shouldRetry(boolean airplaneMode, NetworkInfo info) {
    return info == null || info.isConnected();
  }

  @Override boolean supportsReplay() {
    return true;
  }
  static class ContentLengthException extends IOException {
    public ContentLengthException(String message) {
      super(message);
    }
  }
}

3、請求建立器RequestCreator

Picasso的load方法返回RequestCreator,RequestCreator有兩個功能
配置載入引數。
包括placeHolder與error圖片,載入圖片的大小、旋轉、居中等屬性。
執行載入。
通過呼叫into(object)方法進行載入。

 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));
    }
into方法只能在主執行緒呼叫,否則會丟擲異常。如果沒有要載入的資源,請求會被取消,提高了執行效率
static void checkMain() {
  if (!isMain()) {
    throw new IllegalStateException("Method call should happen from the main thread.");
      }
}
取消操作是Picasso的方法,也只能在主執行緒呼叫
/**
 * Cancel any existing requests for the specified target {@link ImageView}.
 */
public void cancelRequest(ImageView view) {
    // checkMain() is called from cancelExistingRequest()
    if (view == null) {
        throw new IllegalArgumentException("view cannot be null.");
    }
    cancelExistingRequest(view);
}

如果target不為空,執行執行緒正常,就可以正常構建request,建立requestkey,requestkey使用簡單的屬性拼接方法
如果設定了記憶體快取,那麼從記憶體快取中讀取圖片,圖片不為空,直接設定圖片。
如果沒有設定快取,或者從快取中讀取到的圖片為空,那麼建立圖片獲取任務,

4、執行任務Action

Action是一個抽象類,可以理解為一個載入任務,
FetchAction 快取圖片
RemoteViewsAction 給RemoteView設定圖片
GetAction 同步獲取圖片
ImageViewAction 給ImageView設定圖片

TargetAction 對Target設定圖片

呼叫RequestCreator對應方法會對應建立所需要的Action

public void into(Target target)
public void fetch() 
public Bitmap get() throws IOException
public void into(ImageView target)
public void into(RemoteViews remoteViews, int viewId, int notificationId,Notification notification) 

需要特別注意的是 public Bitmap get() throws IOException 是同步方法,其他幾個是非同步方法

非同步方法將Action提交到Dispatcher,同步方法直接使用BitmapHunter獲取圖片

獲取完圖片,處理圖片的方法是

  abstract void complete(Bitmap result, Picasso.LoadedFrom from);
不同子類對應不同實現

5、事件分發器

預設的Dispatcher建立方法

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

context引數就不用多說了,service是Pecasso的非同步執行緒池,HANDLER用來向主執行緒拋訊息,downloader是下載器

cache是圖片快取,stats是執行狀態

Dispatcher是分發器,由Picasso或Hunter來呼叫。Dispatcher主要方法有
dispatcherSubmit()和dispatcherCancel()
hunter中加入action便呼叫dispatcherSubmit(),hunter中取消action便呼叫dispatcherCancel()
dispatcherComplete()和dispatcherError()
載入結束時呼叫。均呼叫batch方法,不過complete操作會將bitmap加入到cache中,以便後續呼叫。
batch()
起緩衝作用,每隔200毫秒執行一次performBatchComplete()批處理。批處理將hunterList回撥給Picasso,Picasso對每個hunter的每個action進行結果回撥。

Dispatcher啟動了自己的執行緒dispatcherThread。DispatcherHandler執行在DispatcherThread中。

RequestCreator中呼叫了Picasso的submit方法,將acton提交到Dispatcher,
DispatcherHandler傳送了REQUEST_SUBMIT這個訊息,然後在DispatcherHandler所線上程中執行了performSubmit,
在performSubmit中建立BitmapHunter,進行下載。

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


6、圖片獲取BitmapHunter

BitmapHunter是一個Runnable,作用是獲取圖片。
BitmapHunter的執行流程:在run()方法中執行hunt()方法嘗試獲取圖片,把結果交給Dispatcher回撥。

  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();
      exifOrientation = result.getExifOrientation();
      bitmap = result.getBitmap();
      ........
    }

    if (bitmap != null) {
      if (picasso.loggingEnabled) {
        log(OWNER_HUNTER, VERB_DECODED, data.logId());
      }
      stats.dispatchBitmapDecoded(bitmap);
      if (data.needsTransformation() || exifOrientation != 0) {
        synchronized (DECODE_LOCK) {
          if (data.needsMatrixTransform() || exifOrientation != 0) {
            bitmap = transformResult(data, bitmap, exifOrientation);
            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;
  }

7、快取處理

記憶體快取使用LruCache,實現是在LruCache.java中,動態分配快取大小,大小為可用記憶體的1/7,

磁碟快取使用了網路框架的快取方案。

8、網路請求處理

定義了三種下載器,分別用於okhttp3,okhttp,urlconneciton

static Downloader createDefaultDownloader(Context context) {
    if (SDK_INT >= GINGERBREAD) {
      try {
        Class.forName("okhttp3.OkHttpClient");
        return OkHttp3DownloaderCreator.create(context);
      } catch (ClassNotFoundException ignored) {
      }
      try {
        Class.forName("com.squareup.okhttp.OkHttpClient");
        return OkHttpDownloaderCreator.create(context);
      } catch (ClassNotFoundException ignored) {
      }
    }
    return new UrlConnectionDownloader(context);
  }

9、執行緒池優化

PicassoExecutorService根據網路狀況,使用不同的執行緒池,命中次數多的,優先順序高

    switch (info.getType()) {
      case ConnectivityManager.TYPE_WIFI:
      case ConnectivityManager.TYPE_WIMAX:
      case ConnectivityManager.TYPE_ETHERNET:
        setThreadCount(4);
        break;
      case ConnectivityManager.TYPE_MOBILE:
        switch (info.getSubtype()) {
          case TelephonyManager.NETWORK_TYPE_LTE:  // 4G
          case TelephonyManager.NETWORK_TYPE_HSPAP:
          case TelephonyManager.NETWORK_TYPE_EHRPD:
            setThreadCount(3);
            break;
          case TelephonyManager.NETWORK_TYPE_UMTS: // 3G
          case TelephonyManager.NETWORK_TYPE_CDMA:
          case TelephonyManager.NETWORK_TYPE_EVDO_0:
          case TelephonyManager.NETWORK_TYPE_EVDO_A:
          case TelephonyManager.NETWORK_TYPE_EVDO_B:
            setThreadCount(2);
            break;
          case TelephonyManager.NETWORK_TYPE_GPRS: // 2G
          case TelephonyManager.NETWORK_TYPE_EDGE:
            setThreadCount(1);
            break;
          default:
            setThreadCount(DEFAULT_THREAD_COUNT);
        }
        break;
      default:
        setThreadCount(DEFAULT_THREAD_COUNT);
    }


歡迎掃描二維碼,關注公眾賬號


相關推薦

Picasso的使用原始碼解析

一、基本介紹 picasso是Square公司開源的一個Android圖片下載快取庫,github地址https://github.com/square/picasso,可以實現圖片下載和快取功能。 Picassso的特點有: 自動將影象快取在本地,自帶記憶體和硬碟二級快取

【NLP】Attention原理原始碼解析

對attention一直停留在淺層的理解,看了幾篇介紹思想及原理的文章,也沒實踐過,今天立個Flag,一天深入原理和原始碼!如果你也是處於attention model level one的狀態,那不妨好好看一下啦。 內容: 核心思想 原理解析(圖解+公式) 模型分類 優缺點 TF原始碼解析

你一定會需要的FutureTask線上程池中應用原始碼解析

FutureTask 是一個支援取消的非同步處理器,一般線上程池中用於非同步接受callable返回值。 主要實現分三部分: 封裝 Callable,然後放到執行緒池中去非同步執行->run。 獲取結果-> get。 取消任務-> cancel。 接下來主

Retrofit基本使用原始碼解析

目錄介紹 1.關於Retrofit基本介紹 2.最簡單使用【配合Rx使用】 3.註解的種類 請求方法註解 請求頭註解 標記註解 引數註解 其它註解 4.Retrofit相關請求引數 @Query()【備註:get請求/ 接上引數 】 @QueryMap

Java執行緒池架構(一)原理原始碼解析

在前面介紹JUC的文章中,提到了關於執行緒池Execotors的建立介紹,在文章:《java之JUC系列-外部Tools》中第一部分有詳細的說明,請參閱; 文章中其實說明了外部的使用方式,但是沒有說內部是如何實現的,為了加深對實現的理解,在使用中可以放心,我們這裡將做原始碼解析以及反饋到原理上

演算法---hash演算法原理(java中HashMap底層實現原理原始碼解析)

散列表(Hash table,也叫雜湊表),是依據關鍵碼值(Key value)而直接進行訪問的資料結構。也就是說,它通過把關鍵碼值對映到表中一個位置來訪問記錄,以加快查詢的速度。這個對映函式叫做雜湊函式,存放記錄的陣列叫做散列表。  比如我們要儲存八十八個資料,我們為他申請了100個

Ali-Wax使用原始碼解析系列-Wax的整合除錯環境搭建

那些多餘的話 在ios平臺的基礎框架中,程式碼的直接動態部署一直是一個無法解決的問題,所以我們只能退而求其次,通過建立指令碼語言和OC的bridge來實現動態更新的目標。在這個方向上,wax-lua框架是第一個解決方案,但是隨著wax-lua作者的放棄維護,w

jdk1.7的Java執行緒池架構原理原始碼解析(ThreadPoolExecutor)

我們現在來看看ThreadPoolExecutor的原始碼是怎麼樣的,也許你剛開始看他的原始碼會很痛苦,因為你不知道作者為什麼是這樣設計的,所以本文就我看到的思想會給你做一個介紹,此時也許你通過知道了一些作者的思想,你也許就知道應該該如何去操作了。 這裡來看下構造方法中對那

CryptoKittes(加密貓,謎戀貓)智慧合約結構原始碼解析

  CryptoKittes(加密貓、謎戀貓,https://www.cryptokitties.co/)是執行在以太坊上的一個遊戲。 謎戀貓是世界首款架構在區塊鏈技術上的數字貓收集與繁殖遊戲,同樣的技術突破使得比特幣和以太坊的運作的基礎。  具體可以參考如下兩篇參考資料: 

Dubbo原理原始碼解析之服務暴露

github新增倉庫 "dubbo-read"(點此檢視),集合所有《Dubbo原理和原始碼解析》系列文章,後續將繼續補充該系列,同時將針對Dubbo所做的功能擴充套件也進行分享。不定期更新,歡迎Follow。 一、框架設計 在官方《Dubbo 使用者指南》架構部分,給出了服務呼叫的整體架構和

Dubbo原理原始碼解析之標籤解析

github新增倉庫 "dubbo-read"(點此檢視),集合所有《Dubbo原理和原始碼解析》系列文章,後續將繼續補充該系列,同時將針對Dubbo所做的功能擴充套件也進行分享。不定期更新,歡迎Follow。 一、Dubbo 配置方式 Dubbo 支援多種配置方式: XML 配置:基於

Dubbo原理原始碼解析之“微核心+外掛”機制

private void loadFile(Map<String, Class<?>> extensionClasses, String dir) { //...... BufferedReader reader = new BufferedReader(new

Dubbo原理原始碼解析之服務引用

github新增倉庫 "dubbo-read"(點此檢視),集合所有《Dubbo原理和原始碼解析》系列文章,後續將繼續補充該系列,同時將針對Dubbo所做的功能擴充套件也進行分享。不定期更新,歡迎Follow。 一、框架設計 在官方《Dubbo 開發指南》框架設計部分,給出了引用服務時序圖:

EventBus的使用原始碼解析

為什麼要使用EventBus 在Android開發過程中經常有這樣的場景–>棧頂Activity需要關閉非棧頂的Activity,或呼叫其中的某些方法。 案例:我有三個Activity,主介面Activity,個人中心Activity,登入介面Acti

Java執行緒池架構原理原始碼解析(ThreadPoolExecutor)

在前面介紹JUC的文章中,提到了關於執行緒池Execotors的建立介紹,在文章:《java之JUC系列-外部Tools》中第一部分有詳細的說明,請參閱;文章中其實說明了外部的使用方式,但是沒有說內部是如何實現的,為了加深對實現的理解,在使用中可以放心,我們這裡將做原始碼解析

Redis系列(五):資料結構List雙向連結串列中基本操作操作命令原始碼解析

1.介紹 Redis中List是通過ListNode構造的雙向連結串列。 特點: 1.雙端:獲取某個結點的前驅和後繼結點都是O(1) 2.無環:表頭的prev指標和表尾的next指標都指向NULL,對連結串列的訪問都是以NULL為終點 3.帶表頭指標和表尾指標:獲取表頭和表尾的複雜度都是O(1) 4.帶連結串

Redis系列(十二):資料結構SortedSet跳躍表中基本操作命令原始碼解析

1.SkipList Redis的sortedSet資料結構是有序不重複的(索引為唯一的,資料(score)卻可以重複), 跳錶是redis的一個核心元件,也同時被廣泛地運用到了各種快取地實現當中,它的主要優點, 就是可以跟紅黑樹、AVL等平衡樹一樣,做到比較穩定地插入、查詢與刪除。理論插入查詢刪除的演算法時

Android 圖片載入框架Picasso基本使用原始碼完全解析

寫在之前 原本打算是每週更新一篇博文,同時記錄一週的生活狀態,但是稍微工作忙一點就顧不上寫部落格了。悲催 還是說下最近的狀況,最近兩週一直在接公司申請的計費點, 沃商店,銀貝殼,微信等等,然後就是不停的被人催促催促,真是一個頭兩個大。在這期間專案組還搞了個A

Android框架原始碼解析之(四)Picasso

這次要分析的原始碼是 Picasso 2.5.2 ,四年前的版本,用eclipse寫的,但不影響這次我們對其原始碼的分析 地址:https://github.com/square/picasso/tree/picasso-parent-2.5.2 Picasso的簡單使用

List集合原始碼解析原理用法

 注:以下所用原始碼均基於JDK1.8基礎(特殊說明除外) 先從原始碼入手解析: public interface List<E> extends Collection<E> {} An ordered collection (also know