国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

Android圖片加載利器之Picasso源碼解析

2019-10-23 18:34:27
字體:
來源:轉載
供稿:網友

看到了這里,相信大家對Picasso的使用已經比較熟悉了,本篇博客中將從基本的用法著手,逐步的深入了解其設計原理。

Picasso的代碼量在眾多的開源框架中算得上非常少的一個了,一共只有35個class文件,但是麻雀雖小,五臟俱全。好了下面跟隨我的腳步,出發了。

基本用法

Picasso.with(this).load(imageUrl).into(imageView);

with(this)方法

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

非常經典的單例模式,雙重校驗鎖

在這多說一句,關于單例模式的實現方式一共有五種,分別是懶漢式,餓漢式,雙重校驗鎖,內部靜態類和枚舉,其中使用的最多的就是雙重校驗鎖和內部靜態類的兩種實現方式,主要優點是程序執行效率高,適應多線程操作。
接下來看下Builder的實現

 public static class Builder {  private final Context context;  private Downloader downloader;  private ExecutorService service;  private Cache cache;  private Listener listener;  private RequestTransformer transformer;  private List<RequestHandler> requestHandlers;  private Bitmap.Config defaultBitmapConfig;  private boolean indicatorsEnabled;  private boolean loggingEnabled;  /**    * 根據context獲取Application的context   * 此方式主要是為了避免context和單例模式的生命周期不同而造成內存泄漏的問題    */  public Builder(Context context) {   ...   this.context = context.getApplicationContext();  }   /** 設置圖片的像素格式,默認為ARGB_8888 */  public Builder defaultBitmapConfig(Bitmap.Config bitmapConfig) {   ...   this.defaultBitmapConfig = bitmapConfig;   return this;  }  /** 自定義下載器,默認OkHttp,具體的實現類是OkHttpDownloader */  public Builder downloader(Downloader downloader) {   ...   this.downloader = downloader;   return this;  }  /** 自定義線程池,默認的實現是PicassoExecutorService */  public Builder executor(ExecutorService executorService) {   ...   this.service = executorService;   return this;  }  /** 自定義緩存策略,默認實現為LruCache */  public Builder memoryCache(Cache memoryCache) {   ...   this.cache = memoryCache;   return this;  }  /** 圖片加載失敗的一個回調事件 */  public Builder listener(Listener listener) {   ...   this.listener = listener;   return this;  }  /** 請求的轉換,在request被提交之前進行轉換 */  public Builder requestTransformer(RequestTransformer transformer) {   ...   this.transformer = transformer;   return this;  }  /** 自定義加載圖片的來源 */  public Builder addRequestHandler(RequestHandler requestHandler) {   ...   requestHandlers.add(requestHandler);   return this;  }  //省略調試相關方法  /** Create the {@link Picasso} instance. */  public Picasso build() {   Context context = this.context;   if (downloader == null) {    downloader = Utils.createDefaultDownloader(context);   }   if (cache == null) {    cache = new LruCache(context);   }   if (service == null) {    service = new PicassoExecutorService();   }   if (transformer == null) {    transformer = RequestTransformer.IDENTITY;   }   Stats stats = new Stats(cache);   //得到一個事件的調度器對象,非常重要,后面會講解到   Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);   // 返回Picasso的對象   return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,     defaultBitmapConfig, indicatorsEnabled, loggingEnabled);  } }

又是一個非常經典的設計模式,建造者模式或者被稱為Buider模式,最大的特點就是鏈式調用,使調用者的代碼邏輯簡潔,同時擴展性非常好。

我們閱讀優秀框架源碼的好處就在于學習里面的設計思想,最終能夠使用到自己的項目中

with方法分析完了,我們得到了一個Picasso的對象

load(imageUrl)方法

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

load重載方法比較多,但是都比較簡單就是創建了一個RequestCreator對象

 RequestCreator(Picasso picasso, Uri uri, int resourceId) {  this.picasso = picasso;  this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig); }

又是一個建造者模式,得到了一個Request.Builder對象賦值給了data變量。

into(imageView)方法

這個方法相對復雜一些,注釋盡量描述的清楚一些,看代碼

 public void into(ImageView target, Callback callback) {  long started = System.nanoTime();  // 只能在主線程中調用  checkMain();  // hasImage()的判斷邏輯是設置了uri或者resourceId返回true  // 如果都未設置則判斷是否設置了placeholder,也就是默認顯示的圖片  if (!data.hasImage()) {   picasso.cancelRequest(target);   if (setPlaceholder) {    setPlaceholder(target, getPlaceholderDrawable());   }   return;  }  // 當設置了fit()時deferred值為true,也就是完全填充  if (deferred) {   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;   }   // 根據target也就是ImageView的大小下載圖片   data.resize(width, height);  }  // 見下方詳解1  Request request = createRequest(started);  // 這個方法的作用就是根據上面的到的Request對象里面綁定的一些參數來生成一個字符串作為key值,  // 邏輯比較清晰,主要包括stableKey(這個是用戶自定義的key值,在第二篇文章中有介紹)、uri、旋轉角度、大小、填充方式。  String requestKey = createKey(request);  // 根據用戶的設置是否從緩存里取圖片信息  if (shouldReadFromMemoryCache(memoryPolicy)) {   // 在LruCache中使用LinkedHashMap<String, Bitmap>來保存圖片信息,key就是上面生成的requestKey   // 在LruCache的get方法中返回Bitmap對象,并記錄命中或者未命中。   Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);   if (bitmap != null) {    picasso.cancelRequest(target);    setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);    // 這個callback是異步加載圖片的一個回調,之前忘記介紹了,看來需要再補充一篇文章來介紹異步和同步請求    if (callback != null) {     callback.onSuccess();    }    return;   }  }  // 如果有設置了默認顯示的圖片,則先將其顯示出來  if (setPlaceholder) {   setPlaceholder(target, getPlaceholderDrawable());  }  // 又出來一個ImageViewAction,可以看到里面傳遞了前面準備好的全部數據,那么這個對象又是做什么的呢?  // 在ImageViewAction代碼中提供了三個方法complete、error、cancel,所以可以猜想這個是用作處理最后的下載結果的  // 如果成功了就將其顯示出來,如果失敗則顯示用戶通過error方法設置的圖片  Action action =    new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,      errorDrawable, requestKey, tag, callback, noFade);  // 這里又回到了Picasso類中,見下方詳解2  picasso.enqueueAndSubmit(action); }

詳解1 createRequest

 private Request createRequest(long started) {  // 返回nextId的值并將其+1,有一個與之對應的方法是incrementAndGet,這個表示先+1再返回  int id = nextId.getAndIncrement();  // 這里面構造了一個Request對象,它是一個實體類用來存放我們請求圖片的一些參數  // 包括地址,大小,填充方式,旋轉參數,優先級等等  Request request = data.build();  request.id = id;  request.started = started;  // 判斷是否有進行request轉化,在上一篇文章中介紹了轉換的方法  Request transformed = picasso.transformRequest(request);  if (transformed != request) {   transformed.id = id;   transformed.started = started;  }  return transformed; }

詳解2 enqueueAndSubmit

從名字可以看到是將action加入到了一個隊列中,經過幾次轉換過程,從Picasso類中跑到了Dispatcher類中,這個我們在上面提到過,是一個調度器,下面我們進入Dispatcher中看看實現邏輯

dispatcher.dispatchSubmit(action);

再次經過幾經周轉,最終的實現代碼如下

 void performSubmit(Action action, boolean dismissFailed) {  // 首先根據tag判斷是否已經下發了暫停下載的命令,pausedTags是WeakHashMap類型的集合  if (pausedTags.contains(action.getTag())) {   pausedActions.put(action.getTarget(), action);   return;  }  // hunterMap是LinkedHashMap<String, BitmapHunter>()類型的對象,用來保存還未執行的下載請求  BitmapHunter hunter = hunterMap.get(action.getKey());  if (hunter != null) {   // 如果新的請求的key值在LinkedHashMap中存在,則合并兩次請求,并重新處理優先級   hunter.attach(action);   return;  }  // 這個方法主要用來判斷該請求采用哪一種requestHandler,Picasso提供了7種,我們也可以自定義  hunter = forRequest(action.getPicasso(), this, cache, stats, action);  // 將hunter添加到線程池中,hunter是Runnable的一個實現  hunter.future = service.submit(hunter);  hunterMap.put(action.getKey(), hunter);  if (dismissFailed) {   failedActions.remove(action.getTarget());  } }

提交到線程池之后就等待線程池調度了,一旦有空閑線程則將會執行BitmapHunter的run方法

// 這里只保留了關鍵的代碼,調用了hunt方法,得到了result對象,然后再通過dispatcher進行分發 public void run() {  result = hunt();  if (result == null) {    dispatcher.dispatchFailed(this);  } else {    dispatcher.dispatchComplete(this);  } }
 Bitmap hunt() throws IOException {  Bitmap bitmap = null;  // 再次檢查內存緩存,和之前的邏輯一樣  if (shouldReadFromMemoryCache(memoryPolicy)) {   bitmap = cache.get(key);   if (bitmap != null) {    stats.dispatchCacheHit();    loadedFrom = MEMORY;    return bitmap;   }  }  // networkPolicy這個值怎么計算的呢?我們先看retryCount是如何得到的  // 在構造方法中this.retryCount = requestHandler.getRetryCount();  // 那么來看getRetryCount()方法得到的值是否為0,代碼中一共有七個類重載了RequestHandler  // 在RequestHandler類中默認返回0,而只有NetworkRequestHandler重寫了getRetryCount()方法,返回2  // 因此就是說當不是從網絡請求圖片時data.networkPolicy = NetworkPolicy.OFFLINE.index  data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;  // 七個類重載了RequestHandler并且都實現了自己的load方法  // 這里面我們只看網絡相關的NetworkRequestHandler,其余的感興趣的童鞋可以自己看下代碼  // 我們先看下下面的關于 NetworkRequestHandler中load方法的代碼,再回來繼續分析  RequestHandler.Result result = requestHandler.load(data, networkPolicy);  if (result != null) {   loadedFrom = result.getLoadedFrom();   exifRotation = result.getExifOrientation();   // 解析bitmap   bitmap = result.getBitmap();   if (bitmap == null) {    InputStream is = result.getStream();    try {     bitmap = decodeStream(is, data);    } finally {     Utils.closeQuietly(is);    }   }  }  // 這一段主要是看用戶是否設置圖片的轉換處理  if (bitmap != null) {   stats.dispatchBitmapDecoded(bitmap);   if (data.needsTransformation() || exifRotation != 0) {    synchronized (DECODE_LOCK) {     if (data.needsMatrixTransform() || exifRotation != 0) {      bitmap = transformResult(data, bitmap, exifRotation);、     }     if (data.hasCustomTransformations()) {      bitmap = applyCustomTransformations(data.transformations, bitmap);     }    }    if (bitmap != null) {     stats.dispatchBitmapTransformed(bitmap);    }   }  }  return bitmap; }
/**  * OkHttpDownloader中的load方法,返回了Result對象 */ public Result load(Request request, int networkPolicy) throws IOException {  // 這里面如果我們自己沒有自定義下載器,則執行的是OkHttpDownloader中的load方法,繼續深入到load方法中一探究竟,代碼在下方了,這里面得到的response是OkHttp給我們返回來的  Response response = downloader.load(request.uri, request.networkPolicy);  // 得到加載位置是SdCard還是網絡  Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;  // 下面分別獲取了Bitmap和InputStream,同時返回了Result對象,我們返回到上面繼續分析  Bitmap bitmap = response.getBitmap();  if (bitmap != null) {   return new Result(bitmap, loadedFrom);  }  InputStream is = response.getInputStream();  if (loadedFrom == NETWORK && response.getContentLength() > 0) {   stats.dispatchDownloadFinished(response.getContentLength());  }  return new Result(is, loadedFrom); }
/**  * 這個方法中主要使用了CacheControl來承載緩存策略,同時將Request對象傳入了OkHttp中 * 看到這里Picasso源碼已經走到了盡頭,如果想繼續分析,只能查看OkHttp的代碼了,目前我還沒有通讀過, * 所以我們將得到的結果向上繼續看了,以后有時間我也會更新一些關于OkHttp的源碼解析。 * BUT 我們目前只看到了判斷內存中是否有緩存,SDCard的緩存還沒有判斷呢? * 沒錯,關于SdCard的讀取和寫入都是有OkHttp來完成的,當然了我們也可以自定義下載器, * 在這里就能看出來Picasso和OkHttp果然是親戚啊!連SdCard的緩存都幫忙實現了。 */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);  }  com.squareup.okhttp.Response response = client.newCall(builder.build()).execute();  int responseCode = response.code();  if (responseCode >= 300) {   response.body().close();   throw new ResponseException(responseCode + " " + response.message(), networkPolicy,     responseCode);  }  boolean fromCache = response.cacheResponse() != null;  ResponseBody responseBody = response.body();  return new Response(responseBody.byteStream(), fromCache, responseBody.contentLength()); }

走到了這里我們已經得到了結果,是一個result對象,然后再通過dispatcher進行分發,進入Dispatcher類中,最終執行的方法如下

 void performComplete(BitmapHunter hunter) {  // 判斷用戶是否設置了寫緩存,默認是需要寫入內存的  if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {   cache.set(hunter.getKey(), hunter.getResult());  }  // hunterMap我們在前面介紹過了,用來保存還未執行的下載請求,因此下載完成之后將其remove到  hunterMap.remove(hunter.getKey());  // 接著看batch的實現  batch(hunter); }
 private void batch(BitmapHunter hunter) {  // 將BitmapHunter對象加入到了batch變量中,batch是一個ArrayList類型的集合  batch.add(hunter);  // 到這里并沒有直接將圖片顯示出來,而是填加到list中,發送了一個延遲消息,延遲200ms  // 其實這是一個批處理,讓本次事件盡快結束,不影響界面的其他操作  // 下面我們跟進handler的HUNTER_DELAY_NEXT_BATCH語句中  if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {   handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);  } }
 void performBatchComplete() {  List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);  batch.clear();  // 將batch里的數據復制了一份,又通過mainThreadHandler發送了一個HUNTER_BATCH_COMPLETE的消息  // mainThreadHandler是怎么來的呢?原來是在Dispatcher的構造方法中傳進來的,那么我們就要回頭找找什么時候創建的Dispatcher對象  // 原來是在Picasso的Builder類build的時候創建的,而Handler也就是在Picasso類中定義,代碼如下  mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));  logBatch(copy); }

幾經周轉,最終我們又回到了Picasso的類中

 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對象組成的list,在這里做個遍歷調用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();  // 這里面來說一下single和joined,還記不記得前面分析到Dispatcher類中的performSubmit方法時  // 判斷了hunterMap中如果有相同的key值則執行hunter.attach(action);  // 因此single得到的action是hunterMap中沒有相同的key值時的action  // 而當hunterMap中存在未處理的key與新的請求的key值相同時則將action添加到了BitmapHunter類的actions對象中  // 因此joined保存的就是與single中具有相同key值的數據,所以要分別處理  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);  } }

接下來進入deliverAction方法中

 private void deliverAction(Bitmap result, LoadedFrom from, Action action) {  ...  // 關鍵代碼就這一句  action.complete(result, from);  ... }

此時進入到了ImageViewAction中的complete方法中,我們在上面提到過ImageViewAction類的作用,是用來處理最后的下載結果的,好激動啊!圖片馬上就顯示出來了~~~

 @Override  public void complete(Bitmap result, Picasso.LoadedFrom from) {  ImageView target = this.target.get();  Context context = picasso.context;  boolean indicatorsEnabled = picasso.indicatorsEnabled;  // 關鍵代碼,進入PicassoDrawable的setBitmap方法中一探究竟  PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);  if (callback != null) {   callback.onSuccess();  } }
 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對象,繼承了BitmapDrawable  PicassoDrawable drawable =    new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);  // 至此圖片終于終于顯示出來了~~~~~~  target.setImageDrawable(drawable); }

寫源碼分析太苦了,我已經盡可能的描述的清楚一些,如果有哪塊不太理解的,可以和我交流~~~

Android,圖片加載,Picasso

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。


注:相關教程知識閱讀請移步到Android開發頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 双牌县| 泾源县| 宜宾县| 同江市| 安岳县| 石阡县| 浦江县| 德昌县| 紫云| 阿拉尔市| 白城市| 通山县| 米脂县| 双城市| 五台县| 沛县| 玉树县| 富川| 弥渡县| 远安县| 黔江区| 昌宁县| 泽库县| 安新县| 合江县| 鄂尔多斯市| 崇左市| 吴忠市| 民乐县| 曲周县| 晋州市| 麻阳| 察隅县| 呼图壁县| 霍城县| 平远县| 尼勒克县| 怀安县| 右玉县| 阜宁县| 五莲县|