Picasso源码结构分析

基本概念

前一段时间复习了Glide的源码,而且这次做了文字整理,又被大神的思路震惊了一次。正好赶上清明假,就把用起来特别相似的,出自JakeWarton大神的Picasso的源码也分析一下,同时跟Glide也做个简单的比较。

Picasso的官网在http://square.github.io/picasso/
用法跟Glide基本一致,就不用多废话介绍了,毕竟已经很有名了。这篇分析基于Picasso 2.5.2

基本流程

还是按照规矩先上图:

看起来有点绕,因为Picasso不像Glide那样类和接口特别多,而是用很少的类实现了强大的功能,那么也就不可避免的有的类承担了多种工作。接下来我们就来分析这些类的作用。

源码分析

我们还是从最基本的Picasso.with(context).load(url).into(view)来分析吧。首先看第一步:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

public static Picasso with(@NonNull Context context) {
if (context == null) {
throw new IllegalArgumentException("context == null");
}
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
singleton = new Builder(context).build();
}
}
}
return singleton;
}

with(context)方法本质上什么都没做,Picasso只支持一种上下文,它不管这是ApplicationContext还是Activity,然后返回了一个单例。那我们再看load(url)方法:

1
2
3
4

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

这里就引出我们的第二个角色了,RequestCreator,类如其名,这个类用来根据传入的参数构建一个请求,注意,它会持有Picasso单例的引用,into(view)方法当然也在里面了,继续看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102

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.picasso = picasso;
this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}

public void into(ImageView target) {
into(target, null);
}

public void into(ImageView target, Callback callback) {
long started = System.nanoTime();
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 || target.isLayoutRequested()) {
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
picasso.defer(target, new DeferredRequestCreator(this, target, callback));
return;
}
data.resize(width, height);
}

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;
}
}

if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}

Action action =
new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);

picasso.enqueueAndSubmit(action);
}

private Request createRequest(long started) {
int id = nextId.getAndIncrement();

Request request = data.build();
request.id = id;
request.started = started;

boolean loggingEnabled = picasso.loggingEnabled;
if (loggingEnabled) {
log(OWNER_MAIN, VERB_CREATED, request.plainId(), request.toString());
}

Request transformed = picasso.transformRequest(request);
if (transformed != request) {
// If the request was changed, copy over the id and timestamp from the original.
transformed.id = id;
transformed.started = started;

if (loggingEnabled) {
log(OWNER_MAIN, VERB_CHANGED, transformed.logId(), "into " + transformed);
}
}

return transformed;
}

这部分代码就有干货了。在into方法里,我们能看到在一连串检查线程,获取尺寸等等一系列准备工作之后,Request终于出现了,它由内部的Builder构建出来(具体细节就不在这里分析了,与多数框架的Request没有大的区别,都只负责携带信息),然后它再加上一个外界传入的请求需要的各种各样的配置信息,就构成了干实事的Action。这个Action内部实现先不看,这时候刚才传入的Picasso对象起作用了,看Picasso里的enqueueAndSubmit(action)方法干了什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

final Dispatcher dispatcher;

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);
}

又一个重要的角色出现了,Dispatcher。在很多框架里都有Dispatcher这个概念,比如赫赫有名的VolleyDispatcher不用看,都知道是一个管理所有请求的调度器,顺着这个方法我们找到了Dispatcher中实际的实现类performSubmit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

final ExecutorService service;

void performSubmit(Action action, boolean dismissFailed) {
if (pausedTags.contains(action.getTag())) {
pausedActions.put(action.getTarget(), action);
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),
"because tag '" + action.getTag() + "' is paused");
}
return;
}

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;
}

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) {
log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
}
}

static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,
Action action)
{

Request request = action.getRequest();
List<RequestHandler> requestHandlers = picasso.getRequestHandlers();

// Index-based loop to avoid allocating an iterator.
//noinspection ForLoopReplaceableByForEach
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);
}

又是ExecutorService,而且又一个关键角色也出现了–BitmapHunter大概这个类名也表明了Picasso只做纯图片加载的意图。首先可以肯定的是BitmapHunter实现了Runnable接口,从代码里我们可以看到它携带者ActionCache等一众小弟,注意,它这时候也持有Picasso和当前Dispatcher的实例,然后去工作线程干活去了。接下来当然是看BitmapHunter里的run()方法了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103

@Override public void run() {
try {
updateThreadName(data);

if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));
}

result = hunt();

if (result == null) {
dispatcher.dispatchFailed(this);
} else {
dispatcher.dispatchComplete(this);
}
} catch (NetworkRequestHandler.ResponseException e) {
if (!NetworkPolicy.isOfflineOnly(e.networkPolicy) || e.code != 504) {
exception = e;
}
dispatcher.dispatchFailed(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);
}
}


Bitmap hunt() throws IOException {
Bitmap bitmap = null;

if (shouldReadFromMemoryCache(memoryPolicy)) {
bitmap = cache.get(key);
if (bitmap != null) {
stats.dispatchCacheHit();
loadedFrom = MEMORY;
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache");
}
return bitmap;
}
}

networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
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 = 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) {
log(OWNER_HUNTER, VERB_DECODED, data.logId());
}
stats.dispatchBitmapDecoded(bitmap);
if (data.needsTransformation() || exifOrientation != 0) {
synchronized (DECODE_LOCK) {
if (data.needsMatrixTransform() || exifOrientation != 0) {
bitmap = transformResult(data, bitmap, exifOrientation);
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;
}

run()里面,其实就是把结果回调给持有的Dispatcher,让它对结果做处理。关键是hunt(),在这里,它走了从内存缓存取数据,未命中则构建RequestHandler从数据源取数据的过程,然后经过一系列处理之后,返回最终的Bitmap对象。RequestHandler的作用稍后讲。BitmapHunter名不虚传,这时候Bitmap已经拿到了,该把它又返回上层了。然后我们就能看到在run()方法里各种情况下执行的Dispatcher不同的回调方法。我们只看成功的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

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");
}
}

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);
}
}

void performBatchComplete() {
List<BitmapHunter> copy = new ArrayList<>(batch);
batch.clear();
mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
logBatch(copy);
}

被处理完的Bitmap对应的BitmapHunter被一步步向上传递,然后通过一个handlerThread将结果又传回给了Picasso:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
      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;
}


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) {
//noinspection ForLoopReplaceableByForEach
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);
}
}

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(result, from);
if (loggingEnabled) {
log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), "from " + from);
}
} else {
action.error(e);
if (loggingEnabled) {
log(OWNER_MAIN, VERB_ERRORED, action.request.logId(), e.getMessage());
}
}
}

最终结果又到了Picasso里面,在这里,它把最终结果传给ActionAction是一个抽象方法,我们看最典型的ImageViewAction的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

@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();
}
}

那么一个完整的加载过程在这里就结束了。

Glide的异同

  • 说实话我能想到的同也就是使用方法类似了,另外大的结构也有类似的地方,比如GlideModelLoaderEngineTarget分别对应PicassoRequestHandlerDispatcherAction,但是如果从零设计一个图片框架的话,应该都会有这些部分。

  • 不同的地方,其实就是设计思路的不同。Glide的功能强大,不仅可以加载一般的图片,也能加载gif、视频,因此具备极致的扩展性,几乎每个组件是一个接口可供外界重新实现,另外它宣传具有极致的滑动流畅性,因此在内存缓存上做了很大的文章,一个完整请求会有四级缓存,其中设计了BitmapPool这个神器来提升列表滑动的体验。

  • Picasso,是一个纯粹的轻量级图片框架。它只能处理Bitmap,也只愿意处理它。在Picasso的issues里很多人都想要更多的功能,得到的答复都是Picasso目前的目标均已实现。它比Glide轻数倍,甚至没有分包,而且扩展性并不强,只能说在加载图片上有一定的可定制性。

总结

Picasso的源码很好读,没有Glide那么多绕来绕去的抽象方法和接口,读完Glide再来读Picasso完全就是享受,它比Glide更轻,能够满足绝大部分场景的图片加载,因此也是很成功的图片加载框架。Picasso的扩展性没那么强,另外它强依赖于OkHttp,甚至磁盘缓存都是通过OkHttp的磁盘缓存做的,可见Square对自己全家桶的自信程度。只能说这个库的目标就是Square全家桶的使用者,毕竟现在哪个新项目不会用OkHttp呢?

但是!再轻的库,麻烦还是分个包好不好···各种各样的类放在一个包里,强迫症有点受不了···