本文采用研究生论文格式编写,方便大家阅读。作者:谭东
摘要
Picasso是美国SQUARE移动支付公司开源的图片缓存加载库。可以实现图片下载和缓存功能,效率和性能都很不错。
Square公司官方博客:http://square.github.io/
Square公司Github地址:https://github.com/square
Square公司Picasso的Wiki地址:http://square.github.io/picasso/
Square公司Picasso的Github地址:https://github.com/square/picasso
1 绪论
Picasso的用法是链式调用,和Glide用法很像。当然也支持个性化设置,是目前比较流行的图片缓存库之一。
1.1 Picasso特点
Picasso是全尺寸下载图片,也就是把要加载的图片原图下载缓存下来。Picasso默认的缓存格式为ARGB_888,相对于RGB_565内存占用高了一半左右,当然图片质量显示要高一些。下面看下Picasso的主要特点:
①链式调用,使用简单;
②具有一般图片框架的基础功能;
③方便的图片转换;
④加载过程监听和错误处理;
⑤自动添加磁盘和内存二级缓存;
⑥支持多种数据源加载。
Picasso默认不支持Gif图片加载。Picasso库很小,类也很少,库仅118KB大小。最新版目前是2.5.2。
2 Picasso基本用法
2.1 引入Picasso
Jar包下载:https://search.maven.org/remote_content?g=com.squareup.picasso&a=picasso&v=LATEST
Gradle引用:
compile 'com.squareup.picasso:picasso:2.5.2'
Maven引用:
<dependency>
<groupId>com.squareup.picasso</groupId>
<artifactId>picasso</artifactId>
<version>2.5.2</version>
</dependency>
2.2 基本加载图片
图2.1 Picasso加载图片
链式调用,逻辑上比较清晰。
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);
改变图片尺寸和填充方式:
Picasso.with(context)
.load(url)
.resize(50, 50)
.centerCrop()
.into(imageView)
图片个性化转换,例如把图片显示加载成正方形,那么你就要自己写具体逻辑。继承Transformation,很简单,只需要重新按照你的要求去绘制和创建个Bitmap即可:
public class CropSquareTransformation implements Transformation {
@Override public Bitmap transform(Bitmap source) {
int size = Math.min(source.getWidth(), source.getHeight());
int x = (source.getWidth() - size) / 2;
int y = (source.getHeight() - size) / 2;
Bitmap result = Bitmap.createBitmap(source, x, y, size, size);
if (result != source) {
source.recycle();
}
return result;
}
@Override public String key() { return "square()"; }
}
加入加载中图片和加载出错图片:
Picasso.with(context)
.load(url)
.placeholder(R.drawable.user_placeholder)
.error(R.drawable.user_placeholder_error)
.into(imageView);
针对不同来源的图片加载,如文件、网络图片、Assets目录、Resource、Content Provider等:
Picasso.with(context).load(R.drawable.landing_screen).into(imageView1);
Picasso.with(context).load("file:///android_asset/DvpvklR.png").into(imageView2);
Picasso.with(context).load(new File(...)).into(imageView3);
图片的Log日志模式和显示图片的缓存来源的标识设置:
Picasso picasso = Picasso.with(this);
picasso.setIndicatorsEnabled(true);//蓝色:从本地缓存读取的图片,红色:从网络加载的图片,绿色:从内存缓存加载的图片
picasso.setLoggingEnabled(true);//日志调试模式
图2.2 Picasso加载图片来源标识
图片缓存颜色格式配置:
Picasso.with(context).load("http://square.github.io/picasso/static/sample.png").config(Bitmap.Config.RGB_565)
.into(imageView);
ARGB_8888要比RGB_565占用内存大一半左右,但图像质量差距不大一般。
3 Picasso高级用法
下面的代码是实现:
1.自定义Picasso全局线程池
2.自定义Picasso默认缓存目录
3.自定义监听Picasso加载图片进度,结合OKHTTP
package com.tandong.picassodemo;
import android.app.Application;
import com.tandong.picassodemo.utils.OkHttp3Downloader;
import com.tandong.picassodemo.utils.ProgressListener;
import com.squareup.picasso.Picasso;
import java.io.IOException;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import okhttp3.Cache;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Response;
/**
* Created by Administrator on 2017/4/12.
*/
/**
* 1.自定义Picasso全局线程池
* 2.自定义Picasso默认缓存目录
* 3.自定义监听Picasso加载图片进度,结合OKHTTP
*/
public class BaseApplication extends Application {
private ThreadPoolExecutor threadPoolExecutor;
private int cpu = 0;
private Picasso picasso;
private OkHttpClient okHttpClient;
public static ProgressListener progress;
@Override
public void onCreate() {
super.onCreate();
initPicasso();
}
private void initPicasso() {
okHttpClient = new OkHttpClient.Builder().addNetworkInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
return response.newBuilder().body(new ProgressResponseBody(progress, response.body())).build();
}
}).cache(new Cache(getExternalCacheDir(), 10 * 1024 * 1024)).build();
cpu = Runtime.getRuntime().availableProcessors();
threadPoolExecutor = new ThreadPoolExecutor(cpu + 1, cpu * 2 + 1, 1, TimeUnit.MINUTES, new PriorityBlockingQueue<Runnable>());
picasso = new Picasso.Builder(this).executor(threadPoolExecutor).downloader(new OkHttp3Downloader(okHttpClient)).build();//默认3个线程
Picasso.setSingletonInstance(picasso);
}
public static void setProgressListener(ProgressListener progressListener) {
progress = progressListener;
}
}
build.gradle引用:
compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.squareup.okhttp3:okhttp:3.6.0'
package com.tandong.picassodemo.utils;
import android.content.Context;
import android.net.Uri;
import android.os.StatFs;
import com.squareup.picasso.Downloader;
import com.squareup.picasso.NetworkPolicy;
import java.io.File;
import java.io.IOException;
import okhttp3.Cache;
import okhttp3.CacheControl;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.ResponseBody;
/**
* Created by office on 2017/4/10.
*/
public final class OkHttp3Downloader implements Downloader {
private static final String PICASSO_CACHE = "picasso-cache";
private static final int MIN_DISK_CACHE_SIZE = 5 * 1024 * 1024; // 5MB
private static final int MAX_DISK_CACHE_SIZE = 50 * 1024 * 1024; // 50MB
private static File defaultCacheDir(Context context) {
File cache = new File(context.getApplicationContext().getCacheDir(), PICASSO_CACHE);
if (!cache.exists()) {
//noinspection ResultOfMethodCallIgnored
cache.mkdirs();
}
return cache;
}
private static long calculateDiskCacheSize(File dir) {
long size = MIN_DISK_CACHE_SIZE;
try {
StatFs statFs = new StatFs(dir.getAbsolutePath());
long available = ((long) statFs.getBlockCount()) * statFs.getBlockSize();
// Target 2% of the total space.
size = available / 50;
} catch (IllegalArgumentException ignored) {
}
// Bound inside min/max size for disk cache.
return Math.max(Math.min(size, MAX_DISK_CACHE_SIZE), MIN_DISK_CACHE_SIZE);
}
/**
* Creates a {@link Cache} that would have otherwise been created by calling
* {@link #OkHttp3Downloader(Context)}. This allows you to build your own {@link OkHttpClient}
* while still getting the default disk cache.
*/
public static Cache createDefaultCache(Context context) {
File dir = defaultCacheDir(context);
return new Cache(dir, calculateDiskCacheSize(dir));
}
private static OkHttpClient createOkHttpClient(File cacheDir, long maxSize) {
return new OkHttpClient.Builder()
.cache(new Cache(cacheDir, maxSize))
.build();
}
private final Call.Factory client;
private final Cache cache;
/**
* Create new downloader that uses OkHttp. This will install an image cache into your application
* cache directory.
*/
public OkHttp3Downloader(Context context) {
this(defaultCacheDir(context));
}
/**
* Create new downloader that uses OkHttp. This will install an image cache into the specified
* directory.
*
* @param cacheDir The directory in which the cache should be stored
*/
public OkHttp3Downloader(File cacheDir) {
this(cacheDir, calculateDiskCacheSize(cacheDir));
}
/**
* Create new downloader that uses OkHttp. This will install an image cache into your application
* cache directory.
*
* @param maxSize The size limit for the cache.
*/
public OkHttp3Downloader(final Context context, final long maxSize) {
this(defaultCacheDir(context), maxSize);
}
/**
* Create new downloader that uses OkHttp. This will install an image cache into the specified
* directory.
*
* @param cacheDir The directory in which the cache should be stored
* @param maxSize The size limit for the cache.
*/
public OkHttp3Downloader(File cacheDir, long maxSize) {
this(createOkHttpClient(cacheDir, maxSize));
}
public OkHttp3Downloader(OkHttpClient client) {
this.client = client;
this.cache = client.cache();
}
public OkHttp3Downloader(Call.Factory client) {
this.client = client;
this.cache = null;
}
@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());
}
@Override
public void shutdown() {
if (cache != null) {
try {
cache.close();
} catch (IOException ignored) {
}
}
}
}
官方地址:
https://github.com/JakeWharton/picasso2-okhttp3-downloader/blob/master/src/main/java/com/jakewharton/picasso/OkHttp3Downloader.java
自定义ResponseBody,实现对数据下载进度的监听处理:
package com.tandong.picassodemo;
import android.util.Log;
import com.tandong.picassodemo.utils.ProgressListener;
import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
import okio.Source;
/**
* Created by Administrator on 2017/4/14.
*/
public class ProgressResponseBody extends ResponseBody {
private ResponseBody responseBody;
private BufferedSource bufferedSource;
private ProgressListener progressListener;
public ProgressResponseBody(ProgressListener progressListener,ResponseBody responseBody) {
this.responseBody = responseBody;
this.progressListener=progressListener;
}
@Override
public MediaType contentType() {
return responseBody.contentType();
}
@Override
public long contentLength() {
return responseBody.contentLength();
}
@Override
public BufferedSource source() {
if (bufferedSource == null) {
bufferedSource = Okio.buffer(source(responseBody.source()));
}
return bufferedSource;
}
private Source source(Source source) {
ForwardingSource forwardingSource = new ForwardingSource(source) {
long totalBytesRead = 0L;
@Override
public long read(Buffer sink, long byteCount) throws IOException {
long bytesRead = super.read(sink, byteCount);
totalBytesRead += bytesRead != -1 ? bytesRead : 0;
progressListener.update((int) ((100 * totalBytesRead) / responseBody.contentLength()));
Log.i("info", "进度:" + (int) ((100 * totalBytesRead) / responseBody.contentLength()));
return bytesRead;
}
@Override
public void close() throws IOException {
super.close();
}
};
return forwardingSource;
}
}
进度接口:
package com.tandong.picassodemo.utils;
import android.support.annotation.IntRange;
/**
* Created by Administrator on 2017/4/14.
*/
public interface ProgressListener {
public void update(@IntRange(from = 0, to = 100) int progress);
}
自定义带进度的ImageView:
package com.tandong.picassodemo.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.IntRange;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.widget.ImageView;
/**
* Created by office on 2017/4/10.
*/
public class PicassoImageView extends ImageView {
private final int MAX_PROGRESS = 100;
private Paint mArcPaint;
private RectF mBound;
private Paint mCirclePaint;
private int mProgress = 0;
public PicassoImageView(Context context) {
this(context, null, 0);
}
public PicassoImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PicassoImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mArcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mArcPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mArcPaint.setStrokeWidth(dpToPixel(0.1f, getContext()));
mArcPaint.setColor(Color.argb(120, 0xff, 0xff, 0xff));
mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCirclePaint.setStyle(Paint.Style.STROKE);
mCirclePaint.setStrokeWidth(dpToPixel(2, getContext()));
mCirclePaint.setColor(Color.argb(120, 0xff, 0xff, 0xff));
mBound = new RectF();
}
public void setProgress(@IntRange(from = 0, to = MAX_PROGRESS) int mProgress) {
this.mProgress = mProgress;
ViewCompat.postInvalidateOnAnimation(this);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
int min = Math.min(w, h);
int max = w + h - min;
int r = min / 5;
//set up a square in the imageView
//设置了在图片中间的一个小圆
mBound.set(max / 2 - r, min / 2 - r, max / 2 + r, min / 2 + r);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画进度条, Paint是画笔,详见源码
if (mProgress != MAX_PROGRESS && mProgress != 0) {
float mAngle = mProgress * 360f / MAX_PROGRESS;
//画扇型
canvas.drawArc(mBound, 270, mAngle, true, mArcPaint);
//画圆
canvas.drawCircle(mBound.centerX(), mBound.centerY(), mBound.height() / 2, mCirclePaint);
}
}
private static float scale;
public static int dpToPixel(float dp, Context context) {
if (scale == 0) {
scale = context.getResources().getDisplayMetrics().density;
}
return (int) (dp * scale);
}
}
Picasso源码原理解析:
先看流程图:
Picasso的源码很小,类也很少,相对简单些。
我们主要关注以下标红的几个类。
不是太想写太多的文字,那就看源码分析吧。
先看构造单例模式的Piasso实例:
/**
* with方式
*/
Picasso.with(this).load("http://square.github.io/picasso/static/sample.png").centerCrop().into(imageView);
/**
* new with方式
*/
Picasso picasso = Picasso.with(this);
/**
* builder方式
*/
picasso = new Picasso.Builder(this).executor(threadPoolExecutor).downloader(new OkHttp3Downloader(okHttpClient)).build();//默认3个线程
Picasso.setSingletonInstance(picasso);
这几种方式通过查看,都是通过一个方式实现的构造,那就是builder方式。
我们按照这句最基本的方式进行源码分析。
Picasso.with(this).load("http://square.github.io/picasso/static/sample.png").centerCrop().into(imageView);
点击with方法进去。
public static Picasso with(Context context) {
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
singleton = new Builder(context).build();
}
}
}
return singleton;
}
/** Create the {@link Picasso} instance. */
public Picasso build() {
Context context = this.context;
//是否自定义下载器,无的话使用默认下载器
if (downloader == null) {
downloader = Utils.createDefaultDownloader(context);
}
//是否自定义缓存方式,无的话使用默认LruCache
if (cache == null) {
cache = new LruCache(context);
}
//是否自定义线程池,无的话使用默认线程池
if (service == null) {
service = new PicassoExecutorService();
}
//是否自定义转换器,无的话使用默认转换器
if (transformer == null) {
transformer = RequestTransformer.IDENTITY;
}
//统计器,统计缓存的命中率
Stats stats = new Stats(cache);
//创建Dispather,分发调度器
Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
//创建Picasso实例
return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}
}
可以看出都是通过builder方式实例化的Picasso类。这里实例化了Dispatcher和Picasso。
接下来点击load方法进去:
/**
* Start an image request using the specified path. This is a convenience method for calling
* {@link #load(Uri)}.
* <p>
* This path may be a remote URL, file resource (prefixed with {@code file:}), content resource
* (prefixed with {@code content:}), or android resource (prefixed with {@code
* android.resource:}.
* <p>
* Passing {@code null} as a {@code path} will not trigger any request but will set a
* placeholder, if one is specified.
*
* @see #load(Uri)
* @see #load(File)
* @see #load(int)
* @throws IllegalArgumentException if {@code path} is empty or blank string.
*/
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));
}
/**
* Start an image request using the specified URI.
* <p>
* Passing {@code null} as a {@code uri} will not trigger any request but will set a placeholder,
* if one is specified.
*
* @see #load(File)
* @see #load(String)
* @see #load(int)
*/
public RequestCreator load(Uri uri) {
return new RequestCreator(this, uri, 0);
}
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);
}
可以看出,这里实例化了一个RequestCreator,我们看下这个源码类干嘛的。
/** Fluent API for building an image download request. */
@SuppressWarnings("UnusedDeclaration") // Public API.
public class RequestCreator {
private static final AtomicInteger nextId = new AtomicInteger();
private final Picasso picasso;
private final Request.Builder data;
private boolean noFade;
private boolean deferred;
private boolean setPlaceholder = true;
private int placeholderResId;
private int errorResId;
private int memoryPolicy;
private int networkPolicy;
private Drawable placeholderDrawable;
private Drawable errorDrawable;
private Object tag;
看下基本的声明,就是进行我们个性化配置存储的类。
我们接下来点击centerCrop方法进去:
/**
* Crops an image inside of the bounds specified by {@link #resize(int, int)} rather than
* distorting the aspect ratio. This cropping technique scales the image so that it fills the
* requested bounds and then crops the extra.
*/
public RequestCreator centerCrop() {
data.centerCrop();
return this;
}
这个配置也是操作我们的RequestCreator。
最后点击into方法。
/**
* Asynchronously fulfills the request into the specified {@link ImageView}.
* <p>
* <em>Note:</em> This method keeps a weak reference to the {@link ImageView} instance and will
* automatically support object recycling.
*/
public void into(ImageView target) {
into(target, null);
}
/**
* Asynchronously fulfills the request into the specified {@link ImageView} and invokes the
* target {@link Callback} if it's not {@code null}.
* <p>
* <em>Note:</em> The {@link Callback} param is a strong reference and will prevent your
* {@link android.app.Activity} or {@link android.app.Fragment} from being garbage collected. If
* you use this method, it is <b>strongly</b> recommended you invoke an adjacent
* {@link Picasso#cancelRequest(android.widget.ImageView)} call to prevent temporary leaking.
*/
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);//时间戳为Key,创建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());
}
//关键,创建Action,这里用的ImageViewAction
Action action =
new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);
//把请求封装成Action,提交分发
picasso.enqueueAndSubmit(action);
}
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分发
dispatcher.dispatchSubmit(action);
}
void dispatchSubmit(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}
private static class DispatcherHandler extends Handler {
private final Dispatcher dispatcher;
public DispatcherHandler(Looper looper, Dispatcher dispatcher) {
super(looper);
this.dispatcher = dispatcher;
}
@Override public void handleMessage(final Message msg) {
switch (msg.what) {
case REQUEST_SUBMIT: {//提交分发下载请求
Action action = (Action) msg.obj;
dispatcher.performSubmit(action);
break;
}
case REQUEST_CANCEL: {
Action action = (Action) msg.obj;
dispatcher.performCancel(action);
break;
}
case TAG_PAUSE: {
Object tag = msg.obj;
dispatcher.performPauseTag(tag);
break;
}
case TAG_RESUME: {
Object tag = msg.obj;
dispatcher.performResumeTag(tag);
break;
}
case HUNTER_COMPLETE: {
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performComplete(hunter);
break;
}
case HUNTER_RETRY: {
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performRetry(hunter);
break;
}
case HUNTER_DECODE_FAILED: {
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performError(hunter, false);
break;
}
case HUNTER_DELAY_NEXT_BATCH: {
dispatcher.performBatchComplete();
break;
}
case NETWORK_STATE_CHANGE: {
NetworkInfo info = (NetworkInfo) msg.obj;
dispatcher.performNetworkStateChange(info);
break;
}
case AIRPLANE_MODE_CHANGE: {
dispatcher.performAirplaneModeChange(msg.arg1 == AIRPLANE_MODE_ON);
break;
}
default:
Picasso.HANDLER.post(new Runnable() {
@Override public void run() {
throw new AssertionError("Unknown handler message received: " + msg.what);
}
});
}
}
}
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,核心,它是个线程,执行图片下载编解码
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;
}
hunter = forRequest(action.getPicasso(), this, cache, stats, action);
hunter.future = service.submit(hunter);//这个service是ExecutorService线程池,提交执行下载线程
hunterMap.put(action.getKey(), hunter);
if (dismissFailed) {
failedActions.remove(action.getTarget());
}
if (action.getPicasso().loggingEnabled) {
log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
}
}
大概看下BitmapHunter源码:
/*
* Copyright (C) 2013 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.picasso;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.net.NetworkInfo;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import static com.squareup.picasso.MemoryPolicy.shouldReadFromMemoryCache;
import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY;
import static com.squareup.picasso.Picasso.Priority;
import static com.squareup.picasso.Picasso.Priority.LOW;
import static com.squareup.picasso.Utils.OWNER_HUNTER;
import static com.squareup.picasso.Utils.VERB_DECODED;
import static com.squareup.picasso.Utils.VERB_EXECUTING;
import static com.squareup.picasso.Utils.VERB_JOINED;
import static com.squareup.picasso.Utils.VERB_REMOVED;
import static com.squareup.picasso.Utils.VERB_TRANSFORMED;
import static com.squareup.picasso.Utils.getLogIdsForHunter;
import static com.squareup.picasso.Utils.log;
class BitmapHunter implements Runnable {
/**
* Global lock for bitmap decoding to ensure that we are only are decoding one at a time. Since
* this will only ever happen in background threads we help avoid excessive memory thrashing as
* well as potential OOMs. Shamelessly stolen from Volley.
*/
private static final Object DECODE_LOCK = new Object();
private static final ThreadLocal<StringBuilder> NAME_BUILDER = new ThreadLocal<StringBuilder>() {
@Override protected StringBuilder initialValue() {
return new StringBuilder(Utils.THREAD_PREFIX);
}
};
private static final AtomicInteger SEQUENCE_GENERATOR = new AtomicInteger();
private static final RequestHandler ERRORING_HANDLER = new RequestHandler() {
@Override public boolean canHandleRequest(Request data) {
return true;
}
@Override public Result load(Request request, int networkPolicy) throws IOException {
throw new IllegalStateException("Unrecognized type of request: " + request);
}
};
final int sequence;
final Picasso picasso;
final Dispatcher dispatcher;
final Cache cache;
final Stats stats;
final String key;
final Request data;
final int memoryPolicy;
int networkPolicy;
final RequestHandler requestHandler;
Action action;
List<Action> actions;
Bitmap result;
Future<?> future;
Picasso.LoadedFrom loadedFrom;
Exception exception;
int exifRotation; // Determined during decoding of original resource.
int retryCount;
Priority priority;
BitmapHunter(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action,
RequestHandler requestHandler) {
this.sequence = SEQUENCE_GENERATOR.incrementAndGet();
this.picasso = picasso;
this.dispatcher = dispatcher;
this.cache = cache;
this.stats = stats;
this.action = action;
this.key = action.getKey();
this.data = action.getRequest();
this.priority = action.getPriority();
this.memoryPolicy = action.getMemoryPolicy();
this.networkPolicy = action.getNetworkPolicy();
this.requestHandler = requestHandler;
this.retryCount = requestHandler.getRetryCount();
}
它是实现了Runable接口的线程,再继续看Run方法。
@Override public void run() {
try {
updateThreadName(data);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));
}
//这个result是Bitmap,调用了hunt方法获取
result = hunt();
if (result == null) {//分发图片加载失败
dispatcher.dispatchFailed(this);
} else {//分发图片加载完成
dispatcher.dispatchComplete(this);
}
} catch (Downloader.ResponseException e) {
if (!e.localCacheOnly || e.responseCode != 504) {
exception = e;
}
dispatcher.dispatchFailed(this);
} catch (NetworkRequestHandler.ContentLengthException e) {
exception = e;
dispatcher.dispatchRetry(this);
} catch (IOException e) {
exception = 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) {
exception = e;
dispatcher.dispatchFailed(this);
} finally {
Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);
}
}
再继续看hunt方法:
Bitmap hunt() throws IOException {
Bitmap bitmap = null;
//内存缓存检查,检查了很多次内存缓存了
if (shouldReadFromMemoryCache(memoryPolicy)) {
bitmap = cache.get(key);
if (bitmap != null) {
stats.dispatchCacheHit();
loadedFrom = MEMORY;
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache");
}
return bitmap;
}
}
data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
RequestHandler.Result result = requestHandler.load(data, networkPolicy);
if (result != null) {
loadedFrom = result.getLoadedFrom();
exifRotation = result.getExifOrientation();
bitmap = result.getBitmap();
// If there was no Bitmap then we need to decode it from the stream.
if (bitmap == null) {
InputStream is = result.getStream();
try {//inputstream解码
bitmap = decodeStream(is, data);
} finally {
Utils.closeQuietly(is);
}
}
}
if (bitmap != null) {
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_DECODED, data.logId());
}
stats.dispatchBitmapDecoded(bitmap);//解码命中率统计
if (data.needsTransformation() || exifRotation != 0) {
synchronized (DECODE_LOCK) {
if (data.needsMatrixTransform() || exifRotation != 0) {
bitmap = transformResult(data, bitmap, exifRotation);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());
}
}
if (data.hasCustomTransformations()) {
bitmap = applyCustomTransformations(data.transformations, bitmap);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), "from custom transformations");
}
}
}
if (bitmap != null) {
stats.dispatchBitmapTransformed(bitmap);
}
}
}
return bitmap;
}
看decodeStream方法里的逻辑:
/**
* Decode a byte stream into a Bitmap. This method will take into account additional information
* about the supplied request in order to do the decoding efficiently (such as through leveraging
* {@code inSampleSize}).
*/
static Bitmap decodeStream(InputStream stream, Request request) throws IOException {
MarkableInputStream markStream = new MarkableInputStream(stream);
stream = markStream;
long mark = markStream.savePosition(65536); // TODO fix this crap.
final BitmapFactory.Options options = RequestHandler.createBitmapOptions(request);
final boolean calculateSize = RequestHandler.requiresInSampleSize(options);
boolean isWebPFile = Utils.isWebPFile(stream);
markStream.reset(mark);
// When decode WebP network stream, BitmapFactory throw JNI Exception and make app crash.
// Decode byte array instead
if (isWebPFile) {
byte[] bytes = Utils.toByteArray(stream);
if (calculateSize) {
BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options,
request);
}
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
} else {
if (calculateSize) {
BitmapFactory.decodeStream(stream, null, options);
RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options,
request);
markStream.reset(mark);
}
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);
if (bitmap == null) {
// Treat null as an IO exception, we will eventually retry.
throw new IOException("Failed to decode stream.");
}
return bitmap;
}
}
就是对inputSteam转为Bimap操作。
接下来,看dispatcher派发加载成功消息的逻辑:
void dispatchComplete(BitmapHunter hunter) {
handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
}
case HUNTER_COMPLETE: {//加载完成
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performComplete(hunter);
break;
}
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");
}
}
private void batch(BitmapHunter hunter) {
if (hunter.isCancelled()) {
return;
}
batch.add(hunter);
if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
}
}
case HUNTER_DELAY_NEXT_BATCH: {//继续派发next_batch消息
dispatcher.performBatchComplete();
break;
}
void performBatchComplete() {
List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);
batch.clear();
mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));//注意这里的mainThreadHandler是Picasso的Handler
logBatch(copy);
}
注意这里的mainThreadHander是Picasso里的Handler,我们可以看下Dispatcher的实例化。
Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler,
Downloader downloader, Cache cache, Stats stats) {
this.dispatcherThread = new DispatcherThread();
this.dispatcherThread.start();
Utils.flushStackLocalLeaks(dispatcherThread.getLooper());
this.context = context;
this.service = service;
this.hunterMap = new LinkedHashMap<String, BitmapHunter>();
this.failedActions = new WeakHashMap<Object, Action>();
this.pausedActions = new WeakHashMap<Object, Action>();
this.pausedTags = new HashSet<Object>();
this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
this.downloader = downloader;
this.mainThreadHandler = mainThreadHandler;//这个就是Picasso传进来的mainThreadHandler
this.cache = cache;
this.stats = stats;
this.batch = new ArrayList<BitmapHunter>(4);
this.airplaneMode = Utils.isAirplaneModeOn(this.context);
this.scansNetworkChanges = hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE);
this.receiver = new NetworkBroadcastReceiver(this);
receiver.register();
}
那我们看下Picasso里的HUNTER_BATCH_COMPLETE消息处理:
static final String TAG = "Picasso";
static final Handler HANDLER = new Handler(Looper.getMainLooper()) {
@Override public void handleMessage(Message msg) {
switch (msg.what) {
case HUNTER_BATCH_COMPLETE: {
@SuppressWarnings("unchecked") List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;
//noinspection ForLoopReplaceableByForEach
for (int i = 0, n = batch.size(); i < n; i++) {
BitmapHunter hunter = batch.get(i);
hunter.picasso.complete(hunter);//回调了complete方法
}
break;
}
case REQUEST_GCED: {
Action action = (Action) msg.obj;
if (action.getPicasso().loggingEnabled) {
log(OWNER_MAIN, VERB_CANCELED, action.request.logId(), "target got garbage collected");
}
action.picasso.cancelExistingRequest(action.getTarget());
break;
}
case REQUEST_BATCH_RESUME:
@SuppressWarnings("unchecked") List<Action> batch = (List<Action>) msg.obj;
//noinspection ForLoopReplaceableByForEach
for (int i = 0, n = batch.size(); i < n; i++) {
Action action = batch.get(i);
action.picasso.resumeAction(action);
}
break;
default:
throw new AssertionError("Unknown handler message received: " + msg.what);
}
}
};
我们看下Picasso的complete方法:
void complete(BitmapHunter hunter) {
Action single = hunter.getAction();//拿到了派发过来的Action
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();//拿到了Bitmap
LoadedFrom from = hunter.getLoadedFrom();
if (single != null) {
deliverAction(result, from, single);//执行deliverAction方法
}
if (hasMultiple) {
//noinspection ForLoopReplaceableByForEach
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);
}
}
我们看下deliverAction方法:
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);//回调了action的complete方法
if (loggingEnabled) {
log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), "from " + from);
}
} else {
action.error();
if (loggingEnabled) {
log(OWNER_MAIN, VERB_ERRORED, action.request.logId());
}
}
}
这里注意下,我们之前用的是Action里的ImageViewAction类,我们看下ImageViewAction源码和它的complete类里的处理逻辑:
/*
* Copyright (C) 2013 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.picasso;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.widget.ImageView;
class ImageViewAction extends Action<ImageView> {
Callback callback;
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;
}
//图片加载完成回调
@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);//设置bitmap给PicassoDrawable
if (callback != null) {
callback.onSuccess();
}
}
@Override public void error() {
ImageView target = this.target.get();
if (target == null) {
return;
}
if (errorResId != 0) {
target.setImageResource(errorResId);
} else if (errorDrawable != null) {
target.setImageDrawable(errorDrawable);
}
if (callback != null) {
callback.onError();
}
}
@Override void cancel() {
super.cancel();
if (callback != null) {
callback = null;
}
}
}
我们简单看下PicassoDrawable源码和它的setBitmap方法:
/*
* Copyright (C) 2013 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.picasso;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.SystemClock;
import android.widget.ImageView;
import static android.graphics.Color.WHITE;
import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY;
final class PicassoDrawable extends BitmapDrawable {//继承自BitmapDrawable
// Only accessed from main thread.
private static final Paint DEBUG_PAINT = new Paint();
private static final float FADE_DURATION = 200f; //ms
/**
* Create or update the drawable on the target {@link ImageView} to display the supplied bitmap
* image.
*/
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);//调用ImageView的setImageDrawable方法将转换后的drawable加载进去显示
}
/**
* Create or update the drawable on the target {@link ImageView} to display the supplied
* placeholder image.
*/
static void setPlaceholder(ImageView target, Drawable placeholderDrawable) {
target.setImageDrawable(placeholderDrawable);
if (target.getDrawable() instanceof AnimationDrawable) {
((AnimationDrawable) target.getDrawable()).start();
}
}
private final boolean debugging;
private final float density;
private final Picasso.LoadedFrom loadedFrom;
Drawable placeholder;
long startTimeMillis;
boolean animating;
int alpha = 0xFF;
PicassoDrawable(Context context, Bitmap bitmap, Drawable placeholder,
Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {
super(context.getResources(), bitmap);
this.debugging = debugging;
this.density = context.getResources().getDisplayMetrics().density;
this.loadedFrom = loadedFrom;
boolean fade = loadedFrom != MEMORY && !noFade;
if (fade) {
this.placeholder = placeholder;
animating = true;
startTimeMillis = SystemClock.uptimeMillis();
}
}
@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);
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
invalidateSelf();
}
}
}
if (debugging) {
drawDebugIndicator(canvas);
}
}
@Override public void setAlpha(int alpha) {
this.alpha = alpha;
if (placeholder != null) {
placeholder.setAlpha(alpha);
}
super.setAlpha(alpha);
}
@Override public void setColorFilter(ColorFilter cf) {
if (placeholder != null) {
placeholder.setColorFilter(cf);
}
super.setColorFilter(cf);
}
@Override protected void onBoundsChange(Rect bounds) {
if (placeholder != null) {
placeholder.setBounds(bounds);
}
super.onBoundsChange(bounds);
}
private void drawDebugIndicator(Canvas canvas) {
DEBUG_PAINT.setColor(WHITE);
Path path = getTrianglePath(new Point(0, 0), (int) (16 * density));
canvas.drawPath(path, DEBUG_PAINT);
DEBUG_PAINT.setColor(loadedFrom.debugColor);
path = getTrianglePath(new Point(0, 0), (int) (15 * density));
canvas.drawPath(path, DEBUG_PAINT);
}
private static Path getTrianglePath(Point p1, int width) {
Point p2 = new Point(p1.x + width, p1.y);
Point p3 = new Point(p1.x, p1.y + width);
Path path = new Path();
path.moveTo(p1.x, p1.y);
path.lineTo(p2.x, p2.y);
path.lineTo(p3.x, p3.y);
return path;
}
}
这样,整个Picasso就实现了我们的图片请求加载显示。
如果你觉得本文太长,可以单独查看Picasso源码原理分析一文:
http://blog.csdn.net/jay100500/article/details/71055229