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); 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)
流程的的序列图,接下来就通过围绕这个序列图来进行分析:
获取 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 public Builder defaultBitmapConfig (@NonNull Bitmap.Config bitmapConfig) Builder downloader (@NonNull Downloader downloader) public Builder executor (@NonNull ExecutorService executorService) 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(); 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 ; int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0 ); List<RequestHandler> allRequestHandlers = new ArrayList<>(builtInHandlers + extraCount); 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 () public RequestCreator resize (int targetWidth, int targetHeight) public RequestCreator centerCrop () public RequestCreator centerInside () public RequestCreator onlyScaleDown () public RequestCreator rotate (float degrees) public RequestCreator config (@NonNull Bitmap.Config config) public RequestCreator transform (@NonNull Transformation transformation) public RequestCreator purgeable () 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) 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 () @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); } 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) { 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 void performSubmit (Action action, boolean dismissFailed) { 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); 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.Result result = requestHandler.load(data, networkPolicy); if (bitmap != null ) { if (data.needsTransformation() || exifOrientation != 0 ) { synchronized (DECODE_LOCK) { if (data.needsMatrixTransform() || exifOrientation != 0 ) { bitmap = transformResult(data, bitmap, exifOrientation); } 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 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 格式加载
记录缓存命中率