Picasso图片加载源码浅析
前言
图片加载在Android开发中是非常重要,好的图片加载库也比比皆是。ImageLoader、Picasso、Glide、Fresco均是优秀的图片加载库。
以上提到的几种图片加载库各有特色。用法与比较,网上已经很多了。
出于学习的角度,个人认为从Picasso入手较好。代码量小,同时API优美,很适合我们学习。
使用
Picasso基本使用:
Picasso.with(this).load("https://...").into(imageView);
Picasso图片加载基本步骤和Glide差不多,都分为with、load、into三步,但相对于Glide图片加载框架它的源码逻辑相对简单一点。
with
with方法就用一个双重锁机制构建一个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);
}
有上述代码片段可知,build方法首先创建了一个downloader,目测为图片下载器,那么来瞅一下:
static Downloader createDefaultDownloader(Context context) {
try {
Class.forName("com.squareup.okhttp.OkHttpClient");
return OkHttpLoaderCreator.create(context);
} catch (ClassNotFoundException ignored) {
}
return new UrlConnectionDownloader(context);
}
显然,在有okhttp库的支持下,默认使用okhttp下载图片,没有支持则使用HttpURLConnection加载图片;
然后创建了Picasso图片缓存器LruCache和线程池执行器等等;最后创建了一个dispatcher实例,这个类主要用于分发消息和开启下载图片任务;最后创建了一个Picasso对象实例;至此with主要创建一些变量初始化工作。
load
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);
}
private Builder(Request request) {
uri = request.uri;
resourceId = request.resourceId;
stableKey = request.stableKey;
targetWidth = request.targetWidth;
targetHeight = request.targetHeight;
centerCrop = request.centerCrop;
centerInside = request.centerInside;
rotationDegrees = request.rotationDegrees;
rotationPivotX = request.rotationPivotX;
rotationPivotY = request.rotationPivotY;
hasRotationPivot = request.hasRotationPivot;
onlyScaleDown = request.onlyScaleDown;
if (request.transformations != null) {
transformations = new ArrayList<Transformation>(request.transformations);
}
config = request.config;
priority = request.priority;
}
由上述代码可见,load方法主要创建图片加载请求并设置一些成员属性;
into
上述with和load方法初始化一些变量并封装图片请求,做了一些准备阶段的工作,那么into方法就进行加载图片并显示的任务了。
public void into(ImageView target, Callback callback) {
long started = System.nanoTime();
checkMain();//是否为主线程
if (target == null) {
throw new IllegalArgumentException("Target must not be null.");
}
//url是否合法
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
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 action =
new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);
//加入请求队列并提交请求
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.dispatchSubmit(action);
}
最后将封装的ImageViewAction提交到dispatcher,并执行dispatchSubmit方法;
void dispatchSubmit(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}
dispatcherSubmit发放最后通过子线程handler处理,交给performSubmit函数进行执行:
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(BitmapHunter继承自Runnable)处理,并发送到线程池执行:
@Override public void run() {
try {
updateThreadName(data);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));
}
//加载网络数据
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方法当中,继续跟踪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 {
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;
}
hunt方法做了两件事,判断内存缓存中是否具有此图片;之后通过requestHandler.load方法加载图片的数据流并返回;但最终都是调用downloader中的load方法获取图片的数据流(使用okHttp或httpUrlConnnection加载)。
最后聊聊图片加载完成后的响应:
void dispatchComplete(BitmapHunter hunter) {
handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
}
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);
}
}
void performBatchComplete() {
List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);
batch.clear();
mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
logBatch(copy);
}
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);
}
break;
}
最后辗转反侧,调用主线程Handler发送消息回调图片加载完成,并显示图片。
至此Picasso图片加载框架加载流程梳理完毕,对比Glide,它是一个轻型框架,中规中矩,简洁明了。