Picasso 作为 Android 上一个老牌的图片加载库,似乎近些年在 Glide 的 “打压” 下已经变的黯然失色,但作为 square 出品的优秀框架,其实现的架构和思想仍然有许多值得借鉴和学习的地方,本文所使用的 Picasso 版本号为 2.71828,在 gradle 中依赖如下:
implementation 'com.squareup.picasso:picasso:2.71828'
Picasso 加载图片的方式和 Glide 类似,都是链式调用:
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)
,这一步则是对资源请求的封装,例如占位图、加载失败图、tag、资源裁剪等;最后则是请求处理阶段,对应 into(ImageView)
,这一步处理的事情最为繁重,包括资源请求、资源处理等,下图为 into(ImageView)
流程的的序列图,接下来就通过围绕这个序列图来进行分析:
获取 Picasso 对象
这一节对应于序列图中的步骤 1、2、3。
静态方法 get() 是通过 DCL 实现的单例模式,返回一个全局的 Picass 对象:
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 对象:
public final class PicassoProvider extends ContentProvider {
@SuppressLint("StaticFieldLeak") static Context context;
@Override public boolean onCreate() {
context = getContext();
return true;
}
...
}
并在自己的 AndroidManifest 文件中注册:
<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 来自行构建,主要提供了如下方法:
// 设置 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()
方法用于对资源进行情理:
public interface Downloader {
@NonNull Response load(@NonNull okhttp3.Request request) throws IOException; // 加载图片
void shutdown();
}
默认只有 OkHttp3Downloader 一个实现,其内部通过 OkHttp 进行网络请求下载图片:
@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() 来指定排序规则:
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 接口提供了基本的对于缓存内容的处理方法:
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 对象,如下所示:
public interface RequestTransformer {
Request transformRequest(Request request);
RequestTransformer IDENTITY = new RequestTransformer() {
@Override public Request transformRequest(Request request) {
return request;
}
};
}
请求处理器
RequestHandler 是一个抽象类:
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:
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 种图片来源的方法:
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 两种,并且互斥,如果同时设置两个则会抛出异常:
public RequestCreator placeholder(@DrawableRes int placeholderResId)
public RequestCreator noPlaceholder() //不设置占位图,与设置占位图互斥
标签
public RequestCreator tag(@NonNull Object tag)
对一次请求进行标记,可用于对请求进行暂停、恢复、取消等操作。
图片处理
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 做处理:
public interface Transformation {
Bitmap transform(Bitmap source);
String key();
}
缓存策略
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 用于指定内存缓存的策略:
public enum MemoryPolicy {
NO_CACHE(1 << 0), //请求时跳过内存缓存中查询
NO_STORE(1 << 1); //请求后跳过缓存到内存中
...
}
NetworkPolicy 用于指定磁盘缓存的策略,而 Picasso 中磁盘缓存是基于 OkHttp 的缓存来实现:
public enum NetworkPolicy {
NO_CACHE(1 << 0), //跳过从磁盘缓存查询,直接通过网络获取资源
NO_STORE(1 << 1), //跳过写入到磁盘缓存
OFFLINE(1 << 2); //跳过从网络获取,直接从磁盘缓存中获取
}
优先级
public RequestCreator priority(@NonNull Priority priority) // 请求优先级,用于对请求任务进行排序
public enum Priority {
LOW,
NORMAL,
HIGH
}
用于在前文说到的线程池阻塞队列进行请求任务排序。
加载动画
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 值不同,也会重新去请求。
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 是一个抽象类:
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 中:
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()
中:
//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 来进行,简化后的代码流程如下:
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()
逻辑:
@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 中,在这里会对结果进行缓存处理:
//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 格式加载
-
记录缓存命中率
网友评论