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

首頁 > 系統(tǒng) > Android > 正文

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

2019-12-12 03:21:35
字體:
供稿:網(wǎng)友

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

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

基本用法

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

非常經(jīng)典的單例模式,雙重校驗鎖

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

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

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

我們閱讀優(yōu)秀框架源碼的好處就在于學(xué)習(xí)里面的設(shè)計思想,最終能夠使用到自己的項目中

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

load(imageUrl)方法

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

load重載方法比較多,但是都比較簡單就是創(chuàng)建了一個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)方法

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

 public void into(ImageView target, Callback callback) {  long started = System.nanoTime();  // 只能在主線程中調(diào)用  checkMain();  // hasImage()的判斷邏輯是設(shè)置了uri或者resourceId返回true  // 如果都未設(shè)置則判斷是否設(shè)置了placeholder,也就是默認顯示的圖片  if (!data.hasImage()) {   picasso.cancelRequest(target);   if (setPlaceholder) {    setPlaceholder(target, getPlaceholderDrawable());   }   return;  }  // 當(dāng)設(shè)置了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;   }   // 根據(jù)target也就是ImageView的大小下載圖片   data.resize(width, height);  }  // 見下方詳解1  Request request = createRequest(started);  // 這個方法的作用就是根據(jù)上面的到的Request對象里面綁定的一些參數(shù)來生成一個字符串作為key值,  // 邏輯比較清晰,主要包括stableKey(這個是用戶自定義的key值,在第二篇文章中有介紹)、uri、旋轉(zhuǎn)角度、大小、填充方式。  String requestKey = createKey(request);  // 根據(jù)用戶的設(shè)置是否從緩存里取圖片信息  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是異步加載圖片的一個回調(diào),之前忘記介紹了,看來需要再補充一篇文章來介紹異步和同步請求    if (callback != null) {     callback.onSuccess();    }    return;   }  }  // 如果有設(shè)置了默認顯示的圖片,則先將其顯示出來  if (setPlaceholder) {   setPlaceholder(target, getPlaceholderDrawable());  }  // 又出來一個ImageViewAction,可以看到里面?zhèn)鬟f了前面準備好的全部數(shù)據(jù),那么這個對象又是做什么的呢?  // 在ImageViewAction代碼中提供了三個方法complete、error、cancel,所以可以猜想這個是用作處理最后的下載結(jié)果的  // 如果成功了就將其顯示出來,如果失敗則顯示用戶通過error方法設(shè)置的圖片  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,有一個與之對應(yīng)的方法是incrementAndGet,這個表示先+1再返回  int id = nextId.getAndIncrement();  // 這里面構(gòu)造了一個Request對象,它是一個實體類用來存放我們請求圖片的一些參數(shù)  // 包括地址,大小,填充方式,旋轉(zhuǎn)參數(shù),優(yōu)先級等等  Request request = data.build();  request.id = id;  request.started = started;  // 判斷是否有進行request轉(zhuǎn)化,在上一篇文章中介紹了轉(zhuǎn)換的方法  Request transformed = picasso.transformRequest(request);  if (transformed != request) {   transformed.id = id;   transformed.started = started;  }  return transformed; }

詳解2 enqueueAndSubmit

從名字可以看到是將action加入到了一個隊列中,經(jīng)過幾次轉(zhuǎn)換過程,從Picasso類中跑到了Dispatcher類中,這個我們在上面提到過,是一個調(diào)度器,下面我們進入Dispatcher中看看實現(xiàn)邏輯

dispatcher.dispatchSubmit(action);

再次經(jīng)過幾經(jīng)周轉(zhuǎn),最終的實現(xiàn)代碼如下

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

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

// 這里只保留了關(guān)鍵的代碼,調(diào)用了hunt方法,得到了result對象,然后再通過dispatcher進行分發(fā) public void run() {  result = hunt();  if (result == null) {    dispatcher.dispatchFailed(this);  } else {    dispatcher.dispatchComplete(this);  } }
 Bitmap hunt() throws IOException {  Bitmap bitmap = null;  // 再次檢查內(nèi)存緩存,和之前的邏輯一樣  if (shouldReadFromMemoryCache(memoryPolicy)) {   bitmap = cache.get(key);   if (bitmap != null) {    stats.dispatchCacheHit();    loadedFrom = MEMORY;    return bitmap;   }  }  // networkPolicy這個值怎么計算的呢?我們先看retryCount是如何得到的  // 在構(gòu)造方法中this.retryCount = requestHandler.getRetryCount();  // 那么來看getRetryCount()方法得到的值是否為0,代碼中一共有七個類重載了RequestHandler  // 在RequestHandler類中默認返回0,而只有NetworkRequestHandler重寫了getRetryCount()方法,返回2  // 因此就是說當(dāng)不是從網(wǎng)絡(luò)請求圖片時data.networkPolicy = NetworkPolicy.OFFLINE.index  data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;  // 七個類重載了RequestHandler并且都實現(xiàn)了自己的load方法  // 這里面我們只看網(wǎng)絡(luò)相關(guān)的NetworkRequestHandler,其余的感興趣的童鞋可以自己看下代碼  // 我們先看下下面的關(guān)于 NetworkRequestHandler中l(wèi)oad方法的代碼,再回來繼續(xù)分析  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);    }   }  }  // 這一段主要是看用戶是否設(shè)置圖片的轉(zhuǎn)換處理  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 {  // 這里面如果我們自己沒有自定義下載器,則執(zhí)行的是OkHttpDownloader中的load方法,繼續(xù)深入到load方法中一探究竟,代碼在下方了,這里面得到的response是OkHttp給我們返回來的  Response response = downloader.load(request.uri, request.networkPolicy);  // 得到加載位置是SdCard還是網(wǎng)絡(luò)  Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;  // 下面分別獲取了Bitmap和InputStream,同時返回了Result對象,我們返回到上面繼續(xù)分析  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源碼已經(jīng)走到了盡頭,如果想繼續(xù)分析,只能查看OkHttp的代碼了,目前我還沒有通讀過, * 所以我們將得到的結(jié)果向上繼續(xù)看了,以后有時間我也會更新一些關(guān)于OkHttp的源碼解析。 * BUT 我們目前只看到了判斷內(nèi)存中是否有緩存,SDCard的緩存還沒有判斷呢? * 沒錯,關(guān)于SdCard的讀取和寫入都是有OkHttp來完成的,當(dāng)然了我們也可以自定義下載器, * 在這里就能看出來Picasso和OkHttp果然是親戚啊!連SdCard的緩存都幫忙實現(xiàn)了。 */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()); }

走到了這里我們已經(jīng)得到了結(jié)果,是一個result對象,然后再通過dispatcher進行分發(fā),進入Dispatcher類中,最終執(zhí)行的方法如下

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

幾經(jīng)周轉(zhuǎn),最終我們又回到了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,在這里做個遍歷調(diào)用complete方法。這時候已經(jīng)回到了主線成中,圖片馬上就要顯示出來了

 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值則執(zhí)行hunter.attach(action);  // 因此single得到的action是hunterMap中沒有相同的key值時的action  // 而當(dāng)hunterMap中存在未處理的key與新的請求的key值相同時則將action添加到了BitmapHunter類的actions對象中  // 因此joined保存的就是與single中具有相同key值的數(shù)據(jù),所以要分別處理  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) {  ...  // 關(guān)鍵代碼就這一句  action.complete(result, from);  ... }

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

 @Override  public void complete(Bitmap result, Picasso.LoadedFrom from) {  ImageView target = this.target.get();  Context context = picasso.context;  boolean indicatorsEnabled = picasso.indicatorsEnabled;  // 關(guān)鍵代碼,進入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); }

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

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持武林網(wǎng)。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 湘乡市| 合水县| 曲松县| 大连市| 南岸区| 新昌县| 炎陵县| 双牌县| 海安县| 乌鲁木齐市| 江陵县| 班玛县| 孟津县| 中江县| 南宫市| 太湖县| 日喀则市| 大洼县| 丽水市| 洪江市| 三亚市| 岳阳市| 咸丰县| 兖州市| 德兴市| 普陀区| 青冈县| 木兰县| 岐山县| 肥东县| 山阳县| 朝阳县| 丰城市| 汉源县| 怀仁县| 昔阳县| 水富县| 布尔津县| 自贡市| 姚安县| 临朐县|