Picasso是Square公司出品的一款非常优秀的开源图片加载库,是目前Android开发中超级流行的图片加载库之一,今天我们就来分析下他的使用及实现流程。
使用简介
首先在项目中引入picasso(以gradle为例)
compile 'com.squareup.picasso:picasso:2.5.2'
传统的ImageVIew设置图片
Picasso.with(
context)
.load(
url)
.placeholder(
R.drawable.tab_item_bg)
.into(
imageView);
自定义的布局设置图片,target是指实现了Target接口的自定义View
Picasso.with(
context)
.load(
url)
.placeholder(
R.drawable.tab_item_bg)
.into(
target);
adapter中的使用
@Override
public void getView(int position, View convertView, ViewGroup
parent) {
SquaredImageView view = (SquaredImageView) convertView;
if (view ==
null) {
view =
new SquaredImageView(context);
}
String url = getItem(position);
Picasso.
with(context).load(url).
into(view);
}
自动设置图片宽高像素的大小
Picasso
.with(
context)
.load(
url)
.resize(50, 50)
.centerCrop()
.into(
imageView)
流程分析
接下来我们就以上面的链式调用来分析Picasso的实现流程,首先来认识几个类
RequestHandler
BitmapHunter
PicassoDrawable
MEMORY(Color.GREEN)
DISK(Color.BLUE)
NETWORK(Color.RED)
DeferredRequestCreator
Action
流程分析
1. Picasso对象的创建
public static Picasso with(Context context) {
if (singleton ==
null) {
synchronized (Picasso.class) {
if (singleton ==
null) {
singleton =
new Builder(context).build();
}
}
}
return singleton;
}
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); }}
2. 加载url,创建并返回一个图片下载请求的构建器RequestCreator
public RequestCreator load(
String path) {
if (path ==
null) {
return new RequestCreator(
this,
null,
0);
}
if (path.trim().length() ==
0) {
throw new IllegalArgumentException(
"Path must not be empty.");
}
return load(Uri.parse(path));
}
RequestCreator(Picasso picasso, Uri uri, int resourceId) {
if (picasso.shutdown) {
throw new IllegalStateException(
"Picasso instance already shut down. Cannot submit new requests.");
}
this.picasso = picasso;
this.data =
new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}
public Request build() {
if (centerInside && centerCrop) {
throw new IllegalStateException(
"Center crop and center inside can not be used together.");
}
if (centerCrop && (targetWidth ==
0 && targetHeight ==
0)) {
throw new IllegalStateException(
"Center crop requires calling resize with positive width and height.");
}
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, purgeable, config, priority); }}
3. 设置默认图片及出错图片
public RequestCreator placeholder(int placeholderResId) {
if (!setPlaceholder) {
throw new IllegalStateException(
"Already explicitly declared as no placeholder.");
}
if (placeholderResId ==
0) {
throw new IllegalArgumentException(
"Placeholder image resource invalid.");
}
if (placeholderDrawable !=
null) {
throw new IllegalStateException(
"Placeholder image already set.");
}
this.placeholderResId = placeholderResId;
return this;
}
4. 修改图片的尺寸,填充图片进ImageView
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) {
if (data.hasSize()) {
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);
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;
}
}
if (setPlaceholder) {
setPlaceholder(
target, getPlaceholderDrawable());
}
Action action =
new ImageViewAction(picasso,
target, request,
memoryPolicy, networkPolicy, errorResId,errorDrawable, requestKey, tag, callback, noFade);
picasso.enqueueAndSubmit(action);
}
5. Dispatcher(任务分发器)会通过Handler来提交任务,然后交由Dispatcher的performSubmit方法来执行
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);
if (dismissFailed) {
failedActions.
remove(action.getTarget());
}
...
}
6.根据不同的加载路径,选择合适的RequestHandler来创建BitmapHunter
static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action) {
Request request = action.getRequest();
List<RequestHandler> requestHandlers = picasso.getRequestHandlers();
for (
int i =
0,
count = requestHandlers.
size(); i <
count; i++) {
RequestHandler requestHandler = requestHandlers.get(i);
if (requestHandler.canHandleRequest(request)) {
return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
}
}
return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);}
7. BitmapHunter被提交到线程池之后,接下来就该run方法执行了
@Override
public void run() {
try {
updateThreadName(
data);
...
}
result = hunt();
if (result ==
null) {
dispatcher.dispatchFailed(
this);
}
else {
dispatcher.dispatchComplete(
this);
}
}
catch (Downloader.ResponseException e) {
if (!e.localCacheOnly || e.responseCode !=
504) {
...
}
dispatcher.dispatchFailed(
this);
}
catch (IOException e) {
...
dispatcher.dispatchRetry(
this);
}
catch (OutOfMemoryError e) {
StringWriter writer = new StringWriter();
stats.createSnapshot().dump(new PrintWriter(writer));
exception = new RuntimeException(writer.toString(), e);
dispatcher.dispatchFailed(
this);
}
catch (Exception e) {
...
dispatcher.dispatchFailed(
this);
}
finally {
...
}
}
8. 重点:bitmap的获取(包括获取途径(内存,硬盘,网络)的判断以及加载)
Bitmap hunt() throws IOException {
Bitmap bitmap =
null;
if (shouldReadFromMemoryCache(memoryPolicy)) {
bitmap = cache.
get(key);
if (bitmap !=
null) {
stats.dispatchCacheHit();
loadedFrom = MEMORY;
...
return bitmap;
}
}
data.networkPolicy = retryCount ==
0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
RequestHandler.Result result = requestHandler.load(
data, networkPolicy);
if (result !=
null) {
loadedFrom = result.getLoadedFrom();
exifOrientation = result.getExifOrientation();
bitmap = result.getBitmap();
if (bitmap ==
null) {
InputStream
is = result.getStream();
try {
bitmap = decodeStream(
is,
data);
}
finally {
Utils.closeQuietly(
is);
}
}
}
if (bitmap !=
null) {
...
stats.dispatchBitmapDecoded(bitmap);
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);
...
}
}
if (bitmap !=
null) {
stats.dispatchBitmapTransformed(bitmap);
}
}
}
return bitmap;
}
9.以网络图片为例介绍下从硬盘和网络加载图片的流程
@
Override
public
Response load(
Uri uri, int networkPolicy) throws
IOException {
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();
}
}
Request.Builder builder = new Request.Builder().url(uri.toString());
if (cacheControl != null) {
builder.cacheControl(cacheControl);
}
okhttp3.Response response = client.newCall(builder.build()).execute();
int responseCode = response.code();
if (responseCode >= 300) {
response.body().close();
throw new ResponseException(responseCode + " " + response.message(), networkPolicy, responseCode);
}
boolean fromCache = response.cacheResponse() != null;
ResponseBody responseBody = response.body();
return new Response(responseBody.byteStream(), fromCache, responseBody.contentLength());
}
10. 当从硬盘或者服务器获取Bitmap之后,就可以通过Action来执行各种自定义的callback了
void complete(BitmapHunter hunter) {
Action single = hunter.getAction();
List<Action> joined = hunter.getActions();
boolean hasMultiple = joined !=
null && !joined.isEmpty();
boolean shouldDeliver = single !=
null || hasMultiple;
if (!shouldDeliver) {
return;
}
Uri uri = hunter.getData().uri;
Exception exception = hunter.getException();
Bitmap result = hunter.getResult();
LoadedFrom
from = hunter.getLoadedFrom();
if (single !=
null) {
deliverAction(result,
from, single);
}
if (hasMultiple) {
for (
int i =
0, n = joined.size(); i < n; i++) {
Action
join = joined.
get(i);
deliverAction(result,
from,
join);
}
}
if (listener !=
null && exception !=
null) {
listener.onImageLoadFailed(
this, uri, exception);
}
}
private void deliverAction(Bitmap result, LoadedFrom from, Action action) {
if (action.isCancelled()) {
return;
}
if (!action.willReplay()) {
targetToAction.
remove(action.getTarget());
}
if (result !=
null) {
if (
from ==
null) {
throw new AssertionError(
"LoadedFrom cannot be null.");
}
action.complete(result,
from);
...
}
else {
action.error();
...
}
}
以ImageViewAction为例看下complete中的实现
@Override
public void complete(Bitmap result, Picasso.LoadedFrom from) {
if (result ==
null) {
throw new AssertionError(String.format(
"Attempted to complete action with no result!\n%s",
this));
}
ImageView
target =
this.
target.get();
if (
target ==
null) {
return;
}
Context context = picasso.context;
boolean indicatorsEnabled = picasso.indicatorsEnabled;
PicassoDrawable.setBitmap(
target, context, result, from, noFade, indicatorsEnabled);
if (callback !=
null) {
callback.onSuccess();
}
}
static void setBitmap(ImageView target, Context context, Bitmap bitmap,
Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {
Drawable placeholder =
target.getDrawable();
if (placeholder
instanceof AnimationDrawable) {
((AnimationDrawable) placeholder).stop();
}
PicassoDrawable drawable =
new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);
target.setImageDrawable(drawable);}
到这里,Picasso加载图片的逻辑就分析完了。下面我们看下Square还留给我们什么其他可以学习的东西。
Picasso的引用清理策略
Picasso的缓存是对请求的缓存,通过WeakReference与ReferenceQueue的联合使用来缓存请求,然后 关于ReferenceQueue请看下文的详细介绍 WeakReference与ReferenceQueue联合使用构建java高速缓存
static class RequestWeakReference<M> extends WeakReference<M> {
final Action action;
public RequestWeakReference(Action action, M referent, ReferenceQueue<? super M> q) {
super(referent, q);
this.action = action;
}
}
Action(Picasso picasso, T
target, Request request,
int memoryPolicy,
int networkPolicy,
int errorResId, Drawable errorDrawable, String key, Object tag,
boolean noFade) {
...
this.
target =
target ==
null ?
null :
new RequestWeakReference<T>(
this,
target, picasso.referenceQueue);
...
}
@Override
public void run() {
Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
while (
true) {
try {
RequestWeakReference<?> remove = (RequestWeakReference<?>) referenceQueue.remove(THREAD_LEAK_CLEANING_MS);
Message message =
handler.obtainMessage();
if (remove !=
null) {
message.what = REQUEST_GCED;
message.obj = remove.action;
handler.sendMessage(message);
}
else {
message.recycle();
}
}
catch (InterruptedException e) {
break;
}
catch (
final Exception e) {
handler.post(
new Runnable() {
@Override
public void run() {
throw new RuntimeException(e);
}
});
break;
}
}
}
Picasso优先级策略
请求的优先级
public
enum Priority {
LOW,
// 只有当通过fetch方法(不需要ImageView,仅需要下载Bitmap并执行回调)请求图片时才会给request设置该优先级
NORMAL,
// 正常的请求优先级
HIGH/
/
}
图片下载任务的优先级
真实的项目开发过程中,只请求图片而不设置给ImageView毕竟是少数,所以大多数的任务依然会是NORMAL级别的,同级别的任务数太多,那么优先级策略就没有太大的效果,所以,Picasso在执行图片下载任务时,又做了第二次优先级划分
1. 依赖于优先级队列的线程池
PicassoExecutorService() {
super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT,
0, TimeUnit.MILLISECONDS,
new PriorityBlockingQueue<Runnable>(),
new Utils.PicassoThreadFactory());
}
2. 实现了Comparable接口的FutureTask
private static final class PicassoFutureTask extends FutureTask<BitmapHunter>
implements Comparable<PicassoFutureTask> {
private final BitmapHunter hunter;
public PicassoFutureTask(BitmapHunter hunter) {
super(hunter,
null);
this.hunter = hunter;
}
@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());
}
}
PicassoExecutorService(Picasso自己封装的线程池,对移动网络做了处理,并提供了支持优先级比较的FutureTask)
switch (info.getType()) {
case ConnectivityManager.
TYPE_WIFI:
case ConnectivityManager.
TYPE_WIMAX:
case ConnectivityManager.
TYPE_ETHERNET:
setThreadCount(
4);
break;
case ConnectivityManager.
TYPE_MOBILE:
switch (info.getSubtype()) {
case TelephonyManager.
NETWORK_TYPE_LTE:
case TelephonyManager.
NETWORK_TYPE_HSPAP:
case TelephonyManager.
NETWORK_TYPE_EHRPD:
setThreadCount(
3);
break;
case TelephonyManager.
NETWORK_TYPE_UMTS:
case TelephonyManager.
NETWORK_TYPE_CDMA:
case TelephonyManager.
NETWORK_TYPE_EVDO_0:
case TelephonyManager.
NETWORK_TYPE_EVDO_A:
case TelephonyManager.
NETWORK_TYPE_EVDO_B:
setThreadCount(
2);
break;
case TelephonyManager.
NETWORK_TYPE_GPRS:
case TelephonyManager.
NETWORK_TYPE_EDGE:
setThreadCount(
1);
break;
default:
setThreadCount(DEFAULT_THREAD_COUNT);
}
break;
default:
setThreadCount(DEFAULT_THREAD_COUNT);
}
}
OKHttp的拦截器链设计模式的应用(简介)
当执行如下代码时,如果底层是通过OKHttp来请求图片,会先执行OKHttp自带的拦截器中的方法,拦截器中的逻辑执行完之后才会执行真正的图片请求
okhttp3.
Response response = client.newCall(builder.build()).
execute();
看一下Call接口的实现类RealCall中execute方法的具体实现。
@Override
public Response execute() throws IOException {
...
try {
Response result = getResponseWithInterceptorChain(
false);
...
return result;
}
finally {
client.dispatcher().finished(
this);
}
}
private Response getResponseWithInterceptorChain(
boolean forWebSocket)
throws IOException {
Interceptor.Chain chain =
new ApplicationInterceptorChain(
0, originalRequest, forWebSocket);
return chain.proceed(originalRequest);
}
class ApplicationInterceptorChain implements Interceptor.Chain {
private final int index;
...
ApplicationInterceptorChain(
int index, Request request,
boolean forWebSocket) {
this.
index =
index;
...
}
...
@Override
public Response proceed(Request request) throws IOException {
if (
index <
client.interceptors().size()) {
Interceptor.Chain chain =
new ApplicationInterceptorChain(
index +
1, request, forWebSocket);
Interceptor interceptor =
client.interceptors().get(
index);
Response interceptedResponse = interceptor.intercept(chain);
if (interceptedResponse ==
null) {
throw new NullPointerException(
"application interceptor " + interceptor +
" returned null");
}
return interceptedResponse;
}
return getResponse(request, forWebSocket);
}
}
到这里,Picasso的使用介绍及流程分析就全部介绍完了,如果有不对的地方请留言指正,接下来,老衲将会带领大家分析另一款超优秀的图片加载框架Glide,敬请期待