概述
picasso是Square公司开源的一个Android图形缓存库,地址http://square.github.io/picasso/,可以实现图片下载和缓存功能。仅仅只需要一行代码就能完全实现图片的异步加载:
Picasso.with(this).load(uri).error(R.mipmap.ic_launcher).into(iv);
还提供了一些辅助功能:
Picasso.with(this).load(uri)
.placeholder(new ColorDrawable(Color.GRAY))// 占位符:网络请求中显示的图片
.error(R.mipmap.ic_launcher)// 图片请求异常显示的图片
.transform(new Transformation() {
@Override
public Bitmap transform(Bitmap source) {
// 在此处可以任意处理图片
return null;
}
@Override
public String key() {
return null;
}
})// 自定义的图片处理
.resize(300,300)
.centerCrop()// 居中显示图片,多余部分裁剪。使用此方法必须先设置resize,并且不能与centerInside一起使用
// .centerInside()// 显示完整的图片。使用此方法必须先设置resize,并且不能与centerCrop一起使用
// .fit()// 填充,使用此方法时,不能使用 resize、centerCrop、centerInside
.into(iv);
源码分析
按照使用流程进入源码一探究竟
Picasso的初始化
Picasso picasso = Picasso.with(this);
源码
public static Picasso with(Context context) {
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
singleton = new Builder(context).build();
}
}
}
return singleton;
}
典型的单例模式+建造者模式,最后返回Picasso对象。紧接着再看看build()方法里具体的构建过程
public Builder(Context context) {
if (context == null) {
throw new IllegalArgumentException("Context must not be null.");
}
this.context = context.getApplicationContext();
}
Builder的构造方法里面,保存了应用程序的全局上下文
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);
}
}
可以看到在build()方法里面初始化了一些默认的配置,下载器、缓存策略、线程池、事务分发器。在此不做具体分析,接着往下看。picasso的load方法:
picasso.load(uri);
可以看到load方法返回了一个RequestCreator 对象。
public RequestCreator load(Uri uri) {
return new RequestCreator(this, uri, 0);
}
RequestCreator的构造方法
RequestCreator(Picasso picasso, Uri uri, int resourceId) {
if (picasso.shutdown) {//如果picasso是关闭的,直接抛异常
throw new IllegalStateException(
"Picasso instance already shut down. Cannot submit new requests.");
}
this.picasso = picasso;//持有picasso对象
//初始化一个Request.Builder对象
this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}
Request.Builder():记录了uri,resourceId ,bitmapConfig
Builder(Uri uri, int resourceId, Bitmap.Config bitmapConfig) {
this.uri = uri;
this.resourceId = resourceId;
this.config = bitmapConfig;
}
到此发现:load方法返回了一个RequestCreator对象,在RequestCreator的构造方法里面又初始化了一个Request.Builder对象。
此处粗略看一下RequestCreator提供了哪些方法
placeholder(int placeholderResId)//设置加载图片中的占位图片,设置一个资源ID
placeholder(Drawable placeholderDrawable)//设置加载图片中的占位图片,设置一个Drawable
error(int errorResId)// 图片加载异常的时候显示的图片
error(Drawable errorDrawable)
tag(Object tag)//设置一个标记(标记是逻辑地关联可以一起管理的相关请求的一种简单方法,例如暂停、恢复或取消。)
resizeDimen(int targetWidthResId, int targetHeightResId)//指定图片大小
resize(int targetWidth, int targetHeight)
centerCrop()//设置图片显示的策略
centerInside()
rotate(float degrees)//设置图片旋转角度
rotate(float degrees, float pivotX, float pivotY)
transform(Transformation transformation)//设置图片转换的自定义策略
transform(List<? extends Transformation> transformations)
fit()//填充ImageView , 不能和resize一起使用
public RequestCreator fit() {
deferred = true;
return this;
}
可以看到上面都是一些我们常用的对图片进行操作的一些API,到此我们知道了RequestCreator提供了一些图片的基本操作,可以设置图片显示的大小,可以对图片做一些自定义的操作。至于 RequestCreator具体是做什么的?Request.Builder是做什么的?我们先放在这里,继续往下看
load方法执行后紧接着是into方法
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) {//如果调用了fit()方法
if (data.hasSize()) {//如果有设置resize()抛出异常
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);//创建一个Request ,传入一个Request 开始时间
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());
}
//创建一个ImageViewAction
Action action =
new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);
picasso.enqueueAndSubmit(action);//加入队列并提交
}
此方法用来创建一个Request
private Request createRequest(long started) {
int id = nextId.getAndIncrement();
Request request = data.build();//调用Request.Builder的build方法
request.id = id;
request.started = started;
...
省略了调试相关代码,和请求转换器的代码
...
return request ;
}
到此into()方法的内容看完了,在into()方法中创建了Request和Action对象,调用picasso对象的enqueueAndSubmit()方法加入队列并提交。可以看到Request.Builder是用来创建Request对象的
,前面的问题我们也可有个大致的猜想:RequestCreator字面意思就是一个Request创造者,在RequestCreator的构造方法中创建了Request.Builder对象,并在into()方法中调用了Request.Builder的build()方法,生成了一个Request对象,然后作为参数创建了一个Action对象。到此我们并没有看到具体的网络请求,并且引入了新的问题,Request是做什么的?Action是做什么的?
带着问题我们先来看一下Request.Builder的build()方法
public Request build() {
if (centerInside && centerCrop) {//这两个不能同时使用
throw new IllegalStateException("Center crop and center inside can not be used together.");
}
//使用centerCrop ,必须先设置resize()
if (centerCrop && (targetWidth == 0 && targetHeight == 0)) {
throw new IllegalStateException(
"Center crop requires calling resize with positive width and height.");
}
//使用centerInside ,必须先设置resize()
if (centerInside && (targetWidth == 0 && targetHeight == 0)) {
throw new IllegalStateException(
"Center inside requires calling resize with positive width and height.");
}
if (priority == null) {
priority = Priority.NORMAL;//设置优先级
}
return new Request(uri, resourceId, stableKey, transformations, targetWidth, targetHeight,
centerCrop, centerInside, onlyScaleDown, rotationDegrees, rotationPivotX, rotationPivotY,
hasRotationPivot, config, priority);
}
此方法先是做了一些验证,然后调用了Request的构造方法
private Request(Uri uri, int resourceId, String stableKey, List<Transformation> transformations,
int targetWidth, int targetHeight, boolean centerCrop, boolean centerInside,
boolean onlyScaleDown, float rotationDegrees, float rotationPivotX, float rotationPivotY,
boolean hasRotationPivot, Bitmap.Config config, Priority priority) {
this.uri = uri;//图片uri
this.resourceId = resourceId;//图片资源id
this.stableKey = stableKey;
if (transformations == null) {
this.transformations = null;
} else {
this.transformations = unmodifiableList(transformations);
}
this.targetWidth = targetWidth;//重定义的宽
this.targetHeight = targetHeight;//重定义的高
this.centerCrop = centerCrop;
this.centerInside = centerInside;
this.onlyScaleDown = onlyScaleDown;
this.rotationDegrees = rotationDegrees;
this.rotationPivotX = rotationPivotX;
this.rotationPivotY = rotationPivotY;
this.hasRotationPivot = hasRotationPivot;
this.config = config;
this.priority = priority;
}
可以看到,Request就是记录了我们加载图片时设置的一些参数:uri、宽、高、图片显示方式、旋转角度
我们接下来看一下ImageViewAction的构造方法
ImageViewAction(Picasso picasso, ImageView imageView, Request data, int memoryPolicy,
int networkPolicy, int errorResId, Drawable errorDrawable, String key, Object tag,
Callback callback, boolean noFade) {
super(picasso, imageView, data, memoryPolicy, networkPolicy, errorResId, errorDrawable, key,
tag, noFade);
this.callback = callback;
}
此处记录下了图片加载的监听,调用了父类的构造
Action(Picasso picasso, T target, Request request, int memoryPolicy, int networkPolicy,
int errorResId, Drawable errorDrawable, String key, Object tag, boolean noFade) {
this.picasso = picasso;
this.request = request;//一个具体的请求
this.target =
target == null ? null : new RequestWeakReference<T>(this, target, picasso.referenceQueue);// into()的目标
this.memoryPolicy = memoryPolicy;//内存缓存策略
this.networkPolicy = networkPolicy;//网络缓存策略
this.noFade = noFade;
this.errorResId = errorResId;
this.errorDrawable = errorDrawable;
this.key = key;
this.tag = (tag != null ? tag : this);
}
可以看到Action就是记录了picasso、request、target、memoryPolicy 、networkPolicy 等的对象
我们接下来去看一下picasso的enqueueAndSubmit()方法
void enqueueAndSubmit(Action action) {
Object target = action.getTarget();
//此处判断了一下,target(就是我们要设置图片的ImageView)是否存在,并且该target是否已经有action了,如果有且不等于当前的action,就取消之前的,添加新的action。此处就是对当我们在listview中使用时,会出现的图片错位,重复加载做了优化
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);//提交Action
}
void submit(Action action) {
dispatcher.dispatchSubmit(action);
}
可以看到,submit()方法中使用了dispatcher来分发了action。
看一下具体的分发逻辑
void dispatchSubmit(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}
此处发送了一个消息,我们来看一下handler是哪一个
this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
发现是DispatcherHandler来接受处理的,这是一个子线程中的handler,进入看一下具体的处理方法
@Override public void handleMessage(final Message msg) {
switch (msg.what) {
case REQUEST_SUBMIT: {
Action action = (Action) msg.obj;
dispatcher.performSubmit(action);
break;
}
....
}
}
在此处调用了dispatcher.performSubmit(action);来处理
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;
}
//创建一个BitmapHunter对象
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());
}
}
可以看到,在此方法里面,创建了一个BitmapHunter,这是一个实现了Runnable接口的类,并最终通过线程池提交了该任务
我们查看一下该任务具体的执行方法,在run中调用了hunt() 方法
@Override public void run() {
.....
result = hunt();
if (result == null) {
dispatcher.dispatchFailed(this);
} else {
// 请求成功后通过事务分发器,分发事务
dispatcher.dispatchComplete(this);
}
.....
}
Bitmap hunt() throws IOException {
Bitmap bitmap = null;
.....
// 在此调用了具体的网络请求方法
RequestHandler.Result result = requestHandler.load(data, networkPolicy);
.....
return bitmap;
}
void dispatchComplete(BitmapHunter hunter) {
handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
}
具体的分发代码
case HUNTER_COMPLETE: {
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performComplete(hunter);
break;
}
中间省略,最终调用了下面的代码,交给了picasso中handler处理
mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
最终调用了ImageViewAction的complete()方法,
@Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
....
PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);
if (callback != null) {
callback.onSuccess();
}
}
可以看到最后是调用了PicassoDrawable的setBitmap()方法来设置图片