Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Picasso的使用和源碼解析

Picasso的使用和源碼解析

編輯:關於Android編程

一、基本介紹

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

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved