一 介绍
Square公司开源的图片加载库。优点是功能还算完善,能满足基本的图片加载需求,使用简单,体量轻易推倒。
官方链接:http://square.github.io/picasso/
Git: https://github.com/square/picasso
二 分析
1. 简单调用
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);
2. 流程图
3. 核心类分析
(主流程顺序,以下贴出的代码都是经过筛选了比较重要的代码)
①Picasso
-- 单例,构造ExecutorService线程池(默认3个线程),Dispatcher,LruCache,Downloader(调用okhttp),其他基本没干什么正事。
//Builder模式构建Picasso实例
public static class Builder {
private final Context context;
private Downloader downloader;
private ExecutorService service;
private Cache cache;
private Listener listener;
private RequestTransformer transformer;
private List<RequestHandler> requestHandlers;
private Bitmap.Config defaultBitmapConfig;
private boolean indicatorsEnabled;
private boolean loggingEnabled;
/** Start building a new {@link Picasso} instance. */
public Builder(Context context) {
if (context == null) {
throw new IllegalArgumentException("Context must not be null.");
}
this.context = context.getApplicationContext();
}
....//省略以下代码
}
②RequestCreator
-- 主流程调用的方法into(), 里面做的最重要的两件事:1.创建Request, 2.把Action加到Dispatcher队列。(Request, Action, Dispatcher几个概念后面会做说明)
public void into(ImageView target, Callback callback) {
Request request = createRequest(started);
String requestKey = createKey(request);
//查看内存缓存是否已经有了,有的话直接set,不用再去构造Action获取了
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;
}
}
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
//构造Action
Action action =
new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);
//扔给任务调度器
picasso.enqueueAndSubmit(action);
}
③Request
-- 请求model,存储请求相关的数据,包括Transformation list,部分内置的转换属性,Bitmap压缩属性, 任务优先级, 图片资源的相关信息等。
//类结构,原生注释很详细
public final class Request {
private static final long TOO_LONG_LOG = TimeUnit.SECONDS.toNanos(5);
/** A unique ID for the request. */
int id;
/** The time that the request was first submitted (in nanos). */
long started;
/** The {@link NetworkPolicy} to use for this request. */
int networkPolicy;
/**
* The image URI.
* <p>
* This is mutually exclusive with {@link #resourceId}.
*/
public final Uri uri;
/**
* The image resource ID.
* <p>
* This is mutually exclusive with {@link #uri}.
*/
public final int resourceId;
/**
* Optional stable key for this request to be used instead of the URI or resource ID when
* caching. Two requests with the same value are considered to be for the same resource.
*/
public final String stableKey;
/** List of custom transformations to be applied after the built-in transformations. */
public final List<Transformation> transformations;
/** Target image width for resizing. */
public final int targetWidth;
/** Target image height for resizing. */
public final int targetHeight;
/**
* True if the final image should use the 'centerCrop' scale technique.
* <p>
* This is mutually exclusive with {@link #centerInside}.
*/
public final boolean centerCrop;
/**
* True if the final image should use the 'centerInside' scale technique.
* <p>
* This is mutually exclusive with {@link #centerCrop}.
*/
public final boolean centerInside;
public final boolean onlyScaleDown;
/** Amount to rotate the image in degrees. */
public final float rotationDegrees;
/** Rotation pivot on the X axis. */
public final float rotationPivotX;
/** Rotation pivot on the Y axis. */
public final float rotationPivotY;
/** Whether or not {@link #rotationPivotX} and {@link #rotationPivotY} are set. */
public final boolean hasRotationPivot;
/** True if image should be decoded with inPurgeable and inInputShareable. */
public final boolean purgeable;
/** Target image config for decoding. */
public final Bitmap.Config config;
/** The priority of this request. */
public final Priority priority;
....//省略以下代码
④Action
-- 可以看做单个图片加载的任务model,抽象类。
实现类可见下图,常用的是ImageViewAction,它会在抽象方法complete真正实现把Bitmap给ImageView的行为。
abstract class Action<T> {
final Picasso picasso; //picasso单例
final Request request; //当前任务的request
final WeakReference<T> target; //目标,imageview
final boolean noFade; //是否渐进渐出
final int memoryPolicy; //内存缓存策略
final int networkPolicy; //网络缓存策略
final int errorResId; //错误占位图资源id
final Drawable errorDrawable; //错误占位图资源drawable
final String key; //内存缓存的key
final Object tag; //dispatcher的任务标识
boolean willReplay; //是否再次放到任务队列中
boolean cancelled; //是否取消
//Bitmap加载成功回调
abstract void complete(Bitmap result, Picasso.LoadedFrom from);
//加载失败回调
abstract void error();
....//省略以下代码
⑤Dispatcher
-- 看名字就看出来是个任务分发调度器,
dispatcher方法流程:
class Dispatcher {
final DispatcherThread dispatcherThread; //dispatcher线程,主要作用是获取looper给handler用
final Context context;
final ExecutorService service; //picasso创建的线程池,唯一
final Downloader downloader; //下载器,唯一
final Map<String, BitmapHunter> hunterMap; //所有的BitmapHunter map
final Map<Object, Action> failedActions;
final Map<Object, Action> pausedActions;
final Set<Object> pausedTags;
final Handler handler; //dispatcher线程handler,任务调用使用
final Handler mainThreadHandler; //主线程handler,用于complete后的行为
final Cache cache; //内存缓存
final Stats stats;
final List<BitmapHunter> batch; //批量complete状态的BitmapHunter
final NetworkBroadcastReceiver receiver; //监控网络状态,用于网络缓存策略
//从Action构造BitmapHunter并且放到线程池中
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;
}
//构造request
hunter = forRequest(action.getPicasso(), this, cache, stats, action);
//执行hunter
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());
}
}
//单个任务完成 ,batch聚合成功任务结果
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");
}
}
//给picasso主线程处理批量结果
void performBatchComplete() {
List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);
batch.clear();
mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
logBatch(copy);
}
⑥BitmapHunter
-- 比较简单,实现了Runnable接口, 重点关注hunt()方法。
Bitmap hunt() throws IOException {
Bitmap bitmap = null;
//省略从内存读bitmap代码...
//从requestHandler 同步load result,RequestHandler见后面分析
RequestHandler.Result result = requestHandler.load(data, networkPolicy);
if (result != null) {
bitmap = result.getBitmap();
// If there was no Bitmap then we need to decode it from the stream.
if (bitmap == null) {
InputStream is = result.getStream();
bitmap = decodeStream(is, data);
}
}
if (bitmap != null) {
//如果需要做内置的转换,则转出相应的bitmap
if (data.needsTransformation() || exifOrientation != 0) {
bitmap = transformResult(data, bitmap, exifOrientation);
}
//如果需要做自定义的转换,则转出相应的bitmap
if (data.hasCustomTransformations()) {
bitmap = applyCustomTransformations(data.transformations, bitmap);
}
}
}
}
}
return bitmap;
}
⑦RequestHandler
-- 抽象类,抽象方法load() 方法获取Result, Result里有bitmap和inputstream
实现类:
我们来看下最常用的NetworkRequestHandler
class NetworkRequestHandler extends RequestHandler {
//根据request加载出bitmap或者inputstream
@Override public Result load(Request request, int networkPolicy) throws IOException {
//走Downloader请求,downloader内部是okhttp实现
Response response = downloader.load(request.uri, request.networkPolicy);
Bitmap bitmap = response.getBitmap();
if (bitmap != null) {
return new Result(bitmap, loadedFrom);
}
InputStream is = response.getInputStream();
if (is == null) {
return null;
}
}
BitmapHunter里List<RequestHandler>是在picasso的构造方法中注入的。
Picasso类相关代码:
List<RequestHandler> allRequestHandlers =
new ArrayList<RequestHandler>(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));
requestHandlers = Collections.unmodifiableList(allRequestHandlers);
BitmapHunter类相关代码:
List<RequestHandler> requestHandlers = picasso.getRequestHandlers();
for (int i = 0, count = requestHandlers.size(); i < count; i++) {
RequestHandler requestHandler = requestHandlers.get(i);
//责任链模式,第一个可用的handler直接使用
if (requestHandler.canHandleRequest(request)) {
return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
}
}
三 内部机制及优缺点
1、提供了双缓存,Memory缓存使用LinkedHashMap实现,提供命中率统计。
Disk缓存使用的是okhttp内部的,无法设置其他的Disk缓存,要想实现只能自己改造代码了。(OkHttpDownloader和OkHttp3Downloader)
2、自定制Bitmap转换,需要继承Transformation接口,在构造RequestCreator的时候add进去。类似切圆角,高斯模糊等需求都在这里完成。Picasso本身提供了Resize,CenterCrop,Rotate,CenterInside转换方式。
3、图片渲染只提供了fade方式, 见PicassoDrawable。
4、可load的图片资源种类:Resource,Asset, File, Http, (Https之前版本不支持,从最新依赖的okhttp 3.0.1看应该是支持的,未测试过)
5、不支持Gif, 支持webp。