Picasso 源码解析

概述

Picasso是大名鼎鼎的Square公司提供的一个适用于Android的强大的图片下载缓存库。

简单使用

Picasso.get()
    .load(url)
    .resize(50, 50)
    .centerCrop()
    .into(imageView)

流程

先简单总结下Picasso加载图片的流程,后面再详细解析:

  1. 通过Picasso#get()方法创建一个Picasso的实例,它是用单例模式+Builder模式实现的。使用默认方式创建Picasso实例时,它会创建默认的缓存、下载器、线程池、监控器、图片处理器集合等;
  2. 通过load(uri)方法将图片的uri传入,创建RequestCreator实例;
  3. 调用RequestCreator#into()方法,它会先从缓存中检索是否有该图片,有的话直接返回;
  4. 缓存中没有,则创建请求任务Action对象,并调用Picasso#enqueueAndSubmit(action)方法提交请求任务,而 Picasso会调用Dispatcher#dispatchSubmit(action)方法,由Dispatcher去处理;
  5. Dispatcher维护了一个子线程和消息队列,在主线程发送消息到子线程的消息队列,然后在子线程中调用Dispatcher#performSubmit(action)方法处理请求;
  6. 处理请求时,首先需要构建BitmapHunter对象,它继承自Runnable,可被线程池执行,构建时会选用合适的RequestHandler来处理请求,网络请求会使用NetworkRequestHandler,最后将BitmapHunter对象扔到线程池中,等待执行;
  7. BitmapHunter执行时会再次检索缓存查看是否有请求的图片,有就返回图片;没有再通过RequestHandler来获取图片;
  8. 获取到请求结果后,Dispatcher会对请求做批处理,延迟200ms再将这一批结果通过mainThreadHandler发送给主线程;
  9. Picasso在主线程接收到消息后,会遍历结果列表,一一调用Action对象的complete()方法将图片显示在ImageView上;如果请求失败则调用的是Action#error(exception)方法。

源码解析

本文使用的是2.71828版本的代码。

Picasso

Picasso是图片下载、变换和缓存管理器。

初始化

我们在使用的时候,都需要通过Picasso.get()方法来获取一个Picasso对象,很容易可以知道它是单例的存在,这里使用了单例模式中的双重锁模式:

static volatile Picasso singleton = null;
public static Picasso get() {
    if (singleton == null) {
        synchronized (Picasso.this) {
            if (singleton == null) {
                if (PicassoProvider.context == null) {
                    throw new IllegalStateException("context == null");
                }
                singleton = new Builder(PicassoProvider.context).build();
            }
        }
    }
    return singleton;
}

构建Picasso对象时,使用的是PicassoProvidercontext对象,PicassoProvider继承自ContentProvider。构建时,采用了Builder模式,我们来看下Builderbuild()方法做了哪些事情。

public Picasso build() {
    Context context = this.context;
    // 创建默认的下载器,默认使用OkHttp3
    if (downloader == null) {
        = new OkHttp3Downloader(context);
    }
    // 创建默认的缓存
    if (cache == null) {
        cache = new LruCache(context);
    }
    // 创建默认的线程池,PicassoExecutorService继承自ThreadPoolExecutor
    if (service == null) {
        service = new PicassoExecutorService();
    }
    // 创建默认的Transformer
    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);
}

可以看到build()方法就是创建一堆基本配置:

  • 创建默认的下载器,默认使用OkHttp3 ,可通过.downloader(downloader)指定下载器。
  • 创建默认的缓存,默认使用LruCache, 可通过.memoryCache(memoryCache)指定缓存。
  • 创建默认的线程池,PicassoExecutorService,继承自ThreadPoolExecutor,默认有3个工作线程,可通过.executor(executorService)指定线程池。
  • 创建默认的RequestTransformerRequestTransformer是个接口,它的唯一方法transformRequest(request)将在请求提交之前被调用,默认实现不做任何事,直接返回请求,可自行实现该接口并使用。
  • 创建默认的监控器,用来统计缓存命中率、下载时长等,不支持自定义。
  • 创建分发器,不支持自定义
  • 创建默认的处理器集合,即RequestHandler,用于处理不同的加载请求,可通过addRequestHandler(requestHandler)添加。

处理器的初始化在Picasso的构造函数中初始化,来看下默认都包含哪些处理器:

// 默认有7个handler
int builtInHandlers = 7;
// 开发者添加的handler个数
int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
List<RequestHandler> allRequestHanadlers = new ArrayList<>(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));
// 添加asset请求处理器
allRequestHandlers.add(new AssetRequestHandler(context));
// 添加文件请求处理器
allRequestHandlers.add(new FileRequestHandler(context));
// 添加网络请求处理器
allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
requestHandlers = Collections.unmodifiableList(allRequestHandlers);

从命名就可以看出这些处理器分别可以从资源、Asset、File、网络等地方加载图片。

load

初始化完成后获得一个Picasso对象,然后使用load(uri)方法设置图片uri

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

这里仅仅做了一件事,就是创建RequestCreator对象并返回。RequestCreator顾名思义就是用来构建图片下载请求的。

private final Picasso picasso;
private final Request.Builder data;

RequestCreator(Picasso picasso, Uri uri, int resourceId) {
    if (picasso.shutdown) {
        throw new IllegalStateException(
                "Picasso instance already shut down. Cannot submit new requests.");
    }
    this.picass = picasso;
    this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}

RequestCreator持有Picasso对象引用,以及一个Request.Builder对象,那它是怎么创建图片请求的呢?来看下Request.Builder做了什么。

Builder(Uri uri, int resourceId, Bitmap.Config bitmapConfig) {
    this.uri = uri;
    this.resourceId = resourceId;
    this.config = bitmapConfig;
}

可以看到这里就是做了一些赋值操作,没有什么特别的,我们接着往下看。

into

设置好一堆配置之后,最后我们会调用into(target)方法,就发送了一次请求。into()方法是在RequestCreator中实现的,因为上面load()之后我们得到的是RequestCreator对象。Picasso支持将图片加载到ImageViewRemoteViewTarget等对象上面,这里我们以ImageView为例来解读代码。

public void into(ImageView target) {
    into(target, null);
}
// 支持回调,这里的回调是强引用,可能导致Activity/Fragment内存不能被释放
public void into(ImageView target, Callback callback) {
    // 请求开始时间
    long started = System.nanoTime();
    // 检查当前是否主线程,如果不是,抛出异常
    checkMain();
    // target为空,抛出异常
    if (target == null) {
        throw new IllegalArgumentException("Target must not be null.");
    }
    // 检查data中有没有图片的uri或resourceId
    if (!data.hasImage()) {
        // 没有uri或资源id,取消此次请求
        picasso.cancelRequest(target);
        // 显示预留图片
        if (setPlaceholder) {
            setPlaceholder(target, getPlaceholderDrawable());
        }
        return;
    }
    // 是否调用了fit(),表示要将图片调整为ImageView的大小
    if (deferred) {
        // fit不能与resize一起使用
        if (data.hasSize()) {
            throw new IllegalStateException("Fit cannot be used with resize.");
        }
        // 获取ImageView的大小
        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);
    }
    // 构建请求
    Request request = createRequest(started);
    // 构建请求对应的key
    String requestKey = createKey(request);
    // 检查是否支持从缓存中读取
    if (shouldReadFromMemoryCache(memoryPolicy)) {
        Bitmap  bitmap = picasso.quickMemoryCacheCheck(requestKey);
        if (bitmap != null) {
            // 缓存命中,不再发送请求,并将图片显示到ImageView上
            picass.cancelRequest(target);
            setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
            if (callback != null) {
                callback.onSuccess();
            }
            return;
        }
    }
    if (setPlaceholder) {
        setPlaceholder(target, getPlaceholderDrawable());
    }
    // 缓存未命中,创建请求任务Action,并将请求任务交给dispatcher处理
    Action action = 
            new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
                    errorDrawable, requestKey, tag, callback, noFade);
    picasso.enqueueAndSubmit(action);
}

首先会先从缓存中获取图片,缓存命中则直接显示图片,不再发送请求;缓存未命中才发送请求。
这里可能有个疑问,已经有了Request,为什么还有个Action,我们通过它们的参数看看它们之间的区别。

public final class Request {
    int id;    // 请求的唯一识别id
    public final Uri uri;    // 图片的URI
    public final int resourceId;    // 图片的资源ID
    public final List<Transformation> transformations;
    public final int targetWidth;    // 用于resize的目标宽度
    public final int targetHeight;    // 用于resize的目标高度
    public final boolean centerCrop;    // 是否使用"centerCrop"缩放
    public final int centerCropGravity;
    public final boolean centerInside;
    public final float rotationDegrees;
    public final float rotationPivotX;
    public final float rotationPivotY;
    ...    // 各种图片变换参数
}
abstract class Action<T> {
    final Picasso picasso;
    final Request request;
    // 持有target的弱引用,不会因为长时间持有导致内存泄漏
    final WeakReference<T> target;
    final boolean noFade;  
    final int memoryPolicy;  
    final int networkPolicy;
    final int errorResId;
    final Drawable errorDrawable;
    final String key;
    final Object tag;
    boolean willReplay;
    boolean cancelld;
}

通过两个类的参数可以发现,Request关注的是请求本身,比如请求的uri、id、大小、变换参数等,而Action代表的则是任务本身,因此需要Picasso对象的引用,也需要Request对象的引用,同时还有一些网络策略、缓存策略,是否重载、是否取消等。
需要注意的是,Action中持有的是Target对象的弱引用,不影响Target对象的回收。
接下来我们看下enqueueAndSubmit()方法。

final Map<Object, Action> targetToAction;
...
this.targetToAction = new WeakHashMap<>();
...
void enqueueAndSubmit(Action action) {
    Object target = action.getTarget();
    if (target != null && targetToAction.get(target) != action) {
        cancelExistingRequest(target);
        targetToAction.put(target, action);
    }
    submit(action);
}

targetToAction是一个TargetAction的映射表,表示一个目标上只允许有一个任务存在。如果当前Target对象有请求任务的话,就停止之前的任务,再重新添加关联并提交请求。

void submit(Action action) {
    dispatcher.dispatchSubmit(action);
}

请求最终交由Dispatcher进行分发处理。

Dispatcher

在分析Dispatcher分发请求之前,我们先来了解下Dispatcher的工作环境。
首先,我们回顾下当初是怎么创建Dispatcher对象的:

Dispatcher dispatcher = new Dispatcher(context, service, HANDLER,   
            downloader, cache, stats);

可以看到Dispatcher需要关联线程池service、主线程的HANDLER、下载器downloader、缓存cache、监控器stats等。
先来看下线程池,默认使用PicassoExecutorService,继承自ThreadPoolExecutor,默认使用3个线程。但是Dispatcher注册了监听网络变化的广播接收器,用以监控网络变化,一旦网络发生变化,会触发PicassoEexcutorService调整线程数量:

  • Wifi情况:4个线程
  • 4G网络:3个线程
  • 3G网络:2个线程
  • 2G网络:1个线程
  • 其他:默认3个线程

回到Dispatcher,来看下它的工作线程dispatcherThreadhandler

class Dispatcher {
    ...
    final DispatcherThread dispatcherThread;
    ...
    final Handler handler;
    ...
    this.dispatcherThread = new DispatcherThread();
    this.dispatcherThread.start();
    this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
    ...
    private static class DispatcherHandler extends Handler {
        private final Dispatcher dispatcher;
        DispatcherHandler(Looper looper, Dispatcher dispatcher) {
            super(looper);
            this.dispatcher = dispatcher;
        }
        @Override
        public void handleMessage(final Message msg) {
            ...
        }
    }

    static class DispatcherThread extends HandlerThread {
        DispatcherThread() {
            super(Utils.THREAD_PREFIX + DISPATCHER_THREAD_NAME, THREAD_PRIORITY_BACKGROUND);
        }
    }
}

可以看到Dispatcher创建了一个DispatcherThread子线程,DispatcherThread继承自HandlerThread,然后在子线程的Looper对象引用传给了handler,即handler是在子线程处理消息的。那么我们可以合理推测Dispatcher的工作都是在子线程中进行的,是不是这样呢?我们接着看dispatchSubmit()

void dispatchSummit(Action action) {
    handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}

果不其然,Dispatcher在主线程发送消息到子线程的消息队列,然后在子线程处理这些消息。

case REQUEST_SUBMIT: {
    Action action = (Action) msg.obj;
    dispatcher.performSubmit(action);
    break;
}

最终提交请求的工作交给了DispatcherperformSubmit()来进行,继续看。

void performSubmit(Action action) {
    performSubmit(action, true);
}

void performSubmit(Action action, boolean dismissFailed) {
    // 如果请求已暂停,不分发
    if (pausedTags.contains(action.getTag())) {
        pausedActions.put(action.getTarget(), action);
        return;
    }
    BitmapHunter hunter = hunterMap.get(action.getKey());
    if (hunter != null) {
        // 相同的请求已经在线程池中,不再重复发送,而是关联到之前的请求中
        hunter.attach(action);
        return;
    }
    // 线程池已经关闭,不再做处理
    if (service.isShutdown()) {
        return;
    }
    // 创建hunter
    hunter = forRequest(action.getPicasso(), this, cache, stats, action);
    hunter.future = service.submit(hunter);
    hunterMap.put(action.getKey(), hunter);
    if (dismissFailed) {
        failedActions.remove(action.getTarget());
    }
}

一通判断之后,我们需要创建一个BitmapHunter对象,它继承自Runnable,可被线程池执行。先来看下forRequest()方法做了什么。

static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache,
        Action action) {
    Request request = action.getRequest();
    List<RequestHandler> requestHandlers = picasso.getRequestHandlers();
    // 从Picasso初始化的请求处理器列表中找到合适的处理器来处理请求
    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);
}

这里主要是查找合适的RequestHandler,对Picasso初始化时创建的处理器列表requestHandlers进行遍历,调用RequestHandlercanHandleRequest()方法,判断该处理器能否处理这个请求,从而找出合适的处理器。如果处理器列表中没有合适的RequestHandler,则会使用ERRORING_HANDLER,它在load()请求时会抛出异常。
接下来看下BitmapHunterrun()方法。

@Override
public void run() {
    try {
        updateThreadName(data);
        result = hunt();
        if (result == null) {
            dispatcher.dispatchFailed(this);
        } else {
            dispatcher.dispatchComplete(this);
        }
    } catch (xxException e) {
        // 捕获了各种异常,ResponseException、IOException、
        // OutOMemoryError等
        ...
        dispatcher.dispatcherFailed(this)
    } finally {
        Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);
    }
}

真正处理请求的操作放在了hunt()方法,我们接着往下看。

Bitmap hunt() throws IOException {
    Bitmap bitmap = null;
    // 再次尝试从缓存中获取
    if (shouldReadFromMemoryCache(memoryPolicy)) {
        bitmap = cache.get(key);
        if (bitmap != null) {
            // cache hit
            stats.dispatchCacheHit();
            loadedFrom = MEMORY;
            return bitmap;
        }
    }
    // cache miss,通过RequestHandler加载图片
    networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
    // 调用RequestHandler的load()方法加载图片
    RequestHandler.Result result = requestHandler.load(data, networkPolicy);
    if (result != null) {
        loadedFrom = result.getLoadedFrom();
        exifOrientation = result.getExifOrientation();
        bitmap = result.getBitmap();
        // 如果没有Bitmap,需要对流进行解码
        if (bitmap == null) {
            Source source = result.getSource();
            try {
                bitmap = decodeStream(source, data);
            } finally {
                try {
                    source.close();
                } catch (IOException ignored) {
                }
            }
        }
    }
    if (bitmap != null) {
        stats.dispatchBitmapDecoded(bitmap);
        // 对图片进行变换操作
        if (data.needsTransformation() || exifOrientation != 0) {
            synchronized (DECODE_LOCK) {
                if (data.needsMatrixTransform() || exifOrientataion != 0) {
                    // 对图片进行矩阵变换
                    bitmap = transformResult(data, bitmap, exifOrientation);
                }
                if (data.hasCustomTransformations()) {
                    // 对图片进行自定义变换
                    bitmap = applyCustomTransformations(data.transformations, bitmap);
                }
            }
            if (bitmap != null) {
                stats.dispatchBitmapTransformed(bitmap);
            }
        }
    }
    return bitmap;
}

我们以NetworkRequestHandler为例,来看下load()方法做了哪些事情。

@Override
public Result load(Request request, int networkPolicy) throws IOExcetpion {
    okhttp3.Request downloaderRequest = createRequest(request, networkPolicy);
    Response response = downloader.load(downloaderRequest);
    ResponseBody body = response.body();
    if (!response.isSuccessful()) {
        body.close();
        throw new ResponseException(response.code(), request.networkPolicy);
    }
    // 判断加载来源,当且仅当响应完全来自网络时,cacheResponse为null
    Picasso.LoadedFrom loadedFrom = response.cacheResponse() == null ? NETWORK : DISK;
    if (loadedFrom == DISK && body.contentLength() = 0) {
        body.close();
        throw new ContentLengthExceptioin("Received response with 0 content-length header.");
    }
    if (loadedFrom == NETWORK && body.contentLength() > 0) {
        stats.dispatchDownloadFinished(body.contentLength());
    }
    return new Result(body.source(), loadedFrom);
}
  1. 调用downloader.load()方法获取响应结果,这里的downloader默认使用OkHttp3Downloader,也可在创建Picasso实例的时候自定义;
  2. 判断请求是否成功,不成功则抛出响应异常;
  3. 判断结果来源。因为默认使用OkHttp3Downloader,而OkHttp则使用DiskLruCache作为磁盘缓存。这里会判断响应是从磁盘缓存中读取的还是网络读取的;
  4. 如果结果来自磁盘缓存,且内容长度为0,抛出异常;
  5. 将请求结果封装成Result对象并返回。

结果分发

回顾下BitmapHunter#run()方法中,针对请求成功和失败的情况,分别调用了Dispatcher的不同方法:

// 请求成功
dispatcher.dispatchComplete(this);
// 请求结果为null或抛出异常
dispatcher.dispatchFailed(this);

来看下Dispatcher怎么做的。

void dispatchComplete(BitmapHunter hunter) {
    handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
}

void dispatchFailed(BitmapHunter hunter) {
    handler.sendMessage(handler.obtainMessage(HUNTER_DECODE_FAILED, hunter);
}

case HUNTER_COMPLETE: {
    BitmapHunter hunter = (BitmapHunter) msg.obj;
    dispatcher.performComplete(hunter);
    break;
}
case HUNTER_DECODE_FAILED: {
    BitmapHunter hunter = (BitmapHunter) msg.obj;
    dispatcher.performError(hunter, false);
    break;
}

void performComplete(BitmapHunter hunter) {
    // 判断是否需要写入内存缓存
    if (shouldWriteToMemoryCache(hunter.getMemoryPolicy()) {
        // 写入内存缓存
        cache.set(hunter.getKey(), hunter.getResult());
    }
    // 将该任务从任务表中移除
    hunterMap.remove(hunter.getKey());
    // 对结果进行批处理
    batch(hunter);
}

void performError(BitmapHunter hunter, boolean willReplay) {
    // 将该任务从任务表中移除
    hunterMap.remove(hunter.getKey());
    // 对结果进行批处理
    batch(hunter);
}

可以看到,请求成功时,会先判断是否需要内存缓存,需要的话则将结果缓存。而不论是成功还是失败,最终都会调用batch()方法对结果批处理。

private static final int BATCH_DELAY = 200;
final List<BitmapHunter> batch;
...
this.batch = new ArrayList<>(4);
...
private void batch(BitmapHunter hunter) {
    // 如果请求取消,不作处理
    if (hunter.isCancelled()) {
        return;
    }
    if (hunter.result != null) {
        hunter.result.prepareToDraw();
    }
    batch.add(hunter);
    if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
        handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
    }
}

首先将结果存入batch列表中,如果消息队列中没有HUNTER_DELAY_NEXT_BATCH消息,则发送一个延迟消息,默认200ms。在这期间,所有的结果都会被添加到batch列表中,时间到了再一并处理,可防止短时间内大量消息造成消息队列拥堵。

case HUNTER_DELAY_NEXT_BATCH: {
    dispatcher.performBatchComplete();
    break;
}

void performBatchComplete() {
    // 新建一份拷贝,然后将batch清空,以便继续接收新的结果
    List<BitmapHunter> copy = new ArrayList<>(batch);
    batch.clear();
    // 向主线程发送消息
    mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy);
    logBatch(copy);
}

我们知道DispatchermainThreadHandler是在创建时传入的,在Picasso类中实现。

static final Handler HANDLER = new Handler(looper.getMainLooper()) {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case HUNTER_BATCH_COMPLETE: {
                List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;
                // 遍历处理每个结果
                for (int i = 0, n = batch.size(); i < n; i++) {
                    BitmapHunter hunter = batch.get(i);
                    hunter.picasso.complete(hunter);
                }
                break;
            }
            // 其他消息
            ...
        }
    }
}

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, exception);
    }
    if (hasMultiple) {
        for (int i = 0, n = joined.size(); i < n; i++) {
            Action join = joined.get(i);
            deliverAction(result, from, join, exception);
        }
    }
    if (listener != null && exception != null) {
        listener.onImageLoadFailed(this, uri, exception);
    }
}

这里首先会获取BitmapHunter的请求任务single以及关联的请求任务列表joined。还记得在将BitmapHunter添加线程池之前,会先判断该hunter是否已经在线程池中,如果是,则不会重复添加,而是将该hunter通过BitmapHunter#attach()方法关联到之前的任务上。这里的joined列表就是当前任务所关联的任务列表。
不管是单个任务还是任务列表,都调用deliverAction()方法一一进行结果分发。

private void deliverAction(Bitmap result, LoadedFrom from, Action action, Exception e) {
    if (action.isCancelled()) {
        return;
    }
    if (!action.willReplay()) {
        // 如果不需要重试,将target到action的关联移除
        targetToAction.remove(action.getTarget());
    }
    if (result != null) {
        if (from == null) {
            throw new AssertionError("LoadedFrom cannot be null");
        }
        action.complete(result, from);
    } else {
        action.error(e);
    }
}

最终调用的是Action类的complete()error()方法。Action是个抽象类,complete()error()是其定义的抽象方法,Picasso支持GetActionFetchActionTargetActionImageViewAction,以及RemoteViewAction。我们在创建Action时使用的是ImageViewAction,那么这里我们就以ImageViewAction为例。

@Overrid
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));
    }
    // Action持有的是ImageView的弱引用,需要判空
    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();
    }
}

@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);
    }
    // 设置错误图,优先判断资源ID
    if (errorResId != 0) {
        target.setImageResource(errorResId);
    } else if (errorDrawable != null) {
        target.setImageDrawable(errorDrawable);
    }
    if (callback != null) {
        callback.onError(e);
    }
}
  • 请求成功,将返回的Bitmap及相关参数封装成PicassoDrawable(继承自BitmapDrawable),然后显示到ImageView上;
  • 请求失败,将之前设置的错误图片显示到ImageView上。

需要注意的是,如果预留图placeholder是动画,在设置新的图片资源前需要先把动画停掉。

到此,Picasso的整个流程分析完毕,下面是整个流程的时序图(根据最新版本的代码,修改了所参考文章的时序图,非原创)

picasso.jpg

参考:
https://blog.csdn.net/chdjj/article/details/49964901

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • 一. 概述 Picasso是Square出品的一个非常精简的图片加载及缓存库,其主要特点包括: 易写易读的流式编程...
    SparkInLee阅读 1,086评论 2 11
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,092评论 1 32
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong阅读 22,363评论 1 92
  • 参考文章: Picasso源码解析 一、简介 介绍:Picasso,可译为“毕加索”,是Android中一个图片加...
    千涯秋瑟阅读 1,682评论 1 2
  • 这次国庆回来见了几个村里的同学,议论最多的就是互相问候什么时候结婚。一下子又有两个准备结婚了,那个堂妹估计...
    Hephie阅读 262评论 0 0