概述
Picasso是大名鼎鼎的Square
公司提供的一个适用于Android的强大的图片下载缓存库。
简单使用
Picasso.get()
.load(url)
.resize(50, 50)
.centerCrop()
.into(imageView)
流程
先简单总结下Picasso
加载图片的流程,后面再详细解析:
- 通过
Picasso#get()
方法创建一个Picasso
的实例,它是用单例模式+Builder
模式实现的。使用默认方式创建Picasso
实例时,它会创建默认的缓存、下载器、线程池、监控器、图片处理器集合等; - 通过
load(uri)
方法将图片的uri
传入,创建RequestCreator
实例; - 调用
RequestCreator#into()
方法,它会先从缓存中检索是否有该图片,有的话直接返回; - 缓存中没有,则创建请求任务
Action
对象,并调用Picasso#enqueueAndSubmit(action)
方法提交请求任务,而Picasso
会调用Dispatcher#dispatchSubmit(action)
方法,由Dispatcher
去处理; -
Dispatcher
维护了一个子线程和消息队列,在主线程发送消息到子线程的消息队列,然后在子线程中调用Dispatcher#performSubmit(action)
方法处理请求; - 处理请求时,首先需要构建
BitmapHunter
对象,它继承自Runnable
,可被线程池执行,构建时会选用合适的RequestHandler
来处理请求,网络请求会使用NetworkRequestHandler
,最后将BitmapHunter
对象扔到线程池中,等待执行; -
BitmapHunter
执行时会再次检索缓存查看是否有请求的图片,有就返回图片;没有再通过RequestHandler
来获取图片; - 获取到请求结果后,
Dispatcher
会对请求做批处理,延迟200ms再将这一批结果通过mainThreadHandler
发送给主线程; -
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
对象时,使用的是PicassoProvider
的context
对象,PicassoProvider
继承自ContentProvider
。构建时,采用了Builder
模式,我们来看下Builder
的build()
方法做了哪些事情。
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)
指定线程池。 - 创建默认的
RequestTransformer
,RequestTransformer
是个接口,它的唯一方法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
支持将图片加载到ImageView
、RemoteView
、Target
等对象上面,这里我们以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
是一个Target
到Action
的映射表,表示一个目标上只允许有一个任务存在。如果当前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
,来看下它的工作线程dispatcherThread
与handler
:
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;
}
最终提交请求的工作交给了Dispatcher
的performSubmit()
来进行,继续看。
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
进行遍历,调用RequestHandler
的canHandleRequest()
方法,判断该处理器能否处理这个请求,从而找出合适的处理器。如果处理器列表中没有合适的RequestHandler
,则会使用ERRORING_HANDLER
,它在load()
请求时会抛出异常。
接下来看下BitmapHunter
的run()
方法。
@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);
}
- 调用
downloader.load()
方法获取响应结果,这里的downloader
默认使用OkHttp3Downloader
,也可在创建Picasso
实例的时候自定义; - 判断请求是否成功,不成功则抛出响应异常;
- 判断结果来源。因为默认使用
OkHttp3Downloader
,而OkHttp
则使用DiskLruCache
作为磁盘缓存。这里会判断响应是从磁盘缓存中读取的还是网络读取的; - 如果结果来自磁盘缓存,且内容长度为0,抛出异常;
- 将请求结果封装成
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);
}
我们知道Dispatcher
的mainThreadHandler
是在创建时传入的,在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
支持GetAction
、FetchAction
、TargetAction
、ImageViewAction
,以及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
的整个流程分析完毕,下面是整个流程的时序图(根据最新版本的代码,修改了所参考文章的时序图,非原创)
参考:
https://blog.csdn.net/chdjj/article/details/49964901
网友评论