Picasso源码解析

接触Android开发的应该对图片加载这块都有过接触,也比较头疼,稍微处理不当就会遇到OOM。这两天在看Picasso,感觉收获很大,在这里记录一下。

1
2
3
4
Picasso
.with(getActivity())
.load("http://i1.mifile.cn/a1/T1pZJgBbZT1RXrhCrK.jpg")
.into(imageView);

这是Picasso最简单的用法,先从入口看。

1
2
3
4
5
6
7
8
9
10
public static Picasso with(Context context) {
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
singleton = new Builder(context).build();
}
}
}
return singleton;
}

非常标准的双重检验锁单例模式实现。Picasso实例是通过构造者模式生成的,进去看一下build()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
}
}

这里简单说就是生成Picasso构造方法所需要的各个参数,有Downloader(图片下载器)、LruCache(LRU缓存算法实现)、PicassoExecutorService(线程池)、Dispatcher(任务调度)等,这些在后面的图片加载过程中都会用到。

到这儿,Picasso实例已经生成了,再看load()方法:

1
2
3
4
5
6
7
8
9
public RequestCreator load(String path) {
if (path == null) {
return new RequestCreator(this, null, 0);
}
if (path.trim().length() == 0) {
throw new IllegalArgumentException("Path must not be empty.");
}
return load(Uri.parse(path));
}

返回一个RequestCreator对象。看一下它的into()方法:

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

先检查是不是主线程,然后根据RUI生成一个requestKey,根据requestKey看这次请求的图片能不能在内存缓存中取到,能的话直接返回,没有的话根据相关属性生成一个ImageViewAction,并调用Picasso的enqueueAndSubmit()方法,把生成的ImageViewAction传过去。

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}

这里有些需要注意,由ImageViewAction的构造方法可知action.getTarget()其实就是ImageView,第3行,从targetToAction的Map中取出ImageView对应的Action,如果该ImageView对应的有Action且和本次要请求的Action不相同,说明之前该ImageView已经绑定过Action,则调用cancelExistingRequest(target)方法取消之前绑定在ImageView上的Action,同时更新为新的Action,之前的访问请求如果已经添加到线程池中会被取消(采用的Future模式)。主要用途在于ListView的View复用,如果图片还没有下载下来就往下滑动导致View不可见View被复用时,取消访问之前的图片,加入新的Action,防止图片加载错乱。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void cancelExistingRequest(Object target) {
checkMain();
Action action = targetToAction.remove(target);
if (action != null) {
action.cancel();
dispatcher.dispatchCancel(action);
}
if (target instanceof ImageView) {
ImageView targetImageView = (ImageView) target;
DeferredRequestCreator deferredRequestCreator =
targetToDeferredRequestCreator.remove(targetImageView);
if (deferredRequestCreator != null) {
deferredRequestCreator.cancel();
}
}
}

enqueueAndSubmit()方法最终调用了dispatcher的dispatchSubmit()方法。这里的dispatcher就是入口方法Picasso.with()中生成赋值的。来看一下。

1
2
3
void dispatchSubmit(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}

使用handler发了一个message,来看接收的地方:

1
2
3
4
5
6
7
8
9
@Override
public void handleMessage(final Message msg) {
switch (msg.what) {
case REQUEST_SUBMIT: {
Action action = (Action) msg.obj;
dispatcher.performSubmit(action);
break;
}
}

这里只摘取REQUEST_SUBMIT逻辑处理部分,看到最终调用了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
void performSubmit(Action action) {
performSubmit(action, true);
}
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());
}
}

第15行,先从内存中根据requestKey取BitmapHunter,如果能取到数据则说明该URI正在处理中(取消、加载完成时会从hunterMap中remove),没必要再次处理,只需要把该次请求的Action添加进BitmapHunter所维护的actionList中,等BitmapHunter处理完之后会自动回调。这么做主要用在多个ImageView加载同一个url图片时,只需要一个任务去加载该url即可,图片下载成功后通知所有要显示该url的ImageView显示。
第28行,会根据传入的ImageViewAction调用forRequest()方法生成一个BitmapHunter,这个forRequest()方法我们一会再说,生成BitmapHunter之后,把这个BitmapHunter对象传给service的submit()方法。同样,这里的service就是前面Picasso中生成的PicassoExecutorService。看一下submit()方法。

1
2
3
4
5
6
@Override
public Future<?> submit(Runnable task) {
PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task);
execute(ftask);
return ftask;
}

由于BitmapHunter类实现了Runnable接口,最终会调用BitmapHunter的run()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@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 (Downloader.ResponseException e) {
// 此处省略部分异常捕获的代码
} finally {
Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);
}
}

主要处理在第10行,到hunt()方法中看一下。

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
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);
if (result != null) {
loadedFrom = result.getLoadedFrom();
exifRotation = result.getExifOrientation();
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);
}
}
}
if (bitmap != null) {
// Bitmap获取之后的转换处理 代码省略
}
return bitmap;
}

开始再次从内存缓存中取一下,取不到的话继续。第35行是Bitmap获取之后的转换处理,暂时先不看。发现result主要是在第17行返回的。不过这个requestHandler又是什么呢?
别急,看一下发现是BitmapHunter的一个全局变量:

1
final RequestHandler requestHandler;

但是这个final类型的全局变量是怎么来的呢?初始没有赋值,那么毫无疑问,就是在BitmapHunter的构造中赋值的了,也就是生成BitmapHunter对象的时候传入的。还记得刚才在dispatcher中调用BitmapHunter的run方法之前有个生成BitmapHunter对象的方法吗?没错,就是forRequest(),看一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
}

遍历Picasso中的requestHandlers这个List,找到一个能够处理这次请求的RequestHandler (requestHandler.canHandleRequest(request)返回true认为能处理),然后用这个RequestHandler生成一个BitmapHunter实例。那这个requestHandlers的List是哪来的呢?
在Picasso的构造方法中有如下代码:

1
2
3
4
5
6
7
8
allRequestHandlers.add(new ResourceRequestHandler(context));
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);

可以看到,在一开始的时候Picasso会内置7种常见的RequestHandler。从名字就能看出每个能够处理的图片资源的类型了,resource资源图片、本地图片文件、网络图片、asset目录下的图片等。canHandleRequest()方法会根据load(URI uri)方法中传入的URI的scheme来判断自己能否处理该种请求类型。如NetworkRequestHandler的canHandleRequest()方法实现如下:

1
2
3
4
5
6
7
8
private static final String SCHEME_HTTP = "http";
private static final String SCHEME_HTTPS = "https";
@Override
public boolean canHandleRequest(Request data) {
String scheme = data.uri.getScheme();
return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
}

如果是http或https开头的请求,则认为是要加载网络图片,交给自己处理,否则认为自己没有处理这种请求的能力,则返回false,交给别的RequestHandler。这么设计符合单一职责原则。

好了,绕了这么大一圈,再回到BitmapHunter的hunt()方法的这一行:

1
RequestHandler.Result result = requestHandler.load(data, networkPolicy);

这下就清晰了,由于我们load(“http://i1.mifile.cn/a1/T1pZJgBbZT1RXrhCrK.jpg“) 传入的是网络图片,那么这里的requestHandler就应该是NetworkRequestHandler了。我们看一下它的load()方法。

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
@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;
}
// 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的load()方法拿到的返回值。还记得Picasso.Builder中downloader是怎么生成的吗?

1
downloader = Utils.createDefaultDownloader(context);

进去看一下:

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

如果项目中引入了OkHttp,那么使用OkHttp框架去发起网络请求,反之,使用UrlConnectionDownloader作为网络请求库。看一下UrlConnectionDownloader的load()方法

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
@Override
public Response load(Uri uri, int networkPolicy) throws IOException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
installCacheIfNeeded(context);
}
HttpURLConnection connection = openConnection(uri);
connection.setUseCaches(true);
if (networkPolicy != 0) {
String headerValue;
if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
headerValue = FORCE_CACHE;
} else {
StringBuilder builder = CACHE_HEADER_BUILDER.get();
builder.setLength(0);
if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
builder.append("no-cache");
}
if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
if (builder.length() > 0) {
builder.append(',');
}
builder.append("no-store");
}
headerValue = builder.toString();
}
connection.setRequestProperty("Cache-Control", headerValue);
}
int responseCode = connection.getResponseCode();
if (responseCode >= 300) {
connection.disconnect();
throw new ResponseException(responseCode + " " + connection.getResponseMessage(),
networkPolicy, responseCode);
}
long contentLength = connection.getHeaderFieldInt("Content-Length", -1);
boolean fromCache = parseResponseSourceHeader(connection.getHeaderField(RESPONSE_SOURCE));
return new Response(connection.getInputStream(), fromCache, contentLength);
}

使用的是Android默认的HttpURLConnection,读取网络图片流后,生成Response()返回。
分析到这里,图片已经从网络上下载下来了,再次回到BitmapHunter的run()方法。

1
2
3
4
5
6
result = hunt();
if (result == null) {
dispatcher.dispatchFailed(this);
} else {
dispatcher.dispatchComplete(this);
}

hunt()成功返回之后会回调dispatcher.dispatchComplete()方法,看一下。

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

到Handler的handlerMessage()方法中

1
2
3
4
5
case HUNTER_COMPLETE: {
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performComplete(hunter);
break;
}

继续看下去:

1
2
3
4
5
6
7
8
9
10
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");
}
}

如果开启了内存缓存(默认是开启的),则会把该次请求结果存到内存中,同时在batch()方法中发出一个HUNTER_DELAY_NEXT_BATCH的消息。

1
2
3
4
5
6
7
8
9
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);
}
}

1
2
3
4
case HUNTER_DELAY_NEXT_BATCH: {
dispatcher.performBatchComplete();
break;
}

performBatchComplete()方法:

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

这里通过Handler由子线程向主线程通信。这个mainThreadHandler是在Dispatcher的构造中赋值的,而生成Dispatcher同样是在Picasso.Builder中:

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

看看这个HANDLER的handlerMessage()方法:

1
2
3
4
5
6
7
8
9
10
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;
}

继续看

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

会回调action.complete(result, from)方法,这里请注意,如果多个ImageView加载同一张图片时,这里要遍历Action,分别回调,第23行。
这里的Action是into()方法里生成的ImageViewAction:

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

看下它的complete方法。

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

再看setBitmap()方法:

1
2
3
4
5
6
7
8
9
10
static void setBitmap(ImageView target, Context context, Bitmap bitmap,
Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {
Drawable placeholder = target.getDrawable();
if (placeholder instanceof AnimationDrawable) {
((AnimationDrawable) placeholder).stop();
}
PicassoDrawable drawable =
new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);
target.setImageDrawable(drawable);
}

把Bitmap设置到ImageView上,到此,所有的流程走完。

总结

  • 针对ListView的优化?
    通过ImageView和Action绑定,当ImageView被复用时取消之前绑定的Action,绑定、加载新的Action,节省后台开销,防止图片显示错乱。ListView滑动期间可以调用pauseTag()暂停加载图片,不滑动的时候再调用resumeTag()开启加载,防止滑动丢帧。

  • 如何处理多个ImageView加载同一个URL图片?
    每个请求加载图片的BitmapHunter会维护一个ActionList,当同一个URI对应的BitmapHunter还在处理图片时,再来一个相同的URI,BitmapHunter会把该Action添加到内部的ActionList中去,等处理完图片后遍历ActionList,回调需要接收该URI的ImageView。

  • Picasso是如何处理硬盘缓存的?
    Picasso其实并没有实现DiskCache,它是借助底层的网络库OkHttp或HttpUrlConnection对缓存的处理。Picasso保存的也是完整图片的缓存,即使ImageView设置的大小不同,图片缓存都是一样的,通过读取完整图片缓存后再对BitMap进行处理。

  • 子线程和UI线程是如果通信的?
    通过Handler机制。子线程请求、解析图片完成后,通过Handler向UI线程sendMessage,然后在handlerMessage()方法里告知ImageView。

  • Picasso使用的是全局的ApplicationContext,在有些插件化架构的APP中会有些问题,无法加载plugin中的静态资源图片,这里需要优化一下。

    1
    2
    3
    4
    5
    6
    public Builder(Context context) {
    if (context == null) {
    throw new IllegalArgumentException("Context must not be null.");
    }
    this.context = context.getApplicationContext();
    }
  • 对线程池的处理。
    根据用户网络不同,动态创建线程数量。这里其实可以加入CPU核数,和网络类型一起控制线程数。

    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
    void adjustThreadCount(NetworkInfo info) {
    if (info == null || !info.isConnectedOrConnecting()) {
    setThreadCount(DEFAULT_THREAD_COUNT);
    return;
    }
    switch (info.getType()) {
    case ConnectivityManager.TYPE_WIFI:
    case ConnectivityManager.TYPE_WIMAX:
    case ConnectivityManager.TYPE_ETHERNET:
    setThreadCount(4);
    break;
    case ConnectivityManager.TYPE_MOBILE:
    switch (info.getSubtype()) {
    case TelephonyManager.NETWORK_TYPE_LTE: // 4G
    case TelephonyManager.NETWORK_TYPE_HSPAP:
    case TelephonyManager.NETWORK_TYPE_EHRPD:
    setThreadCount(3);
    break;
    case TelephonyManager.NETWORK_TYPE_UMTS: // 3G
    case TelephonyManager.NETWORK_TYPE_CDMA:
    case TelephonyManager.NETWORK_TYPE_EVDO_0:
    case TelephonyManager.NETWORK_TYPE_EVDO_A:
    case TelephonyManager.NETWORK_TYPE_EVDO_B:
    setThreadCount(2);
    break;
    case TelephonyManager.NETWORK_TYPE_GPRS: // 2G
    case TelephonyManager.NETWORK_TYPE_EDGE:
    setThreadCount(1);
    break;
    default:
    setThreadCount(DEFAULT_THREAD_COUNT);
    }
    break;
    default:
    setThreadCount(DEFAULT_THREAD_COUNT);
    }
    }