Picasso 是非常好用的图像加载框架,它清晰的图片加载流程:with->load->into,即使用当前上下文加载指定路径的图片资源,然后显示在 ImageView 中。所以,分析 Picasso 的源码,可以从它的流程着手。
用法回顾:Picasso.with(context).load(uri).into(mImageView);
一. with() 创建必备的对象
二. load()
三. into()
首先在 with 方法内部使用 Builder 创建了单例的 Picasso 对象,而且这种单例是加上 volatile 关键字+双重检查加锁+ 懒汉式,懒汉式特点是:线程安全、高效、延迟加载(节约内存)。使用 volatile 关键字可以禁止 CPU 重排序优化、单例的修改对所有线程可见。此种写法,在 Android 系统不建议使用枚举的前提下,不失为一种较优解。
static volatile Picasso singleton = null;
public static Picasso with(Context context) {
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
singleton = new Builder(context).build();
}
}
}
return singleton;
}
接着,在 build 中初始化各种对象
/** Create the {@link Picasso} instance. */
public Picasso build() {
Context context = this.context;
// 创建默认下载器
if (downloader == null) {
downloader = Utils.createDefaultDownloader(context);
}
// 默认的内存缓存算法(通过Lru 最近最少原则,进行内存的管理)
if (cache == null) {
cache = new LruCache(context);
}
// 创建Picasso自有的线程池对象(通过多线程加载网络图片)
if (service == null) {
service = new PicassoExecutorService();
}
// 初始化自定义的Transmform对象(用户对图片的自定义转换)
if (transformer == null) {
transformer = RequestTransformer.IDENTITY;
}
// 创建了一个统计类
Stats stats = new Stats(cache);
// 初始化调度者(需要传递:上下文、线程池、handler,下载器,缓存目录和统计类)
Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
// 最终利用 builder 对象的各种属性、对象,创建Picasso对象(需要传递:上下文、调度者、缓存目录、监听器、handler等,默认图片参数、是否开启调试标志、是否开启日志标志)
return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}
}
Picasso 是如何初始化默认的 Downloader 的?
答案是:在 createDefaultDownloader 中,首先通过反射查找是否存在okHttpClient 对象,如果存在则使用 okHttpDownloader,否则使用 UrlConnectionDownloader
static Downloader createDefaultDownloader(Context context) {
try {
Class.forName("com.squareup.okhttp.OkHttpClient");
return OkHttpLoaderCreator.create(context);
} catch (ClassNotFoundException ignored) {
}
return new UrlConnectionDownloader(context);
}
在 Picasso 类中,还能清晰的看到定义调试模式的颜色
(当设置 Picasso 属性 indicatorsEnabled(true),加载后的 Bitmap 左上角会根据不同颜色指示图片来源):
/** Describes where the image was loaded from. */
public enum LoadedFrom {
MEMORY(Color.GREEN),
DISK(Color.BLUE),
NETWORK(Color.RED);
final int debugColor;
private LoadedFrom(int debugColor) {
this.debugColor = debugColor;
}
}
load(Uri) 返回的 RequestCreator 是一个请求创建者,可以对其设置各种属性。Picasso.load() 方法可以加载:File、resId、String 以及其他Uri类型的地址,除了 resId 单独作为参数交给 Request.Builder,其他的都被转换成了 Uri。
RequestCreateor构造方法主要的作用:
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 提供的各种设置方法:
public RequestCreator noPlaceholder() 不需要加载前显示的图片
public RequestCreator placeholder(int placeholderResId) 设置加载前显示的图片
public RequestCreator error(int errorResId) 加载出错时显示的图片
public RequestCreator tag(Object tag) 设置全局tag,以便对加载过程进行暂停、取消、继续的操作
public RequestCreator fit() 设置图片的显示模式为:填充ImageView控件宽高
public RequestCreator resize(int targetWidth, int targetHeight) 重新调整图片的大小
public RequestCreator centerCrop() 和 resize 方法配合使用,以resize前的尺寸作为参考,按比例修改图片尺寸
public RequestCreator centerInside() 和 resize 方法配合使用,以resize后的尺寸作为参考,将图片强制缩放到resize
如果说 with、load 是 Picasso 图像加载的前戏, into 就是高潮:下载(hunt)、解析(decodeStream)。这个过程分散在以下几个区域。让我们一一解析。
一、RequestCreater类中
1 . checkMain 检查当前线程是否是主线程
为啥要检查是否在主线程,因为通过分析可以发现,BitmapHunter 类下载完图片、解析完成后,会通过 handler message 机制向主线程发送消息,所以必须检查是否在主线程
2 . 设置图片的占位符、调整图片大小、图片裁切等
就是之前介绍的 RequestCreator 中的系列设置方法
3 . 创建 Request 对象
4 . 从内存中获取图片,获取到则直接返回
5 . 创建 ImageViewAction 对象
Picasso.enqueueActionSubmit(action) 提交请求
将上面的action对象提交到Picasso中了
二、Picasso 类中
1 . Picasso中submit(action);
2 . dispatcher.dispatcherSubmit(action);
上面说到,RequestCreator 对象中通过Picasso.enqueueActionSubmit(action) 将 action 提交到 Picasso 中,而 Picasso 中通过 dispatcher.dispatcherSubmit(action) 又将对象放入调度者中
三、Dispatcher类中
1 . handler.sendMessage()
2 . handler 类回调 handleMessage
3 . 创建 bitmaoHunter
上面说到,Picasso 中通过 dispatch.dispatchSubmit(action) 方法,将 action 提交到 dispatcher 调度者中,而调度者通过一个 handler message,将 action又重新发到 Picasso 中,最后在 Picasso 的 handler 的 handlerMessage 中创建了 BitmapHunter 对象。
四、BitmapHunter
1 . hunt 获取图片
2 . decodeStream 对图片进行解析
上面说到,Picasso 的handler 的 handleMessgae 中创建了BitmapHunter,而 BitmapHunter 将具体通过线程池下载和解析图片(hunt and decodeStream)
另外,BitmapHunter 实现了Runnable接口(就是一个会被添加到线程池ExecutorService 的一个任务),所有的加载、解析图片的过程都发生在子线程
hunt 的全过程:
Bitmap hunt() throws IOException {
Bitmap bitmap = null;
// 首先从内存缓存中加载图片(根据在RequestCreator中设置的memoryPolicy)
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;
}
}
// 网络加载图片(根据在RequestCreator中设定的NetworkPolicy)
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) {
// 在这里根据用户自定义的图形转换规则,对Bitmap做进一步处理
stats.dispatchBitmapTransformed(bitmap);
}
}
}
// 加载、解析后返回被处理的btimap。至此,整个Picasso的异步加载过程完成
return bitmap;
}
至此,整个过程算是完成了
参考:
1 . Android图片加载库Picasso源码分析
2 . picasso-强大的Android图片下载缓存库