图片加载库源码浅析之 Picasso

Picasso 作为 Android 上一个老牌的图片加载库,似乎近些年在 Glide 的 “打压” 下已经变的黯然失色,但作为 square 出品的优秀框架,其实现的架构和思想仍然有许多值得借鉴和学习的地方,本文所使用的 Picasso 版本号为 2.71828,在 gradle 中依赖如下:

1
implementation 'com.squareup.picasso:picasso:2.71828'

Picasso 加载图片的方式和 Glide 类似,都是链式调用:

1
2
3
Picasso.get().load("").into(target); // 异步加载并设置到 target 上
Picasso.get().load("").get(); //同步加载,不能在主线程调用
Picasso.get().load("").fetch(); // 异步加载

这里 Picasso 提供了三种方式:

  • into() :参数可以是 View,如 ImageView 和 RemoteViews,也可以是一个 Target 类型的对象,等于是通过回调的方式来实现加载结果的处理
  • get():同步获取 btimap 对象,会检查当前线程是否是主线程,如果是则直接会抛出异常
  • fetch():异步获取 bitmap 对象,可以通过设置回调来监听是否加载成功

使用 into()加载的过程大致上可以分为三个阶段:一是获取 Picasso 对象,对应 Picasso.get(),这一步是初始化如线程池、缓存等;二是请求封装,对应 load(url),这一步则是对资源请求的封装,例如占位图、加载失败图、标志、资源裁剪等;最后则是请求处理阶段,对应 into(ImageView) ,这一步处理的事情最为繁重,包括资源请求、资源处理等,下图为 into(ImageView) 流程的的序列图,接下来就通过围绕这个序列图来进行分析:

dVA17n.png

获取 Picasso 对象

这一节对应于序列图中的步骤 1、2、3。

静态方法 get() 是通过 DCL 实现的单例模式,返回一个全局的 Picass 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static Picasso get() {
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
if (PicassoProvider.context == null) {
throw new IllegalStateException("context == null");
}
singleton = new Builder(PicassoProvider.context).build();
}
}
}
return singleton;
}

这里 context 的获取是通过 PicassoProvider 实现的, PicassoProvider 继承自 ContentProvider,主要作用就是为 Picasso 提供 context 对象:

1
2
3
4
5
6
7
8
9
10
public final class PicassoProvider extends ContentProvider {

@SuppressLint("StaticFieldLeak") static Context context;

@Override public boolean onCreate() {
context = getContext();
return true;
}
...
}

并在自己的 AndroidManifest 文件中注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.squareup.picasso" >

<uses-sdk android:minSdkVersion="14" />

<application>
<provider
android:name="com.squareup.picasso.PicassoProvider"
android:authorities="${applicationId}.com.squareup.picasso"
android:exported="false" />
</application>

</manifest>

由于在进程启动时 ContentProvider 的加载是优先于 Application 的加载,因此很多三方 SDK 也通常使用这种方式来自动进行初始化,通过在 aar 包中添加一个自定义的 ContentProvider,在其 onCreate() 方法中实现初始化逻辑。

除了通过 get() 方法,也可以通过 Picasso 的静态内部类 Builder 来自行构建,主要提供了如下方法:

1
2
3
4
5
6
7
8
9
10
11
12
// 设置 Bitmap.Config
public Builder defaultBitmapConfig(@NonNull Bitmap.Config bitmapConfig)
// 下载图片的方式,默认使用 OkHttp3Downloader
Builder downloader(@NonNull Downloader downloader)
// 线程池,默认为 PicassoExecutorService
public Builder executor(@NonNull ExecutorService executorService)
// 内存缓存,默认为 LruCache
public Builder memoryCache(@NonNull Cache memoryCache)
// 设置请求转换器,只能有一个
public Builder requestTransformer(@NonNull RequestTransformer transformer)
// 添加请求处理器,可以有多个
public Builder addRequestHandler(@NonNull RequestHandler requestHandler)

下载器

对下载行为的抽象,提供 load() 方法来加载图片并返回一个 okhttp3.Response 对象,shutdown() 方法用于对资源进行清理:

1
2
3
4
5
6
public interface Downloader {

@NonNull Response load(@NonNull okhttp3.Request request) throws IOException; // 加载图片

void shutdown();
}

默认只有 OkHttp3Downloader 一个实现,其内部通过 OkHttp 进行网络请求下载图片:

1
2
3
@NonNull @Override public Response load(@NonNull Request request) throws IOException {
return client.newCall(request).execute();
}

线程池

默认使用的线程池为 PicassoExecutorService,核心线程数和最大线程数数量相同,默认都为 3 个,但可根据网络状态进行动态调整:2G 网络为 1 个、3G 网络为 2 个、4G网络为 3 个、WIFI 网络为 4 个。工作队列为 PriorityBlockingQueue,是一个无界阻塞队列,可以通过自定义 compareTo() 来指定排序规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class PicassoExecutorService extends ThreadPoolExecutor {
private static final int DEFAULT_THREAD_COUNT = 3;

PicassoExecutorService() {
super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS,
new PriorityBlockingQueue<Runnable>(), new Utils.PicassoThreadFactory());
}

private static final class PicassoFutureTask extends FutureTask<BitmapHunter>
implements Comparable<PicassoFutureTask> {
...

@Override
public int compareTo(PicassoFutureTask other) {
Picasso.Priority p1 = hunter.getPriority();
Picasso.Priority p2 = other.hunter.getPriority();

// High-priority requests are "lesser" so they are sorted to the front.
// Equal priorities are sorted by sequence number to provide FIFO ordering.
return (p1 == p2 ? hunter.sequence - other.hunter.sequence : p2.ordinal() - p1.ordinal());
}
}
}

compareTo() 中会首先判断两个请求的优先级,如果优先级相同,则会接着判断添加的序号,sequence 是一个自增的 int 整型,也就是说先入队的请求先处理,是一种先进先出的方式。

内存缓存器

Cache 接口提供了基本的对于缓存内容的处理方法:

1
2
3
4
5
6
7
8
public interface Cache {
Bitmap get(String key);
void set(String key, Bitmap bitmap);
int size();
int maxSize(); //最大可缓存的字节数
void clear();
void clearKeyUri(String keyPrefix); //根据前缀移除对应的缓存
}

其有 2 个实现类,NONE 和 LruCache,其中 NONE 是空实现,LruCache 内部是对 android.util.LruCache 的封装,最大缓存大小为可用内存大小的 1/7。

请求转换器

RequestTransformer 可以理解为是 Picasso 向外提供的一个对请求进行转换的接口,用户可以通过自定义一个转换器来对生成的 Request 对象进行处理,只能设置一个,默认提供的实现为 IDENTITY 直接返回原 Request 对象,如下所示:

1
2
3
4
5
6
7
8
9
10
public interface RequestTransformer {

Request transformRequest(Request request);

RequestTransformer IDENTITY = new RequestTransformer() {
@Override public Request transformRequest(Request request) {
return request;
}
};
}

请求处理器

RequestHandler 是一个抽象类:

1
2
3
4
5
public abstract class RequestHandler {
public abstract boolean canHandleRequest(Request data);
public abstract Result load(Request request, int networkPolicy) throws IOException;
...
}

其作用就是在不同场景下加载图片,例如 Picasso 通过 AssetRequestHandler 来加载 asset 文件夹下的图片资源,用 NetworkRequestHandler 来加载网络图片资源等,通过遍历所有的 Handler,并调用它们的 canHandleRequest(Request data) 方法,如果返回 true,则表示对应 Handler 可以处理该请求,则该请求就会交由这个 Handler 处理。在 Picasso 的构造函数中会添加默认 Handler 和用户定义的 Handler:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
...
int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.
int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
List<RequestHandler> allRequestHandlers = new ArrayList<>(builtInHandlers + extraCount);
// 优先添加用户自定义的 handler
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));
...
}

请求封装

这一节对应于序列图中的步骤 4、5、6,主要作用就是封装 Request 对象。

Picasso 提供了 4 种图片来源的方法:

1
2
3
4
public RequestCreator load(@NonNull File file)
public RequestCreator load(@DrawableRes int resourceId)
public RequestCreator load(@Nullable Uri uri)
public RequestCreator load(@Nullable String path)

这里需要注意的是,传入的参数不能为空,不然会直接抛一个异常,因此使用前的判空非常有必要。load() 方法会返回一个 RequestCreator 对象,通过这个对象就可以来对请求做一些处理操作。RequestCreator 内部持有一个 Request 对象,所有关于请求的配置都在 Request 类中。

占位图和失败图

占位图支持 resId 和 drawable 两种,并且互斥,如果同时设置两个则会抛出异常:

1
2
public RequestCreator placeholder(@DrawableRes int placeholderResId)
public RequestCreator noPlaceholder() //不设置占位图,与设置占位图互斥

标签

1
public RequestCreator tag(@NonNull Object tag)

对一次请求进行标记,可用于对请求进行暂停、恢复、取消等操作。

图片处理

1
2
3
4
5
6
7
8
9
10
public RequestCreator fit() //调整图片大小为 ImageView 大小
public RequestCreator resize(int targetWidth, int targetHeight)
public RequestCreator centerCrop()
public RequestCreator centerInside()
public RequestCreator onlyScaleDown() //仅在图片大于 target 大小时对图片进行缩放处理
public RequestCreator rotate(float degrees) // bitmap 旋转角度
public RequestCreator config(@NonNull Bitmap.Config config) // bitmap config
public RequestCreator transform(@NonNull Transformation transformation) // 对 bitmap 对象做自定义处理
public RequestCreator purgeable() // bitmap复用
public Builder transform(@NonNull Transformation transformation)

Transformation 用于对加载后的 bitmap 做处理:

1
2
3
4
public interface Transformation {
Bitmap transform(Bitmap source);
String key();
}

缓存策略

1
2
3
public RequestCreator stableKey(@NonNull String stableKey) // 设置缓存 key 值,拥有相同 key 值的资源将被视为是相同资源
public RequestCreator memoryPolicy(@NonNull MemoryPolicy policy, @NonNull MemoryPolicy... additional) // 内存缓存策略
public RequestCreator networkPolicy(@NonNull NetworkPolicy policy, @NonNull NetworkPolicy... additional) //磁盘缓存策略

MemoryPolicy 用于指定内存缓存的策略:

1
2
3
4
5
public enum MemoryPolicy {
NO_CACHE(1 << 0), //请求时跳过内存缓存中查询
NO_STORE(1 << 1); //请求后跳过缓存到内存中
...
}

NetworkPolicy 用于指定磁盘缓存的策略,而 Picasso 中磁盘缓存是基于 OkHttp 的缓存来实现:

1
2
3
4
5
public enum NetworkPolicy {
NO_CACHE(1 << 0), //跳过从磁盘缓存查询,直接通过网络获取资源
NO_STORE(1 << 1), //跳过写入到磁盘缓存
OFFLINE(1 << 2); //跳过从网络获取,直接从磁盘缓存中获取
}

优先级

1
2
3
4
5
6
public RequestCreator priority(@NonNull Priority priority) // 请求优先级,用于对请求任务进行排序
public enum Priority {
LOW,
NORMAL,
HIGH
}

用于在前文说到的线程池阻塞队列进行请求任务排序。

加载动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public RequestCreator noFade() // 加载图片到 ImageView 时不显示渐变动画

// com.squareup.picasso.PicassoDrawable#draw
@Override public void draw(Canvas canvas) {
if (!animating) {
super.draw(canvas);
} else {
float normalized = (SystemClock.uptimeMillis() - startTimeMillis) / FADE_DURATION;
if (normalized >= 1f) {
animating = false;
placeholder = null;
super.draw(canvas);
} else {
if (placeholder != null) {
placeholder.draw(canvas);
}
// setAlpha will call invalidateSelf and drive the animation.
int partialAlpha = (int) (alpha * normalized);
super.setAlpha(partialAlpha);
super.draw(canvas);
super.setAlpha(alpha);
}
}
}

PicassoDrawable 继承自 BitmapDrawable,通过重写 onDraw() 方法实现了渐变过渡动画。

请求处理

对应于序列图中步骤 7 以后。

请求入队

into() 中,首先会通过 createRequest() 来创建一个 Request 请求对象,然后通过 createKey() 来创建请求的 key 值,这里 key 值的创建通过 stableKey、uri、rotationDegrees、resize、centerCrop 等属性组合而成,因此即使请求的是同个资源,如果其中有任何一个属性有变化,由于生成的 key 值不同,也会重新去请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void into(ImageView target, Callback callback) {
...
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 (callback != null) {
callback.onSuccess();
}
return;
}
}
...
Action action = new ImageViewAction(...);
picasso.enqueueAndSubmit(action);
}

如果没有从内存中读取到,会先创建一个 Action 对象,Action 是一个抽象类:

1
2
3
4
5
6
abstract class Action<T> {
...
abstract void complete(Bitmap result, Picasso.LoadedFrom from);
abstract void error(Exception e);
...
}

它有几个实现类:FetchAction、GetAction、ImageViewAction 等,分别对应了 fetch()get()into(ImageView) 方法。接着会将这个 action 对象通过 Picasso.enqueueAndSubmit() 重新又发送到了 Picasso 中:

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

请求发送

这里 action 对象最终会被传入到 Dispatcher#performSubmit() 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//com.squareup.picasso.Dispatcher#performSubmit()
void performSubmit(Action action, boolean dismissFailed) {
//如果该请求已经暂停,则加入到 pausedActions 中
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 = forRequest(action.getPicasso(), this, cache, stats, action);
//将请求封装成为 Runnable 对象并发送到线程池中
hunter.future = service.submit(hunter);
hunterMap.put(action.getKey(), hunter);
...
}

forRequest() 会返回一个 BitmapHunter 对象,它继承自 Runnable,当 run() 执行时,会通过 hunt()来获取 Bitmap,这里就是真正来解析 Bitmap 的地方,首先还是会去内存里读取,接着会调用 RequestHandler 的 load() 来加载资源,然后对将加载得到的 bitmap 进行处理,具体如何处理则取决于之前在 RequestCreator 中所设置,这一步处理完成后,还要对用户自定义的 Transformation 来进行,简化后的代码流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Bitmap hunt() throws IOException {
Bitmap bitmap = null;
//从内存缓存读取
if (shouldReadFromMemoryCache(memoryPolicy)) {
bitmap = cache.get(key);
}
//从 requestHandler 加载
RequestHandler.Result result = requestHandler.load(data, networkPolicy);
if (bitmap != null) {
// 对 bitmap 做处理,如 fit
if (data.needsTransformation() || exifOrientation != 0) {
synchronized (DECODE_LOCK) {
if (data.needsMatrixTransform() || exifOrientation != 0) {
bitmap = transformResult(data, bitmap, exifOrientation);
}
//用户自定义 bitmap 处理逻辑
if (data.hasCustomTransformations()) {
bitmap = applyCustomTransformations(data.transformations, bitmap);
}
}
}
}
return bitmap;
}

看下 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
30
31
32
33
34
35
36
37
38
39
40
41
@Override public Result load(Request request, int networkPolicy) throws IOException {
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);
}
Picasso.LoadedFrom loadedFrom = response.cacheResponse() == null ? NETWORK : DISK;
if (loadedFrom == DISK && body.contentLength() == 0) {
body.close();
throw new ContentLengthException("Received response with 0 content-length header.");
}
if (loadedFrom == NETWORK && body.contentLength() > 0) {
stats.dispatchDownloadFinished(body.contentLength());
}
return new Result(body.source(), loadedFrom);
}

private static okhttp3.Request createRequest(Request request, int networkPolicy) {
CacheControl cacheControl = null;
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();
}
}
okhttp3.Request.Builder builder = new okhttp3.Request.Builder().url(request.uri.toString());
if (cacheControl != null) {
builder.cacheControl(cacheControl);
}
return builder.build();
}

首先会调用 createRequest() 来创建一个 Okhttp 的 Request 对象,会根据之前定义的磁盘缓存逻辑来设置 Request 的 CacheControl。接着会将这个创建好的 Request 对象传给 OkHttp3Downloader 来调用 okhttp3.Call.Factory#newCall() 发送网络请求。

请求结果缓存

通过 BitmapHunter.hunt() 获取到 Bitmap 后,会通过 Dispatcher#dispatchComplete() 来回调结果到 Dispatcher 中,在这里会对结果进行缓存处理:

1
2
3
4
5
6
7
8
//com.squareup.picasso.Dispatcher#performComplete
void performComplete(BitmapHunter hunter) {
if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
cache.set(hunter.getKey(), hunter.getResult());
}
hunterMap.remove(hunter.getKey());
batch(hunter);
}

最终结果依次被回调到 ImageViewAction#complete() 或者是 ImageViewAction#error() 中来分别处理加载成功和失败的逻辑,加载成功则将 bitmap 封装成 PicassoDrawable 后设置给 ImageView,加载失败则显示设置的失败图资源。

总结

  • 二级缓存,LruCache 的内存缓存和基于 Okhttp Cache 的磁盘缓存,内存缓存只会缓存最终处理后的 bitmap,不会对原 bitmap 也进行缓存
  • 线程池线程数可根据网络状态进行动态切换
  • 不支持 Gif 格式加载
  • 记录缓存命中率