前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Picasso源码解析

Picasso源码解析

原创
作者头像
笔头
发布2022-04-03 16:55:20
5550
发布2022-04-03 16:55:20
举报
文章被收录于专栏:Android记忆Android记忆

一、介绍

一句话概括:Picasso 收到加载及显示图片的任务,创建 RequestCreator 并将它交给 Dispatcher,Dispatcher 创建 BitmapHunter (并为它找到具体的 RequestHandler) 提交到线程池,BitmapHunter 调用具体 RequestHandler,任务通过 MemoryCache 及 Handler(数据获取接口) 获取图片,图片获取成功后通过 PicassoDrawable 显示到 Target 中。

我们这次源码解析,以【网络请求】,【请求成功处理】 为切入点。

二、源码解析

我们来个简单案例来看其过程源码。

代码语言:javascript
复制
Picasso.with(MainActivity.this).load("http://nuuneoi.com/uploads/source/playstore/cover.jpg").into(imageView, new Callback() {
    @Override
    public void onSuccess() {
    }
    @Override
    public void onError() {
    }
});

我们主要看with、load、into这三个源码。

一、with

代码语言:javascript
复制
public static Picasso with(Context context) {
  if (singleton == null) {
    synchronized (Picasso.class) {
      if (singleton == null) {
        singleton = new Builder(context).build();
      }
    }
  }
  return singleton;
}

with方法是创建Picasso的实例,我们可以看到,with里面用了双重加锁的方式,保证了创建Picasso实例线程安全,而且Picasso实例是用Builder模式创建的,在多个属性情况下,Builder模式比较方便。来看下具体源码。

代码语言:javascript
复制
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);

  return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
      defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}

这个里面主要初始化了Downloader、LruCache、PicassoExecutorService、Stats、Dispatcher,最后返回了Picasso实例。

我们大致看下他们具体内容

Downloader

代码语言:javascript
复制
static Downloader createDefaultDownloader(Context context) {
  try {
    Class.forName("com.squareup.okhttp.OkHttpClient");
    return OkHttpLoaderCreator.create(context);
  } catch (ClassNotFoundException ignored) {
  }
  return new UrlConnectionDownloader(context);
}

可以看到,先使用java反射机制查找是否含有OkHttpClient,没有的话就生成UrlConnectionDownloader。okhttp已改名okhttp3,这里肯定找不到的,所以我们download最后是UrlConnectionDownloader。

LruCache 这是个缓存相关的类

PicassoExecutorService 是个线程池

Stats 是用来保存图片相关状态信息

Dispatcher 是个控制的中心,控制线程的加载和取消、网络监听、消息处理等。这个里面参数有个HANDLER,是主线程的Handler,后面从子线程切换到主线程就是通过它实现。

最后调用了return new Picasso,我们看下源码,里面有一些关键信息

代码语言:javascript
复制
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
    RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
    Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
  this.context = context;
  this.dispatcher = dispatcher;
  this.cache = cache;
  this.listener = listener;
  this.requestTransformer = requestTransformer;
  this.defaultBitmapConfig = defaultBitmapConfig;

  int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.
  int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
  List<RequestHandler> allRequestHandlers =
      new ArrayList<RequestHandler>(builtInHandlers + extraCount);
  allRequestHandlers.add(new ResourceRequestHandler(context));
  if (extraRequestHandlers != null) {
    allRequestHandlers.addAll(extraRequestHandlers);
  }
  allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
  allRequestHandlers.add(new MediaStoreRequestHandler(context));
  allRequestHandlers.add(new ContentStreamRequestHandler(context));
  allRequestHandlers.add(new AssetRequestHandler(context));
  allRequestHandlers.add(new FileRequestHandler(context));
  allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
  requestHandlers = Collections.unmodifiableList(allRequestHandlers);

这里我们要注意requestHandlers,后面BitmapHunter的forRequest方法中将会使用。我们这次是以网络请求为切入点,后面使用的requestHandler就是NetworkRequestHandler。

Picasso 里面还有个HANDLER,用来子线程切换主线程使用的,后面将使用。

总结下,Picasso主要就是各种初始化,以及以及一些工具api。

with就是使用Picasso前的做了一些配置准备。接下来看下load方法

load

代码语言:javascript
复制
public RequestCreator load(Uri uri) {
  return new RequestCreator(this, uri, 0);
}

他是直接返回一个RequestCreator对象,我们看看他是什么。

代码语言:javascript
复制
RequestCreator(Picasso picasso, Uri uri, int resourceId) {
  if (picasso.shutdown) {
    throw new IllegalStateException(
        "Picasso instance already shut down. Cannot submit new requests.");
  }
  this.picasso = picasso;
  this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}
代码语言:javascript
复制
Builder(Uri uri, int resourceId, Bitmap.Config bitmapConfig) {
  this.uri = uri;
  this.resourceId = resourceId;
  this.config = bitmapConfig;
}

我们可以看到RequestCreator,里面包含了picasso对象,还有图片相关配置等等。

load方法主要功能就是获取请求uri以及图片配置。

with和load,还比较简单,基本都是在请求前做一些准备工作。接下来看下into

into 源码

代码语言:javascript
复制
public void into(ImageView target, Callback callback) {
  long started = System.nanoTime();
  //-------------------------1-------------------
  checkMain();

  if (target == null) {
    throw new IllegalArgumentException("Target must not be null.");
  }

  if (!data.hasImage()) {
    picasso.cancelRequest(target);
    if (setPlaceholder) {
      setPlaceholder(target, getPlaceholderDrawable());
    }
    return;
  }

  if (deferred) {
    if (data.hasSize()) {
      throw new IllegalStateException("Fit cannot be used with resize.");
    }
    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;
    }
    data.resize(width, height);
  }

  //------------------------------2------------------------------
  Request request = createRequest(started);
  String requestKey = createKey(request);

  if (shouldReadFromMemoryCache(memoryPolicy)) {
    Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
    if (bitmap != null) {
      picasso.cancelRequest(target);
      setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
      if (picasso.loggingEnabled) {
        log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
      }
      if (callback != null) {
        callback.onSuccess();
      }
      return;
    }
  }

   //-----------------------------3-----------------------------
  if (setPlaceholder) {
    setPlaceholder(target, getPlaceholderDrawable());
  }
  
  Action action =
      new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
          errorDrawable, requestKey, tag, callback, noFade);

  picasso.enqueueAndSubmit(action);
}

这份代码主要分成三块。

第一块,各种检查,检查是不是在主线程上,检查图片是否有数据等等。

第二块,是判断是否从缓存获取图片,如果是,则从缓存获取图片,整个流程结束!

第三块,如果不从缓存获取图片,则准备请求网络。先看下是否设置placeholder,有的话设置placeholder,然后创建ImageViewAction,ImageViewAction整合了picasso、target、request等请求信息,同时还设置结果回调处理。最后调用了picasso.enqueueAndSubmit(action)发起请求。

代码语言:javascript
复制
void enqueueAndSubmit(Action action) {
  Object target = action.getTarget();
  if (target != null && targetToAction.get(target) != action) {
    // This will also check we are on the main thread.
    cancelExistingRequest(target);
    targetToAction.put(target, action);
  }
  submit(action);
}
void submit(Action action) {
  dispatcher.dispatchSubmit(action);
}
void dispatchSubmit(Action action) {
  handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}

上面我们可以发现,主要分两块。

第一块,是判断要不要把请求Action添加到targetToAction中。

第二块,是调用Dispatcher的dispatchSubmit,提交请求,最终是通过handler发送提交请求消息,发起请求。 这里的handler,通过源码我们发现

代码语言:javascript
复制
this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);

这里的dispatcherThread是一个HandlerThread,那我们知道此时的this.handler是非UI线程的handler。我们继续看handler的消息是如何处理的。

代码语言:javascript
复制
@Override public void handleMessage(final Message msg) {
  switch (msg.what) {
    case REQUEST_SUBMIT: {
      Action action = (Action) msg.obj;
      dispatcher.performSubmit(action);
      break;
    }

最终是调用了

代码语言:javascript
复制
void performSubmit(Action action, boolean dismissFailed) {
  ..........................
  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;
  }
  //-------------------1-----------------------
  hunter = forRequest(action.getPicasso(), this, cache, stats, action);
  hunter.future = service.submit(hunter);
  hunterMap.put(action.getKey(), hunter);
  if (dismissFailed) {
    failedActions.remove(action.getTarget());
  }
 .....................
}

这里主要调用了forRequest,返回一个BitmapHunter,这个是什么?我们看源码,他是个Runnable。service是PicassoExecutorService是个线程池,那service.submit(hunter)就很容易理解啦,我们直接看BitmapHunter的run方法。

代码语言:javascript
复制
@Override public void run() {
  try {
    ...........................
    result = hunt();

    if (result == null) {
      dispatcher.dispatchFailed(this);
    } else {
      dispatcher.dispatchComplete(this);
    }
  } catch (Downloader.ResponseException e) {
    if (!e.localCacheOnly || e.responseCode != 504) {
      exception = e;
    }
    dispatcher.dispatchFailed(this);
  } catch (NetworkRequestHandler.ContentLengthException e) {
    exception = e;
    dispatcher.dispatchRetry(this);
  } catch (IOException e) {
    exception = e;
    dispatcher.dispatchRetry(this);
  } catch (OutOfMemoryError e) {
    StringWriter writer = new StringWriter();
    stats.createSnapshot().dump(new PrintWriter(writer));
    exception = new RuntimeException(writer.toString(), e);
    dispatcher.dispatchFailed(this);
  } catch (Exception e) {
    exception = e;
    dispatcher.dispatchFailed(this);
  } finally {
    Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);
  }
}

这份代码主要也是两块,

第一个是执行hunt(),应该是个请求函数,下面我们继续看。

第二个,就是不断的catch,然后通过dispatcher来处理。

我们先看下hunt()

代码语言:javascript
复制
Bitmap hunt() throws IOException {
  Bitmap bitmap = null;
  if (shouldReadFromMemoryCache(memoryPolicy)) {
    bitmap = cache.get(key);
    if (bitmap != null) {
      stats.dispatchCacheHit();
      loadedFrom = MEMORY;
      ...........
      return bitmap;
    }
  }

  data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
  RequestHandler.Result result = requestHandler.load(data, networkPolicy);
  if (result != null) {
    loadedFrom = result.getLoadedFrom();
    exifRotation = result.getExifOrientation();
    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 (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;
}

我们可以看到,图片请求先判断是否从缓存取,如果需要,则从缓存拿数据,拿到数据后,则返回bitmap,结束。如果不从缓存拿数据,就通过requestHandler.load获取返回数据。

到目前为止,我们知道如何请求数据,接口调用必须在UI线程中,请求操作在handler子线程,请求处理我们是通过线程池来处理的。在这过程中,如果可以从缓存获取数据,就从缓存取,否则通过线程池请求图片,这个线程池能开几个线程,取决于当前网络情况,由于篇幅我这里没有写,好奇的可以自行看下代码。

好了,下面我们来继续看requestHandler.load,这个操作在线程池中执行的。这个requestHandler是哪里来的呢?这个参数是在实例BitmapHunter带过来的,我们看下是在哪里。

代码语言:javascript
复制
static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,
    Action action) {
  Request request = action.getRequest();
  List<RequestHandler> requestHandlers = picasso.getRequestHandlers();
  for (int i = 0, count = requestHandlers.size(); i < count; i++) {
    RequestHandler requestHandler = requestHandlers.get(i);
    if (requestHandler.canHandleRequest(request)) {
      return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
    }
  }
  return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
}

这里的picasso.getRequestHandlers()是在哪里生成的?这里是在Picasso实例化的时候

代码语言:javascript
复制
allRequestHandlers.add(new ResourceRequestHandler(context));
if (extraRequestHandlers != null) {
  allRequestHandlers.addAll(extraRequestHandlers);
}
allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
allRequestHandlers.add(new MediaStoreRequestHandler(context));
allRequestHandlers.add(new ContentStreamRequestHandler(context));
allRequestHandlers.add(new AssetRequestHandler(context));
allRequestHandlers.add(new FileRequestHandler(context));
allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));

每个RequestHandler是一个请求处理方式,有网络请求,有文件等等。。假如我们现在是从网络请求,那此时的requestHandler就是NetworkRequestHandler,我们看下其load方法

代码语言:javascript
复制
@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;
  }
  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);
}

这里会把请求的数据包装在Result中

代码语言:javascript
复制
if (result == null) {
  dispatcher.dispatchFailed(this);
} else {
  dispatcher.dispatchComplete(this);
}

如果result!=null,那表示请求成功,我们这就看成功吧,其他状态处理基本类似。

继续看下 dispatcher.dispatchComplete(this);他会执行handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));我们发现dispatcher处理都是在子线程中执行的。在子线程Handler中

代码语言:javascript
复制
case HUNTER_COMPLETE: {
  BitmapHunter hunter = (BitmapHunter) msg.obj;
  dispatcher.performComplete(hunter);
  break;
}

继续执行dispatcher.performComplete(hunter);

代码语言:javascript
复制
void performComplete(BitmapHunter hunter) {
  if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
    cache.set(hunter.getKey(), hunter.getResult());
  }
  hunterMap.remove(hunter.getKey());
  batch(hunter);
  if (hunter.getPicasso().loggingEnabled) {
    log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), "for completion");
  }
}

请求成功的话,1.要不要缓存 2.把当前请求完成的hunter从hunterMap移除,不然假如后面再次网络请求这个相同图片的话,就不会处理了,原因可以看下performSubmit方法。3.执行batch

到这里,整个请求流程都处理完毕,处理数据Result也拿到。接下来就是把bitmap显示在屏幕上吧,也就是从子线程转移到UI主线程。

这个在哪执行的呢,就是在batch方法里

代码语言:javascript
复制
private void batch(BitmapHunter hunter) {
  if (hunter.isCancelled()) {
    return;
  }
  batch.add(hunter);
  if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
    handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
  }
}

理解不难,这里的handler还是子线程。继续

代码语言:javascript
复制
case HUNTER_DELAY_NEXT_BATCH: {
  dispatcher.performBatchComplete();
  break;
}

继续

代码语言:javascript
复制
void performBatchComplete() {
  List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);
  batch.clear();
  mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
}

哦,看到了,这里出现了主线程中的mainThreadHandler,他项主线程MessageQueue发送了消息。我们找下mainThreadHandler。还记得创建Dispatcher时里面参数HANDLER吗?就是他。

代码语言:javascript
复制
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;
      }

HUNTER_BATCH_COMPLETE条件下,不断遍历所有BitmapHunter,继续看下picasso.complete(hunter)

代码语言:javascript
复制
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();

  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

代码语言:javascript
复制
private void deliverAction(Bitmap result, LoadedFrom from, Action action) {
  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(result, from);
  } else {
    action.error();
  }
}

这里主要看下action.complete(result, from);此时的action是谁,是ImageViewAction,那我们看下其complete

代码语言:javascript
复制
@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;
  PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);
  if (callback != null) {
    callback.onSuccess();
  }
}

这里应该是终结了,PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);把bitmap显示在target上了,同时调用了回调callback.onSuccess();

Picasso整个流程就是这样。看一两遍可能还是云里雾里,结合下面图看可能更快的理解。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、介绍
  • 二、源码解析
    • 一、with
    • load
    • into 源码
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档