Picasso源码解析及优化见解

Picasso是一款图片加载库出自Square,以小巧功能齐全出名,本文将从源码解析Picasso的整个加载流程。


Picasso时序图

时序图为笔者根据整个调用流程所画,有误私聊笔者进行修改

整个流程

  • 调用Picasso创建一个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设置请求参数,url、资源id、transform、fix等
  • 调用RequestCreator的into,设置了fix的情况下,如果控件已经获取到尺寸就创建Action,否则就创建一DeferredRequestCreator(延迟请求),获取到后重新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();
    //控件已经获取到宽高,重新into
    this.creator.unfit().resize(width, height).into(target, callback);
    return true;
  }

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

  • 调用Picasso.enqueueAndSubmit(Action)->Dispatcher.performSubmit(Action)->ExecutorService.submit(BitmapHunter)将请求提交给线程池去处理
  void performSubmit(Action action, boolean dismissFailed) {
    //已暂停的,就结束了
    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;
    }
    //通过请求key获取BitmapHunter核心类,这一步的主要作用是,一个图片有两个控价都需要使用她,
    //避免重复请求。这里的attach方法就是将action,添加当前这个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;
    }
    //策略模式,选取可以请求该种类型
   //根据传入的action中的Request资源地址来判定使用那种请求处理器
    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) {
      Utils.log(Utils.OWNER_DISPATCHER, Utils.VERB_ENQUEUED, action.request.logId());
    }
  }
  • BitmapHunter执行run方法获取图片资源,run方法调用hunt获取bitmap,并执行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)) {
      //根据请求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;
      }
    }
    //没有缓存或者不允许读取缓存,继续往下执行
    
    networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
    //请求处理器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 {
          //将流文件转为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());
      }
      //状态更新
      stats.dispatchBitmapDecoded(bitmap);
      //是否需要transformation,必须调用RequestCreator的fix()方法,默认的Transformation才会被调用。会根据获取到控件的size调整图
      if (data.needsTransformation() || exifOrientation != 0) {
        synchronized (DECODE_LOCK) {
          if (data.needsMatrixTransform() || exifOrientation != 0) {
            //默认的transformation,会根据控件大小调整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;
  }

从上面的源码可知缓存逻辑:内存-->本地缓存(由okhttp提供)-->网络,Picasso一共提供了7图片请求处理器,这里着重介绍哈网络处理器
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;
  }
  //这个处理器,可以处理那种类型的资源,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 {
    //创建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);
    }
    //资源来源,网络/磁盘
    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());
    }
    //返回结果
    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;

  //初始化获设置缓存目录默认:/data/data/包名/cache/picasso-cache下
  public OkHttp3Downloader(final Context context) {
    this(Utils.createDefaultCacheDir(context));
  }

  //初始化获设置缓存
  public OkHttp3Downloader(final File cacheDir) {
    this(cacheDir, Utils.calculateDiskCacheSize(cacheDir));
  }

  //初始化获设置缓存
  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执行同步方法去加载网络上的图片,是否使用本地缓存的图片,这里有okhttp去实现
  //如果需要更换网络访问框架,需要实现1.网络下载图片; 2. 缓存图片到本地,下次请求时,返回缓存图片
  @NonNull @Override public Response load(@NonNull Request request) throws IOException {
    return client.newCall(request).execute();
  }
  ...省略
}
  • Dispatcher.dispatchComplete最终调用了performComplete方法
  void performComplete(BitmapHunter hunter) {
    //是否缓存到内次
    if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
      cache.set(hunter.getKey(), hunter.getResult());
    }
    //移除进行时的bitmap请求
    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的预画
      hunter.result.prepareToDraw();
    }
    batch.add(hunter);
    if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
      handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
    }
  }

最终调用到了调用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.");
      }
     //调用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;
  }
  //加载完成时
  @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;
    //设置bitmap到target,也就是我们的目标view
    PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);
    //回调
    if (callback != null) {
      callback.onSuccess();
    }
  }
  
  //加载失败时
  @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加载流程就已经解析完了,网络和本地缓存都依赖OKhttp,我们整个项目图片加载不多,项目中使用的OKhttp,所以我选用了Picasso来减少代码量。

Picasso需要优化点

  1. 内存缓存,可以看到是通过requestKey+Bitmap存入LruCache,但是requestkey上带有尺寸旋转角度等参数,也就是说同一张图片,因为尺寸等参数不同就会造成储存了多张bitmap,bitmap(吃内存大户啊)
    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后发现比当前大,就Transformation。比请求的小,就再去请求一次原图(这里有磁盘缓存),Transformation后保存原图

  1. 及时调用Picasso.shutdown(),此方法会停止Picasso和清理缓存,只有在确定不需要使用Picasso时调用

Picasso如何防止传入的View内存泄露

  1. 弱引用View
  2. 引用队列保留action,发现View被gc后取消请求
  • Picasso实例化时启动清理线程,初始化应用对象
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
      RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
      Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
...省略
   //创建引用队列
    this.referenceQueue = new ReferenceQueue<>();
    this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);
    //启动清理线程
    this.cleanupThread.start();
  }
  • Action实例化时用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,并传入引用队列,当View被GC时当前RequestWeakReference对象会被放入referenceQueue中
    this.target =
        target == null ? null : new RequestWeakReference<>(this, target, picasso.referenceQueue);
...省略
  }
  • RequestWeakReference中有一个请求的Action
  static class RequestWeakReference<M> extends WeakReference<M> {
    //请求action
    final Action action;

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

-清理线程,不停的从引用队列中取出RequestWeakReference对象,回收RequestWeakReference对象

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

    CleanupThread(ReferenceQueue<Object> referenceQueue, Handler handler) {
      //应用队列
      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对象,并发送消息给Handler取消Action
          //remove超时时间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取消请求
  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;
        }

整个清理流程:

  1. View被RequestWeakReference对象持有, RequestWeakReference被Action持有,Action也被RequestWeakReference持有
  2. RequestWeakReference内的持有对象被回收后,加入到引用队列ReferenceQueue
  3. CleanupThread在Picasso创建时被启动,不停的从ReferenceQueue对象中移除RequestWeakReference对象
  4. 在移除对象时得到RequestWeakReference中action对象,发送消息取消Action
  5. Action和RequestWeakReference是相互引用关系,其它地方都已经释放,所以都可以被gc了
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容