Picasso 是由 Square 发布的图片加载框架,优雅地实现了 Android 系统中图片加载的功能。其中请求分类、任务调度、多级缓存等思想很有借鉴意义,本文将从源码角度阐述 Picasso 的工作原理。

Ask Yourself:实现一个图片加载框架

图片的加载流程是获取 - 变换 - 显示,来源可能是网络、资源文件、本地文件,其中还要加上缓存,综上,可以整理出这样的流程图。

流程图
流程图

结合上图,整理出主要职责类:

  • 请求包装类,包含请求地址、缓存策略
  • 缓存类,内存+磁盘,两级缓存管理
  • 请求处理类,实现相同的接口,有网络、本地、资源三种实现
  • 下载器类,兼有暂停、恢复、取消功能
  • 图片变换类,获取完图片后进行裁剪、缩放、旋转、圆角等变换
  • 调度器类,管理线程

有了自己的理解之后,再结合 Picasso 的代码进行学习。

请求信息类 Request/RequestCreator

首先是 Request.java,这个类包含了待显示图片的地址、尺寸、缩放、旋转、裁剪信息,采用 Builder 模式,属性如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static final class Builder {
private Uri uri;
private int resourceId;
private String stableKey;
private int targetWidth;
private int targetHeight;
private boolean centerCrop;
private boolean centerInside;
private boolean onlyScaleDown;
private float rotationDegrees;
private float rotationPivotX;
private float rotationPivotY;
private boolean hasRotationPivot;
private List<Transformation> transformations;
private Bitmap.Config config;
private Priority priority;
}

然后才是请求包装类 RequestCreator.java,在 Request.java 基础上,这个类增加了显示效果的属性,比如淡入、加载中的占位图片、错误图片、tag 以及缓存策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class RequestCreator {
private static final AtomicInteger nextId = new AtomicInteger();

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

private boolean noFade;
private boolean deferred;
private boolean setPlaceholder = true;
private int placeholderResId;
private int errorResId;
private int memoryPolicy;
private int networkPolicy;
private Drawable placeholderDrawable;
private Drawable errorDrawable;
private Object tag;
}

另外,Picasso.with(context).load("fake_url").into(someImageView) 中的into方法也是由 RequestCreator.java 类实现的。

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
public void into(ImageView target, Callback callback) {
long started = System.nanoTime();
checkMain(); // 因为要显示在 ImageView 上,所以必须在主线程调用

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) { // 采用 fit 调整图片大小以适应 ImageView,延时操作
if (data.hasSize()) { // fit 后自然不能人工设置长宽
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);
}

Request request = createRequest(started);
String requestKey = createKey(request); // cache 的 key

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

代码中有一个deferred的判断,如果为true则会创建一个DeferredRequestCreator,看一下这个类的实现代码:

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
class DeferredRequestCreator implements ViewTreeObserver.OnPreDrawListener {

final RequestCreator creator;
final WeakReference<ImageView> target;
Callback callback;

@TestOnly DeferredRequestCreator(RequestCreator creator, ImageView target) {
this(creator, target, null);
}

DeferredRequestCreator(RequestCreator creator, ImageView target, Callback callback) {
this.creator = creator;
this.target = new WeakReference<ImageView>(target);
this.callback = callback;
target.getViewTreeObserver().addOnPreDrawListener(this);
}

@Override public boolean onPreDraw() { // 覆盖这个方法,以便当ViewTree计算完成准备绘制时,拿到ImageView的长宽
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;
}

vto.removeOnPreDrawListener(this);

this.creator.unfit().resize(width, height).into(target, callback); // 将ImageView长宽传给RequestCreator以便对图片进行resize
return true;
}

void cancel() {
callback = null;
ImageView target = this.target.get();
if (target == null) {
return;
}
ViewTreeObserver vto = target.getViewTreeObserver();
if (!vto.isAlive()) {
return;
}
vto.removeOnPreDrawListener(this);
}
}

只有当在显示图片的地方调用fit()时才会创建DeferredRequestCreator对象。

RequestCreator里面有两个字断分别对应着内存设置与网络设置,是缓存策略,对应的枚举类如下,可以看到两者都有NO_CACHE NO_STORE,网络策略枚举里多了一个OFFLINE

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
public enum MemoryPolicy {

/** Skips memory cache lookup when processing a request. */
NO_CACHE(1 << 0),
/**
* Skips storing the final result into memory cache. Useful for one-off requests
* to avoid evicting other bitmaps from the cache.
*/
NO_STORE(1 << 1);

static boolean shouldReadFromMemoryCache(int memoryPolicy) {
return (memoryPolicy & MemoryPolicy.NO_CACHE.index) == 0;
}

static boolean shouldWriteToMemoryCache(int memoryPolicy) {
return (memoryPolicy & MemoryPolicy.NO_STORE.index) == 0;
}

final int index;

private MemoryPolicy(int index) {
this.index = index;
}
}

public enum NetworkPolicy {

/** Skips checking the disk cache and forces loading through the network. */
NO_CACHE(1 << 0),

/**
* Skips storing the result into the disk cache.
* <p>
* <em>Note</em>: At this time this is only supported if you are using OkHttp.
*/
NO_STORE(1 << 1),

/** Forces the request through the disk cache only, skipping network. */
OFFLINE(1 << 2);

public static boolean shouldReadFromDiskCache(int networkPolicy) {
return (networkPolicy & NetworkPolicy.NO_CACHE.index) == 0;
}

public static boolean shouldWriteToDiskCache(int networkPolicy) {
return (networkPolicy & NetworkPolicy.NO_STORE.index) == 0;
}

public static boolean isOfflineOnly(int networkPolicy) {
return (networkPolicy & NetworkPolicy.OFFLINE.index) != 0;
}

final int index;

private NetworkPolicy(int index) {
this.index = index;
}
}

缓存管理 LruCache

Picasso.java里维护了一个成员变量cache,对应接口是Cache.java,(只贴出主要代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* A memory cache for storing the most recently used images.
* <p>
* <em>Note:</em> The {@link Cache} is accessed by multiple threads. You must ensure
* your {@link Cache} implementation is thread safe when {@link Cache#get(String)} or {@link
* Cache#set(String, android.graphics.Bitmap)} is called.
*/
public interface Cache {
/** Retrieve an image for the specified {@code key} or {@code null}. */
Bitmap get(String key);

/** Store an image in the cache for the specified {@code key}. */
void set(String key, Bitmap bitmap);

/** Returns the current size of the cache in bytes. */
int size();

/** Returns the maximum size in bytes that the cache can hold. */
int maxSize();

/** Clears the cache. */
void clear();
}

很简单的几个接口,需要注意的是在实现过程中要保证get/set的线程安全。对应的实现类是LruCache.java,这是LruCache一个很漂亮的实现,该有的功能都有,丝毫不拖泥带水,我忍不住把整个类都贴出来。单词eviction的翻译是驱逐

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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/** A memory cache which uses a least-recently used eviction policy. */
public class LruCache implements Cache {
final LinkedHashMap<String, Bitmap> map; // 有序 map
private final int maxSize;

private int size;
private int putCount;
private int evictionCount;
private int hitCount; // 命中计数
private int missCount; // 未命中计数

/** Create a cache using an appropriate portion of the available RAM as the maximum size. */
public LruCache(Context context) {
this(Utils.calculateMemoryCacheSize(context)); // Utils.java 里用于计算内存的工具方法,返回的数值是1/7(约15%)应用可用内存。
}

/** Create a cache with a given maximum size in bytes. */
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("Max size must be positive.");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true); // 第三个参数 true 表示 LinkedHashMap 的排序是按照最近 access,false则为最近 insertion
}

@Override public Bitmap get(String key) {
if (key == null) {
throw new NullPointerException("key == null");
}

Bitmap mapValue;
synchronized (this) { // 前面说了,get/set必须要做同步控制
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}

return null;
}

@Override public void set(String key, Bitmap bitmap) {
if (key == null || bitmap == null) {
throw new NullPointerException("key == null || bitmap == null");
}

Bitmap previous;
synchronized (this) { // 一样的同步
putCount++;
size += Utils.getBitmapBytes(bitmap);
previous = map.put(key, bitmap);
if (previous != null) {
size -= Utils.getBitmapBytes(previous);
}
}

trimToSize(maxSize); // 若尺寸超过 maxSize 则需要清理
}

private void trimToSize(int maxSize) {
while (true) {
String key;
Bitmap value;
synchronized (this) { // 凡是所有操作 map 的地方都加锁
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(
getClass().getName() + ".sizeOf() is reporting inconsistent results!");
}

if (size <= maxSize || map.isEmpty()) {
break;
}

Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key); // 清理出 map,由 GC 自动回收
size -= Utils.getBitmapBytes(value);
evictionCount++;
}
}
}

/** Clear the cache. */
public final void evictAll() {
trimToSize(-1); // -1 will evict 0-sized elements
}

@Override public final synchronized int size() {
return size;
}

@Override public final synchronized int maxSize() {
return maxSize;
}

@Override public final synchronized void clear() {
evictAll();
}

@Override public final synchronized void clearKeyUri(String uri) { // 清除某个 URI 对应的全部图片,同样内容的图片因为尺寸缩放旋转不同,会在 cache 里存在多个实例
boolean sizeChanged = false;
int uriLength = uri.length();
for (Iterator<Map.Entry<String, Bitmap>> i = map.entrySet().iterator(); i.hasNext();) {
Map.Entry<String, Bitmap> entry = i.next();
String key = entry.getKey();
Bitmap value = entry.getValue();
int newlineIndex = key.indexOf(KEY_SEPARATOR);
if (newlineIndex == uriLength && key.substring(0, newlineIndex).equals(uri)) {
i.remove();
size -= Utils.getBitmapBytes(value);
sizeChanged = true;
}
}
if (sizeChanged) {
trimToSize(maxSize);
}
}

/** Returns the number of times {@link #get} returned a value. */
public final synchronized int hitCount() {
return hitCount;
}

/** Returns the number of times {@link #get} returned {@code null}. */
public final synchronized int missCount() {
return missCount;
}

/** Returns the number of times {@link #set(String, Bitmap)} was called. */
public final synchronized int putCount() {
return putCount;
}

/** Returns the number of values that have been evicted. */
public final synchronized int evictionCount() {
return evictionCount;
}
}

知道了 cache 的用法,就可以很好地在Picasso.java里面检查 cache 了,用起来十分简单。

1
2
3
4
5
6
7
8
9
Bitmap quickMemoryCacheCheck(String key) {
Bitmap cached = cache.get(key);
if (cached != null) {
stats.dispatchCacheHit();
} else {
stats.dispatchCacheMiss();
}
return cached;
}

请求处理器 RequestHandler

在 Picasso.java 的构造函数里,有这样一段初始化代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
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);
}

可以看出这是实现了多种请求处理类,所有的XXXRequetHandler类都继承自抽象类RequestHandler.java,其中最重要的是canHandleRequestload这两个方法。源码注释里给出了简洁的说明,不再赘述。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class RequestHandler {
/**
* Whether or not this {@link RequestHandler} can handle a request with the given {@link Request}.
*/
public abstract boolean canHandleRequest(Request data);

/**
* Loads an image for the given {@link Request}.
*
* @param request the data from which the image should be resolved.
* @param networkPolicy the {@link NetworkPolicy} for this request.
*/
public abstract Result load(Request request, int networkPolicy) throws IOException;
}

我们看一下NetworkRequestHandler类的实现

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
class NetworkRequestHandler extends RequestHandler {
static final int RETRY_COUNT = 2;

private static final String SCHEME_HTTP = "http"; // 这两个常量用于判断 url
private static final String SCHEME_HTTPS = "https";

private final Downloader downloader; // 下载器,有 OkHttp 和 URLConnectionDownloader 两种实现,稍后讲解
private final Stats stats;

public NetworkRequestHandler(Downloader downloader, Stats stats) {
this.downloader = downloader;
this.stats = stats;
}

@Override public boolean canHandleRequest(Request data) {
String scheme = data.uri.getScheme(); // 从 scheme 判断是否为网络图片请求
return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
}

@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) { // 直接拿到 Bitmap,则返回
return new Result(bitmap, loadedFrom);
}

InputStream is = response.getInputStream(); // 拿到输入流,需要解析成 Bitmap
if (is == null) {
return null;
}
// Sometimes response content length is zero when requests are being replayed. Haven't found
// root cause to this but retrying the request seems safe to do so.
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);
}
}

可以看到它是将下载任务委托给了Downloader进行,Downloader的注释里描述的很清楚,它是“A mechanism to load images from external resources such as a disk cache and/or the internet.”,用于加载外部图片(相比于内存缓存而言)。最主要的是load方法

1
Response load(Uri uri, int networkPolicy) throws IOException;

其中Response的结构是

1
2
3
4
5
6
class Response {
final InputStream stream;
final Bitmap bitmap;
final boolean cached; // 图片是否来源自磁盘缓存(local disk cache)
final long contentLength;
}

Downloader有两个实现

  • OKHttpDownloader,对应 Android 版本 >= 4.4
  • URLConnectionDownloader,对应 Android 版本 < 4.4

判断代码位于Utils.java

1
2
3
4
5
6
7
8
static Downloader createDefaultDownloader(Context context) {
try {
Class.forName("com.squareup.okhttp.OkHttpClient");
return OkHttpLoaderCreator.create(context);
} catch (ClassNotFoundException ignored) {
}
return new UrlConnectionDownloader(context);
}

OKHttpDownloader里面的load方法,采用 square 自家的OKHttp进行下载,在构建其 Builder 时决定是否使用磁盘缓存。换言之,虽然Picasso声称有内存+磁盘两级缓存,其磁盘缓存其实是借助OKHttp实现的。

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
@Override public Response load(Uri uri, int networkPolicy) throws IOException {
CacheControl cacheControl = null; // 这个 CacheControl 是 OKHttp 里面的类
if (networkPolicy != 0) {
if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
cacheControl = CacheControl.FORCE_CACHE;
} else {
CacheControl.Builder builder = new CacheControl.Builder();
if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
builder.noCache();
}
if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
builder.noStore();
}
cacheControl = builder.build();
}
}

Request.Builder builder = new Request.Builder().url(uri.toString());
if (cacheControl != null) {
builder.cacheControl(cacheControl);
}

com.squareup.okhttp.Response response = client.newCall(builder.build()).execute(); // 同步接口
int responseCode = response.code();
if (responseCode >= 300) {
response.body().close();
throw new ResponseException(responseCode + " " + response.message(), networkPolicy,
responseCode);
}

boolean fromCache = response.cacheResponse() != null;

ResponseBody responseBody = response.body();
return new Response(responseBody.byteStream(), fromCache, responseBody.contentLength());
}

至此RequestHandler的分析已经完成,虽然它名为 Handler,但实际上并没有包含工作线程一类的东西,这部分相关代码实际上位于Dispatcher.java中。

任务调度器 Dispatcher

理解Dispatcher之前,我们需要先了解一下任务类Action.java

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
abstract class Action<T> {
static class RequestWeakReference<M> extends WeakReference<M> {
final Action action;

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

final Picasso picasso;
final Request request;
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 cancelled;

abstract void complete(Bitmap result, Picasso.LoadedFrom from);

abstract void error();

Action可以理解成一项图片加载任务,它组合了之前介绍的RequestRequestCreator的内容,且包含加载成功/失败的回调。

所有的Action任务都会被丢给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
29
30
31
32
33
34
35
36
37
38
39
40
class Dispatcher {
private static final int RETRY_DELAY = 500;
private static final int AIRPLANE_MODE_ON = 1; // 飞行模式,会影响并发线程数
private static final int AIRPLANE_MODE_OFF = 0;

static final int REQUEST_SUBMIT = 1; // 消息类型,供 handler 使用
static final int REQUEST_CANCEL = 2;
static final int REQUEST_GCED = 3;
static final int HUNTER_COMPLETE = 4;
static final int HUNTER_RETRY = 5;
static final int HUNTER_DECODE_FAILED = 6;
static final int HUNTER_DELAY_NEXT_BATCH = 7;
static final int HUNTER_BATCH_COMPLETE = 8;
static final int NETWORK_STATE_CHANGE = 9;
static final int AIRPLANE_MODE_CHANGE = 10;
static final int TAG_PAUSE = 11;
static final int TAG_RESUME = 12;
static final int REQUEST_BATCH_RESUME = 13;

private static final String DISPATCHER_THREAD_NAME = "Dispatcher";
private static final int BATCH_DELAY = 200; // ms

final DispatcherThread dispatcherThread; // 调度器线程
final Context context;
final ExecutorService service; // 线程池
final Downloader downloader; // 下载器
final Map<String, BitmapHunter> hunterMap; // 线程 map
final Map<Object, Action> failedActions;
final Map<Object, Action> pausedActions;
final Set<Object> pausedTags;
final Handler handler; // DispatchHandler
final Handler mainThreadHandler; // UI 变更
final Cache cache;
final Stats stats; // 统计数据,包含命中缓存、未命中缓存、解码、下载计数
final List<BitmapHunter> batch;
final NetworkBroadcastReceiver receiver; // 监听网络变化,调整并发线程数
final boolean scansNetworkChanges;

boolean airplaneMode;
}

既然名为Dispatcher,肯定少不了dispatchXXX的方法,它们的处理方法完全一样,都是把消息丢给DispatchHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void dispatchSubmit(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}

void dispatchCancel(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_CANCEL, action));
}

void dispatchPauseTag(Object tag) {
handler.sendMessage(handler.obtainMessage(TAG_PAUSE, tag));
}

// 以下还有 dispatchResumeTag、dispatchComplete、dispatchRetry,略
...
}

DispatchHandler是用于接收处理这些消息的类

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
private static class DispatcherHandler extends Handler {
private final Dispatcher dispatcher;

public DispatcherHandler(Looper looper, Dispatcher dispatcher) {
super(looper);
this.dispatcher = dispatcher;
}

@Override public void handleMessage(final Message msg) {
switch (msg.what) {
case REQUEST_SUBMIT: {
Action action = (Action) msg.obj;
dispatcher.performSubmit(action);
break;
}
case REQUEST_CANCEL: {
Action action = (Action) msg.obj;
dispatcher.performCancel(action);
break;
}
case TAG_PAUSE: {
Object tag = msg.obj;
dispatcher.performPauseTag(tag);
break;
}
case TAG_RESUME: {
Object tag = msg.obj;
dispatcher.performResumeTag(tag);
break;
}
case HUNTER_COMPLETE: {
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performComplete(hunter);
break;
}
case HUNTER_RETRY: {
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performRetry(hunter);
break;
}
case HUNTER_DECODE_FAILED: {
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performError(hunter, false);
break;
}
case HUNTER_DELAY_NEXT_BATCH: {
dispatcher.performBatchComplete();
break;
}
case NETWORK_STATE_CHANGE: {
NetworkInfo info = (NetworkInfo) msg.obj;
dispatcher.performNetworkStateChange(info);
break;
}
case AIRPLANE_MODE_CHANGE: {
dispatcher.performAirplaneModeChange(msg.arg1 == AIRPLANE_MODE_ON);
break;
}
default:
Picasso.HANDLER.post(new Runnable() {
@Override public void run() {
throw new AssertionError("Unknown handler message received: " + msg.what);
}
});
}
}
}

可以看到,处理手段是取出消息里的Action,然后调用DispatcherperformXXX直接进行处理。这种Dispatcher组合Handler的模式值得学习借鉴。接下来我们具体看一下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
void performSubmit(Action action, boolean dismissFailed) {
if (pausedTags.contains(action.getTag())) { // 如果请求已经被 pause,则不继续向下处理
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) { // BitmapHunter 是工作线程类(实现 Runnable 接口),若一张图片已经有工作线程在处理,则将当前 Action 附到工作线程的队列中
hunter.attach(action);
return;
}

if (service.isShutdown()) { // 线程池已停止
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down");
}
return;
}
// 生成一个 BitmapHunter
hunter = forRequest(action.getPicasso(), this, cache, stats, action);
hunter.future = service.submit(hunter); // 这里很重要,提交一个 Runnable 给线程池
hunterMap.put(action.getKey(), hunter); // 存进工作线程 Map
if (dismissFailed) {
failedActions.remove(action.getTarget());
}

if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
}
}

职责重大的 BitmapHunter

BitmapHunter实现了Runnable接口,run方法可以看作对一次图片请求的完整处理过程,也就是文初提到的“获取 - 变换 - 显示”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class BitmapHunter implements Runnable {
final int sequence; // 序列号,基于 AtomicInteger 实现
final Picasso picasso; // 单例
final Dispatcher dispatcher; // 任务调度器
final Cache cache;
final Stats stats;
final String key;
final Request data;
final int memoryPolicy;
int networkPolicy;
final RequestHandler requestHandler;

Action action;
List<Action> actions; // 一个 BitmapHunter 可以处理多个 Action
Bitmap result;
Future<?> future;
Picasso.LoadedFrom loadedFrom;
Exception exception;
int exifRotation; // Determined during decoding of original resource.
int retryCount;
Priority priority;
}

EXIF(Exchangeable Image File)是“可交换图像文件”的缩写,当中包含了专门为数码相机的照片而定制的元数据,可以记录数码照片的拍摄参数、缩略图及其他属性信息。很多图像编辑器会自动读取Exif数据来对图像进行优化,最常见的便是从 Exif中读取出相机姿态信息,从而自动识别出竖拍甚至是颠倒拍摄的照片并对其进行旋转校正。

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
@Override public void run() {
try {
updateThreadName(data);

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

result = hunt(); // 同步获取结果

if (result == null) { // 通知 Dispatcher 任务完成
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中,对得起BitmapHunter这个名字

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

data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
RequestHandler.Result result = requestHandler.load(data, networkPolicy); // 调用 handler 同步获取结果
if (result != null) {
loadedFrom = result.getLoadedFrom();
exifRotation = result.getExifOrientation(); // 从 exif 里获取旋转角度

bitmap = result.getBitmap();

// If there was no Bitmap then we need to decode it from the stream.
if (bitmap == null) {
InputStream is = result.getStream(); // 解码图片
try {
bitmap = decodeStream(is, data);
} finally {
Utils.closeQuietly(is); // quietly 的含义是无视所有异常
}
}
}

if (bitmap != null) { // 取到解码后的图片,进行转换和显示
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_DECODED, data.logId());
}
stats.dispatchBitmapDecoded(bitmap); // 这里只是计数,并未操作转换
if (data.needsTransformation() || exifRotation != 0) {
synchronized (DECODE_LOCK) { // 必须加锁,同一时间只能处理一张图片,防止 OOM
if (data.needsMatrixTransform() || exifRotation != 0) {
bitmap = transformResult(data, bitmap, exifRotation); // 运用 matrix 的 scale 与 rotate 处理图片
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;
}

到此为止,整个图片的加载流程基本分析完成,下面是一个完整的类图,出处见文末。

类图
类图

碎碎念

在我看来,做一个开源项目的源码解读,一般有两个切入点。

  1. 先反问自己一个问题,“如果让你实现一个 XX 框架,你会怎么设计?”,画出流程图,列出承担主要职责的类;然后再对照着自己的设计,去阅读并理解项目代码。我把它叫做自顶向下
  2. 从项目代码的入口开始(一般是函数调用处),逐级追踪,弄清楚每一步涉及到哪些类,它们的职责是什么,这样最后会有一个链式的调用在你脑海中。我把它叫做自底向上

方法 1 能让你在开始时纵览全局,对每一个模块有大致的轮廓,随着逐步深入学习代码,对其认识会进一步加深,最后化为己用;但缺点也很明显,你不可能一上来就把流程图、类图绘制地明明白白,需要你对项目代码有一些理解,也许还需要一定的设计模式知识。方法 2 容易上手,如果项目简单还好,若是复杂项目很容易让人迷失其中。本文采用的是方法 1 的思路,以下两篇文章分别对应方法 1 和 2,它们在我写本文的过程中提供了大量帮助,非常感谢原作者。

  1. Android 面试助力:一次读懂热门图片框架 Picasso 源码及流程
  2. Picasso源代码分析