Picasso的使用和原始碼解析

robert_chao發表於2016-06-03

一、基本介紹

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("https://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);
    }



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


相關文章