Picasso,Square公司开源的一款图片加载库,实现了图片的下载,缓存及显示功能,本身的Api调用非常的简单,一行代码实现异步加载:
Picasso.with(context).load("url").into(imageView);
Picasso的一些优点:
在Adapter里自带ImageView的显示加载与取消
支持图片转换,降低内存的占用
默认支持二级缓存(内存、硬盘缓存)
下面我们来看Picasso的实现,首先是Picasso.with(context)的初始化代码片段,静态内部类Builder。
public static Picasso with(Context context) {
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
//建造者模式,构建单一Picasso实例
singleton = new Builder(context).build();
}
}
}
return singleton;
}
下面我们看一下,在Builder里做的一些初始化操作
public Picasso build() {
Context context = this.context;
if (downloader == null) {
//创建下载的DownLoader OkhttpClient或者HttpUrlConnection
downloader = Utils.createDefaultDownloader(context);
}
if (cache == null) {
//实现缓存的类
cache = new LruCache(context);
}
if (service == null) {
//线程池,默认3条线程
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);
}
同时还有一些其他的可自己选择的构建参数,是否显示Log管理,自定义下载Downloader,加载失败的监听Listener,自定义线程池管理,request请求转换器,自定义请求下载处理RequestHandler等。
/**
* Specify the default {@link Bitmap.Config} used when decoding images. This can be overridden
* on a per-request basis using {@link RequestCreator#config(Bitmap.Config) config(..)}.
*/
public Builder defaultBitmapConfig(Bitmap.Config bitmapConfig) {
if (bitmapConfig == null) {
throw new IllegalArgumentException("Bitmap config must not be null.");
}
this.defaultBitmapConfig = bitmapConfig;
return this;
}
/** Specify the {@link Downloader} that will be used for downloading images. */
public Builder downloader(Downloader downloader) {
if (downloader == null) {
throw new IllegalArgumentException("Downloader must not be null.");
}
if (this.downloader != null) {
throw new IllegalStateException("Downloader already set.");
}
this.downloader = downloader;
return this;
}
/**
* Specify the executor service for loading images in the background.
*
* Note: Calling {@link Picasso#shutdown() shutdown()} will not shutdown supplied executors.
*/
public Builder executor(ExecutorService executorService) {
if (executorService == null) {
throw new IllegalArgumentException("Executor service must not be null.");
}
if (this.service != null) {
throw new IllegalStateException("Executor service already set.");
}
this.service = executorService;
return this;
}
/** Specify the memory cache used for the most recent images. */
public Builder memoryCache(Cache memoryCache) {
if (memoryCache == null) {
throw new IllegalArgumentException("Memory cache must not be null.");
}
if (this.cache != null) {
throw new IllegalStateException("Memory cache already set.");
}
this.cache = memoryCache;
return this;
}
/** Specify a listener for interesting events. */
public Builder listener(Listener listener) {
if (listener == null) {
throw new IllegalArgumentException("Listener must not be null.");
}
if (this.listener != null) {
throw new IllegalStateException("Listener already set.");
}
this.listener = listener;
return this;
}
/**
* Specify a transformer for all incoming requests.
*
* NOTE: This is a beta feature. The API is subject to change in a backwards incompatible
* way at any time.
*/
public Builder requestTransformer(RequestTransformer transformer) {
if (transformer == null) {
throw new IllegalArgumentException("Transformer must not be null.");
}
if (this.transformer != null) {
throw new IllegalStateException("Transformer already set.");
}
this.transformer = transformer;
return this;
}
/** Register a {@link RequestHandler}. */
public Builder addRequestHandler(RequestHandler requestHandler) {
if (requestHandler == null) {
throw new IllegalArgumentException("RequestHandler must not be null.");
}
if (requestHandlers == null) {
requestHandlers = new ArrayList();
}
if (requestHandlers.contains(requestHandler)) {
throw new IllegalStateException("RequestHandler already registered.");
}
requestHandlers.add(requestHandler);
return this;
}
/**
* @deprecated Use {@link #indicatorsEnabled(boolean)} instead.
* Whether debugging is enabled or not.
*/
@Deprecated public Builder debugging(boolean debugging) {
return indicatorsEnabled(debugging);
}
/** Toggle whether to display debug indicators on images. */
public Builder indicatorsEnabled(boolean enabled) {
this.indicatorsEnabled = enabled;
return this;
}
/**
* Toggle whether debug logging is enabled.
*
* WARNING: Enabling this will result in excessive object allocation. This should be only
* be used for debugging purposes. Do NOT pass {@code BuildConfig.DEBUG}.
*/
public Builder loggingEnabled(boolean enabled) {
this.loggingEnabled = enabled;
return this;
}
前面说到,在Builder里面构建了Picasso实例,
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
RequestTransformer requestTransformer, List extraRequestHandlers, Stats stats,
Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
...
...添加了各种资源的request请求,
//弱引用封装请求Action,
this.targetToAction = new WeakHashMap
这样我们的Picasso就基本上初始化完毕。
下面看一下.load(uri)方法得源代码。
public RequestCreator load(Uri uri) {
return new RequestCreator(this, uri, 0);
}
RequestCreator,构造下载请求Request的类,这里可以进行占位图placeHolder(),内存策略memoryPolicy(),网络策略networkPolicy(),渐显noFade()等。
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);
}
在这里,得到了一个Request.Builder,这里我们可以进行各种ImageView的属性设置,如resize(),centerCrop(),rotate(),config(),transform()等操作,即如下代码:
Picasso.with(context)
.load("url")
.placeHolder()
.centerCrop()
.config(new Bitmap.config())
.into(imageView);
经过上述的一些操作,一个包含各参数的请求Request就构建完成,下面就是图片的下载展示.into(imageView)。
public void into(ImageView target, Callback callback) {
long started = System.nanoTime();
//检查是否在主线程
checkMain();
if (target == null) {
throw new IllegalArgumentException("Target must not be null.");
}
//检查是否uri为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 request = createRequest(started);
//构建每个请求的标识Key,uri,size,centerCrop等
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里,完成了RequestWeakReference和ReferenceQueue的关联。
Action action =
new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);
//加入请求队列
picasso.enqueueAndSubmit(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.dispatchSubmit(action);
}
跟踪下来,看到在Dispatcher调度者中performSubmit(),创建了BitmapHunter的封装Action的Runnable下载任务,并加入了LinkedHashMap进行管理,
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任务,BitmapHunter关RequestHandler,Key,Cache等。
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的forRequest()和run()方法,
static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,
Action action) {
Request request = action.getRequest();
List requestHandlers = picasso.getRequestHandlers();
// Index-based loop to avoid allocating an iterator.
//noinspection ForLoopReplaceableByForEach
for (int i = 0, count = requestHandlers.size(); i < count; i++) {
RequestHandler requestHandler = requestHandlers.get(i);
//找到能处理不同请求的RequestHandler ,例如NetRequestHandler 以uri的scheme位判断
if (requestHandler.canHandleRequest(request)) {
return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
}
}
return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
}
@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 {
//成功得到图片,后面转到主线程进行设置bitmap
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);
}
}
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;
}
最终调用了NetworkRequestHandler的load方法,根据项目内是否有OkHttp来使用Okhttp或者UrlConnection进行下载、及磁盘缓存。
static Downloader createDefaultDownloader(Context context) {
try {
Class.forName("com.squareup.okhttp.OkHttpClient");
return OkHttpLoaderCreator.create(context);
} catch (ClassNotFoundException ignored) {
}
return new UrlConnectionDownloader(context);
}
接着上面调用Dispatcher的成功回调方法dispatchComplete(),一路追踪可以看到后面调用了下面这个方法,在这里完成了子线程向主线程的过渡去设置bitmap。
void performBatchComplete() {
List copy = new ArrayList(batch);
batch.clear();
mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
logBatch(copy);
}
然后就在我们一开始构建的ImageViewAction里,使用PicassoDrawable完成图片的渐显变换、设置。
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);
}
PicassoDrawable继承了BitmapDrawable,在onDraw()方法里进行了渐显的变换。
@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);
}
}
这样,我们就基本完成了Picasso源码的一整个逻辑实现。