Picasso源碼解析及優(yōu)化見解

Picasso是一款圖片加載庫(kù)出自Square,以小巧功能齊全出名,本文將從源碼解析Picasso的整個(gè)加載流程。


Picasso時(shí)序圖

時(shí)序圖為筆者根據(jù)整個(gè)調(diào)用流程所畫,有誤私聊筆者進(jìn)行修改

整個(gè)流程

  • 調(diào)用Picasso創(chuàng)建一個(gè)RequestCreator,并返回
  public RequestCreator load(@Nullable 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));
  }

  public RequestCreator load(@Nullable Uri uri) {
    return new RequestCreator(this, uri, 0);
  }
  • RequestCreator設(shè)置請(qǐng)求參數(shù),url、資源id、transform、fix等
  • 調(diào)用RequestCreator的into,設(shè)置了fix的情況下,如果控件已經(jīng)獲取到尺寸就創(chuàng)建Action,否則就創(chuàng)建一DeferredRequestCreator(延遲請(qǐng)求),獲取到后重新into
class DeferredRequestCreator implements OnPreDrawListener, OnAttachStateChangeListener {
  private final RequestCreator creator;
  @VisibleForTesting final WeakReference<ImageView> target;
  @VisibleForTesting Callback callback;

  DeferredRequestCreator(RequestCreator creator, ImageView target, Callback callback) {
    this.creator = creator;
    this.target = new WeakReference<>(target);
    this.callback = callback;

    target.addOnAttachStateChangeListener(this);

    // Only add the pre-draw listener if the view is already attached.
    // See: https://github.com/square/picasso/issues/1321
    if (target.getWindowToken() != null) {
      onViewAttachedToWindow(target);
    }
  }

  @Override public void onViewAttachedToWindow(View view) {
    view.getViewTreeObserver().addOnPreDrawListener(this);
  }

  @Override public void onViewDetachedFromWindow(View view) {
    view.getViewTreeObserver().removeOnPreDrawListener(this);
  }

  @Override public boolean onPreDraw() {
    ImageView target = this.target.get();
    if (target == null) {
      return true;
    }

    ViewTreeObserver vto = target.getViewTreeObserver();
    if (!vto.isAlive()) {
      return true;
    }

    int width = target.getWidth();
    int height = target.getHeight();

    if (width <= 0 || height <= 0) {
      return true;
    }

    target.removeOnAttachStateChangeListener(this);
    vto.removeOnPreDrawListener(this);
    this.target.clear();
    //控件已經(jīng)獲取到寬高,重新into
    this.creator.unfit().resize(width, height).into(target, callback);
    return true;
  }

  void cancel() {
  ...省略
  }
}

  • 調(diào)用Picasso.enqueueAndSubmit(Action)->Dispatcher.performSubmit(Action)->ExecutorService.submit(BitmapHunter)將請(qǐng)求提交給線程池去處理
  void performSubmit(Action action, boolean dismissFailed) {
    //已暫停的,就結(jié)束了
    if (pausedTags.contains(action.getTag())) {
      pausedActions.put(action.getTarget(), action);
      if (action.getPicasso().loggingEnabled) {
        Utils.log(Utils.OWNER_DISPATCHER, Utils.VERB_PAUSED, action.request.logId(),
            "because tag '" + action.getTag() + "' is paused");
      }
      return;
    }
    //通過請(qǐng)求key獲取BitmapHunter核心類,這一步的主要作用是,一個(gè)圖片有兩個(gè)控價(jià)都需要使用她,
    //避免重復(fù)請(qǐng)求。這里的attach方法就是將action,添加當(dāng)前這個(gè)BitmapHunter的action列表中
    BitmapHunter hunter = hunterMap.get(action.getKey());
    if (hunter != null) {
      hunter.attach(action);
      return;
    }

    if (service.isShutdown()) {
      if (action.getPicasso().loggingEnabled) {
        Utils.log(Utils.OWNER_DISPATCHER, Utils.VERB_IGNORED, action.request.logId(), "because shut down");
      }
      return;
    }
    //策略模式,選取可以請(qǐng)求該種類型
   //根據(jù)傳入的action中的Request資源地址來判定使用那種請(qǐng)求處理器
    hunter = forRequest(action.getPicasso(), this, cache, stats, action);
   //提交線程池,請(qǐng)求圖片
    hunter.future = service.submit(hunter);
    //保存任務(wù)句柄(取消時(shí)可用)
    hunterMap.put(action.getKey(), hunter);
    if (dismissFailed) {
      failedActions.remove(action.getTarget());
    }

    if (action.getPicasso().loggingEnabled) {
      Utils.log(Utils.OWNER_DISPATCHER, Utils.VERB_ENQUEUED, action.request.logId());
    }
  }
  • BitmapHunter執(zhí)行run方法獲取圖片資源,run方法調(diào)用hunt獲取bitmap,并執(zhí)行Transformation
 @Override public void run() {
    try {
      result = hunt();

      if (result == null) {
        dispatcher.dispatchFailed(this);
      } else {
        dispatcher.dispatchComplete(this);
      }
    } 
    ...省略
  }

  Bitmap hunt() throws IOException {
    Bitmap bitmap = null;
    //讀取策略,是否可以讀取緩存
    if (MemoryPolicy.shouldReadFromMemoryCache(memoryPolicy)) {
      //根據(jù)請(qǐng)求key獲取緩存bitmap
      bitmap = cache.get(key);
      if (bitmap != null) {
        //記錄緩存熱度
        stats.dispatchCacheHit();
        loadedFrom = Picasso.LoadedFrom.MEMORY;
        if (picasso.loggingEnabled) {
          Utils.log(Utils.OWNER_HUNTER, Utils.VERB_DECODED, data.logId(), "from cache");
        }
        return bitmap;
      }
    }
    //沒有緩存或者不允許讀取緩存,繼續(xù)往下執(zhí)行
    
    networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
    //請(qǐng)求處理器load圖片資源
    RequestHandler.Result result = requestHandler.load(data, networkPolicy);
    if (result != null) {
      loadedFrom = result.getLoadedFrom();
      exifOrientation = result.getExifOrientation();
      bitmap = result.getBitmap();

      // If there was no Bitmap then we need to decode it from the stream.
      if (bitmap == null) {
        Source source = result.getSource();
        try {
          //將流文件轉(zhuǎn)為bitmap
          bitmap = decodeStream(source, data);
        } finally {
          try {
            //noinspection ConstantConditions If bitmap is null then source is guranteed non-null.
            source.close();
          } catch (IOException ignored) {
          }
        }
      }
    }

    if (bitmap != null) {
      if (picasso.loggingEnabled) {
        Utils.log(Utils.OWNER_HUNTER, Utils.VERB_DECODED, data.logId());
      }
      //狀態(tài)更新
      stats.dispatchBitmapDecoded(bitmap);
      //是否需要transformation,必須調(diào)用RequestCreator的fix()方法,默認(rèn)的Transformation才會(huì)被調(diào)用。會(huì)根據(jù)獲取到控件的size調(diào)整圖
      if (data.needsTransformation() || exifOrientation != 0) {
        synchronized (DECODE_LOCK) {
          if (data.needsMatrixTransform() || exifOrientation != 0) {
            //默認(rèn)的transformation,會(huì)根據(jù)控件大小調(diào)整bitmap
            bitmap = transformResult(data, bitmap, exifOrientation);
            if (picasso.loggingEnabled) {
              Utils.log(Utils.OWNER_HUNTER, Utils.VERB_TRANSFORMED, data.logId());
            }
          }
          if (data.hasCustomTransformations()) {
             //自定義的transformation,圓角圖片啥的變換
            bitmap = applyCustomTransformations(data.transformations, bitmap);
            if (picasso.loggingEnabled) {
              Utils.log(Utils.OWNER_HUNTER, Utils.VERB_TRANSFORMED, data.logId(), "from custom transformations");
            }
          }
        }
        if (bitmap != null) {
          stats.dispatchBitmapTransformed(bitmap);
        }
      }
    }

    return bitmap;
  }

從上面的源碼可知緩存邏輯:內(nèi)存-->本地緩存(由okhttp提供)-->網(wǎng)絡(luò),Picasso一共提供了7圖片請(qǐng)求處理器,這里著重介紹哈網(wǎng)絡(luò)處理器
NetworkRequestHandler.java

class NetworkRequestHandler extends RequestHandler {
  private static final String SCHEME_HTTP = "http";
  private static final String SCHEME_HTTPS = "https";

  private final Downloader downloader;
  private final Stats stats;

  NetworkRequestHandler(Downloader downloader, Stats stats) {
    this.downloader = downloader;
    this.stats = stats;
  }
  //這個(gè)處理器,可以處理那種類型的資源,scheme是http和https的
  @Override public boolean canHandleRequest(Request data) {
    String scheme = data.uri.getScheme();
    return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
  }
  //加載并返回result
  @Override public Result load(Request request, int networkPolicy) throws IOException {
    //創(chuàng)建okhttp request
    Request downloaderRequest = createRequest(request, networkPolicy);
    //OkHttp3Downloader的load方法去下載資源
    Response response = downloader.load(downloaderRequest);
    ResponseBody body = response.body();

    if (!response.isSuccessful()) {
      body.close();
      throw new ResponseException(response.code(), request.networkPolicy);
    }
    //資源來源,網(wǎng)絡(luò)/磁盤
    Picasso.LoadedFrom loadedFrom = response.cacheResponse() == null ? NETWORK : DISK;
    if (loadedFrom == DISK && body.contentLength() == 0) {
      body.close();
      throw new ContentLengthException("Received response with 0 content-length header.");
    }
    if (loadedFrom == NETWORK && body.contentLength() > 0) {
      stats.dispatchDownloadFinished(body.contentLength());
    }
    //返回結(jié)果
    return new Result(body.source(), loadedFrom);
  }
...省略
}

OkHttp3Downloader下載器源碼

public final class OkHttp3Downloader implements Downloader {
  @VisibleForTesting final Call.Factory client;
  private final Cache cache;
  private boolean sharedClient = true;

  //初始化獲設(shè)置緩存目錄默認(rèn):/data/data/包名/cache/picasso-cache下
  public OkHttp3Downloader(final Context context) {
    this(Utils.createDefaultCacheDir(context));
  }

  //初始化獲設(shè)置緩存
  public OkHttp3Downloader(final File cacheDir) {
    this(cacheDir, Utils.calculateDiskCacheSize(cacheDir));
  }

  //初始化獲設(shè)置緩存
  public OkHttp3Downloader(final Context context, final long maxSize) {
    this(Utils.createDefaultCacheDir(context), maxSize);
  }

  /**
   * Create new downloader that uses OkHttp. This will install an image cache into the specified
   * directory.
   *
   * @param cacheDir The directory in which the cache should be stored
   * @param maxSize The size limit for the cache.
   */
  public OkHttp3Downloader(final File cacheDir, final long maxSize) {
    this(new OkHttpClient.Builder().cache(new Cache(cacheDir, maxSize)).build());
    sharedClient = false;
  }
  //load執(zhí)行同步方法去加載網(wǎng)絡(luò)上的圖片,是否使用本地緩存的圖片,這里有okhttp去實(shí)現(xiàn)
  //如果需要更換網(wǎng)絡(luò)訪問框架,需要實(shí)現(xiàn)1.網(wǎng)絡(luò)下載圖片; 2. 緩存圖片到本地,下次請(qǐng)求時(shí),返回緩存圖片
  @NonNull @Override public Response load(@NonNull Request request) throws IOException {
    return client.newCall(request).execute();
  }
  ...省略
}
  • Dispatcher.dispatchComplete最終調(diào)用了performComplete方法
  void performComplete(BitmapHunter hunter) {
    //是否緩存到內(nèi)次
    if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
      cache.set(hunter.getKey(), hunter.getResult());
    }
    //移除進(jìn)行時(shí)的bitmap請(qǐng)求
    hunterMap.remove(hunter.getKey());
    batch(hunter);
    if (hunter.getPicasso().loggingEnabled) {
      Utils.log(Utils.OWNER_DISPATCHER, Utils.VERB_BATCHED, Utils.getLogIdsForHunter(hunter), "for completion");
    }
  }

  private void batch(BitmapHunter hunter) {
    if (hunter.isCancelled()) {
      return;
    }
    if (hunter.result != null) {
     //bitmap的預(yù)畫
      hunter.result.prepareToDraw();
    }
    batch.add(hunter);
    if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
      handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
    }
  }

最終調(diào)用到了調(diào)用Picasso類的deliverAction方法

  private void deliverAction(Bitmap result, LoadedFrom from, Action action, Exception e) {
    if (action.isCancelled()) {//取消
      return;
    }
    if (!action.willReplay()) {//將重試
      targetToAction.remove(action.getTarget());
    }
    if (result != null) {
      if (from == null) {
        throw new AssertionError("LoadedFrom cannot be null.");
      }
     //調(diào)用action的complete
      action.complete(result, from);
      if (loggingEnabled) {
        Utils.log(Utils.OWNER_MAIN, Utils.VERB_COMPLETED, action.request.logId(), "from " + from);
      }
    } else {
      action.error(e);
      if (loggingEnabled) {
        Utils.log(Utils.OWNER_MAIN, Utils.VERB_ERRORED, action.request.logId(), e.getMessage());
      }
    }
  }

Picasso提供了4種Actiong,這里貼哈ImageViewAction

class ImageViewAction extends Action<ImageView> {

  Callback callback;

  ImageViewAction(Picasso picasso, ImageView imageView, Request data, int memoryPolicy,
      int networkPolicy, int errorResId, Drawable errorDrawable, String key, Object tag,
      Callback callback, boolean noFade) {
    super(picasso, imageView, data, memoryPolicy, networkPolicy, errorResId, errorDrawable, key,
        tag, noFade);
    this.callback = callback;
  }
  //加載完成時(shí)
  @Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
    if (result == null) {
      throw new AssertionError(
          String.format("Attempted to complete action with no result!\n%s", this));
    }

    ImageView target = this.target.get();
    if (target == null) {
      return;
    }

    Context context = picasso.context;
    boolean indicatorsEnabled = picasso.indicatorsEnabled;
    //設(shè)置bitmap到target,也就是我們的目標(biāo)view
    PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);
    //回調(diào)
    if (callback != null) {
      callback.onSuccess();
    }
  }
  
  //加載失敗時(shí)
  @Override public void error(Exception e) {
    ImageView target = this.target.get();
    if (target == null) {
      return;
    }
    Drawable placeholder = target.getDrawable();
    if (placeholder instanceof Animatable) {
      ((Animatable) placeholder).stop();
    }
    if (errorResId != 0) {
      target.setImageResource(errorResId);
    } else if (errorDrawable != null) {
      target.setImageDrawable(errorDrawable);
    }

    if (callback != null) {
      callback.onError(e);
    }
  }
  
//取消
  @Override void cancel() {
    super.cancel();
    if (callback != null) {
      callback = null;
    }
  }
}

Picasso加載流程就已經(jīng)解析完了,網(wǎng)絡(luò)和本地緩存都依賴OKhttp,我們整個(gè)項(xiàng)目圖片加載不多,項(xiàng)目中使用的OKhttp,所以我選用了Picasso來減少代碼量。

Picasso需要優(yōu)化點(diǎn)

  1. 內(nèi)存緩存,可以看到是通過requestKey+Bitmap存入LruCache,但是requestkey上帶有尺寸旋轉(zhuǎn)角度等參數(shù),也就是說同一張圖片,因?yàn)槌叽绲葏?shù)不同就會(huì)造成儲(chǔ)存了多張bitmap,bitmap(吃內(nèi)存大戶啊)
    Utils下的requestKey源碼
static String createKey(Request data, StringBuilder builder) {
    if (data.stableKey != null) {
      builder.ensureCapacity(data.stableKey.length() + KEY_PADDING);
      builder.append(data.stableKey);
    } else if (data.uri != null) {
      String path = data.uri.toString();
      builder.ensureCapacity(path.length() + KEY_PADDING);
      builder.append(path);
    } else {
      builder.ensureCapacity(KEY_PADDING);
      builder.append(data.resourceId);
    }
    builder.append(KEY_SEPARATOR);

    if (data.rotationDegrees != 0) {
      builder.append("rotation:").append(data.rotationDegrees);
      if (data.hasRotationPivot) {
        builder.append('@').append(data.rotationPivotX).append('x').append(data.rotationPivotY);
      }
      builder.append(KEY_SEPARATOR);
    }
    if (data.hasSize()) {
      builder.append("resize:").append(data.targetWidth).append('x').append(data.targetHeight);
      builder.append(KEY_SEPARATOR);
    }
    if (data.centerCrop) {
      builder.append("centerCrop:").append(data.centerCropGravity).append(KEY_SEPARATOR);
    } else if (data.centerInside) {
      builder.append("centerInside").append(KEY_SEPARATOR);
    }

    if (data.transformations != null) {
      //noinspection ForLoopReplaceableByForEach
      for (int i = 0, count = data.transformations.size(); i < count; i++) {
        builder.append(data.transformations.get(i).key());
        builder.append(KEY_SEPARATOR);
      }
    }

    return builder.toString();
  }

解決方案:
按照地址生成key,保存bitmap最大的一張,取出bitmap后發(fā)現(xiàn)比當(dāng)前大,就Transformation。比請(qǐng)求的小,就再去請(qǐng)求一次原圖(這里有磁盤緩存),Transformation后保存原圖

  1. 及時(shí)調(diào)用Picasso.shutdown(),此方法會(huì)停止Picasso和清理緩存,只有在確定不需要使用Picasso時(shí)調(diào)用

Picasso如何防止傳入的View內(nèi)存泄露

  1. 弱引用View
  2. 引用隊(duì)列保留action,發(fā)現(xiàn)View被gc后取消請(qǐng)求
  • Picasso實(shí)例化時(shí)啟動(dòng)清理線程,初始化應(yīng)用對(duì)象
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
      RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
      Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
...省略
   //創(chuàng)建引用隊(duì)列
    this.referenceQueue = new ReferenceQueue<>();
    this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);
    //啟動(dòng)清理線程
    this.cleanupThread.start();
  }
  • Action實(shí)例化時(shí)用WeakReference包裹View
Action(Picasso picasso, T target, Request request, int memoryPolicy, int networkPolicy,
      int errorResId, Drawable errorDrawable, String key, Object tag, boolean noFade) {
    //使用WeakReference持有View,并傳入引用隊(duì)列,當(dāng)View被GC時(shí)當(dāng)前RequestWeakReference對(duì)象會(huì)被放入referenceQueue中
    this.target =
        target == null ? null : new RequestWeakReference<>(this, target, picasso.referenceQueue);
...省略
  }
  • RequestWeakReference中有一個(gè)請(qǐng)求的Action
  static class RequestWeakReference<M> extends WeakReference<M> {
    //請(qǐng)求action
    final Action action;

    RequestWeakReference(Action action, M referent, ReferenceQueue<? super M> q) {
      super(referent, q);
      this.action = action;
    }
  }

-清理線程,不停的從引用隊(duì)列中取出RequestWeakReference對(duì)象,回收RequestWeakReference對(duì)象

  private static class CleanupThread extends Thread {
    private final ReferenceQueue<Object> referenceQueue;
    private final Handler handler;

    CleanupThread(ReferenceQueue<Object> referenceQueue, Handler handler) {
      //應(yīng)用隊(duì)列
      this.referenceQueue = referenceQueue;
      this.handler = handler;
      setDaemon(true);
      setName(Utils.THREAD_PREFIX + "refQueue");
    }

    @Override public void run() {
      Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
      while (true) {
        try {
          //回收RequestWeakReference對(duì)象,并發(fā)送消息給Handler取消Action
          //remove超時(shí)時(shí)間1s,如果1s任然為null,則直接返回null
          Action.RequestWeakReference<?> remove =
              (Action.RequestWeakReference<?>) referenceQueue.remove(Utils.THREAD_LEAK_CLEANING_MS);
          Message message = handler.obtainMessage();
          if (remove != null) {
            message.what = REQUEST_GCED;
            message.obj = remove.action;
            handler.sendMessage(message);
          } else {
            message.recycle();
          }
        } catch (InterruptedException e) {
          break;
        } catch (final Exception e) {
          handler.post(new Runnable() {
            @Override public void run() {
              throw new RuntimeException(e);
            }
          });
          break;
        }
      }
    }

    void shutdown() {
      interrupt();
    }
  }
  • Action取消請(qǐng)求
  case REQUEST_GCED: {
          Action action = (Action) msg.obj;
          if (action.getPicasso().loggingEnabled) {
            Utils.log(Utils.OWNER_MAIN, Utils.VERB_CANCELED, action.request.logId(), "target got garbage collected");
          }
          action.picasso.cancelExistingRequest(action.getTarget());
          break;
        }

整個(gè)清理流程:

  1. View被RequestWeakReference對(duì)象持有, RequestWeakReference被Action持有,Action也被RequestWeakReference持有
  2. RequestWeakReference內(nèi)的持有對(duì)象被回收后,加入到引用隊(duì)列ReferenceQueue
  3. CleanupThread在Picasso創(chuàng)建時(shí)被啟動(dòng),不停的從ReferenceQueue對(duì)象中移除RequestWeakReference對(duì)象
  4. 在移除對(duì)象時(shí)得到RequestWeakReference中action對(duì)象,發(fā)送消息取消Action
  5. Action和RequestWeakReference是相互引用關(guān)系,其它地方都已經(jīng)釋放,所以都可以被gc了
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容