Picasso源码分析
来源:互联网 发布:vm12虚拟机安装mac os 编辑:程序博客网 时间:2024/05/22 12:31
“A powerful image downloading and caching library for Android”
强大的图像下载和缓存库为Android所用!到底有多强大,我们一探究竟。
一、介绍
它简单粗暴的一行代码就搞定了你想要的从url加载图片:
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);
Android上的图像加载的许多常见的陷阱是由Picasso自动处理:
1)、处理Adapter中的 ImageView 回收和取消已经回收ImageView的下载进程
2)、使用最少的内存完成复杂的图片转换,比如把下载的图片转换为圆角等
3)、自动添加磁盘和内存缓存
二、特性
1、Adapter中的使用
自动检测Adapter中的ImageView重用和取消不必要的下载。
@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);}
2、图片转换
转换图片以适合所显示的ImageView,来减少内存消耗。
Picasso.with(context) .load(url) .resize(50, 50) .centerCrop() .into(imageView)
还可以设置自定义转换来实现高级效果。
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()"; }}
通过实现Transformation 类来重写transform这个方法。
3、占位符图片
Picasso支持下载和加载错误占位符图片。
Picasso.with(context) .load(url) .placeholder(R.drawable.user_placeholder) .error(R.drawable.user_placeholder_error) .into(imageView);
这里提供两个占位符图片,一个下载错误的图片一个加载错误的图片;这里会重试3次。
4、支持本地资源加载
从 Resources, assets, files, content providers 加载图片都支持。
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);
5、DEBUG支持
调用函数 setIndicatorsEnabled(true) 可以在加载的图片左上角显示一个 三角形 ,不同的颜色代表加载的来源
三、源码分析
从Picasso的简单使用我们可以看到,它的过程是with–>load–>into.
我们就从这三个过程来看:
1、with.
首先在 with 方法内部使用 Builder (建造者模式)创建了单例的 Picasso 对象,而且这种单例是加上 volatile 关键字+双重检查加锁+ 懒汉式 ,使用 volatile 关键字可以禁止 CPU 重排序优化、单例的修改对所有线程可见。双重检查加锁可以保证线程安全。懒汉式的方式延迟加载(节约内存)提高了效率。此种写法,在 Android 系统不建议使用枚举的前提下,不失为一种较优解。
static volatile Picasso singleton = null;... ...public static Picasso with(@NonNull Context context) { if (context == null) { throw new IllegalArgumentException("context == null"); } if (singleton == null) { synchronized (Picasso.class) { if (singleton == null) { singleton = new Builder(context).build(); } } } return singleton; }
接着就是它的build过程:
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对象。 return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,defaultBitmapConfig, indicatorsEnabled, loggingEnabled); } }
Picasso 是如何初始化默认的 Downloader 的?
在 createDefaultDownloader 中,首先通过反射查找是否存在okHttpClient 对象,如果存在则使用 okHttpDownloader,否则使用 UrlConnectionDownloader
static Downloader createDefaultDownloader(Context context) { if (SDK_INT >= GINGERBREAD) { try { Class.forName("okhttp3.OkHttpClient"); return OkHttp3DownloaderCreator.create(context); } catch (ClassNotFoundException ignored) { } try { Class.forName("com.squareup.okhttp.OkHttpClient"); return OkHttpDownloaderCreator.create(context); } catch (ClassNotFoundException ignored) { } } return new UrlConnectionDownloader(context); }
基于LinkedHashMap的LruCache就不在这里细说了,自己翻阅资料。
然后就是Picasso自有的线程池对象PicassoExecutorService的adjustThreadCount(NetworkInfo info) 方法,它会根据网络WIFI、4G、3G、2G的不同状态,创建了不同的线程池大小。
RequestTransformer定义成一个接口,可用于实现它进行自定义转换图片。
Stats用于统计监控,支持图片缓存使用的监控,包括缓存命中率、已使用内存大小、节省的流量等。
接着 实例化 Diaptcher 对象,开启异步下载,开启缓存。
最后返回实例化后的 Picasso 对象。
2、load
load(Uri) 返回的 RequestCreator 是一个请求创建者,可以对其设置各种属性。Picasso.load() 方法可以加载:File、resId、String 以及其他Uri类型的地址,除了 resId 单独作为参数交给 Request.Builder,其他的都被转换成了 Uri。
RequestCreateor构造方法主要的作用:
a)、传入了 Picasso 对象
b)、使用 Request.Builder 创建了 Request 对象,后续对ReqestCreateor 的设置都作用在 Reqest 对象中
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); }
3、into
重头戏来了!之前都是伏笔,先一睹它的芳容:
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 || target.isLayoutRequested()) { if (setPlaceholder) { setPlaceholder(target, getPlaceholderDrawable()); } picasso.defer(target, new DeferredRequestCreator(this, target, callback)); return; } data.resize(width, height); } //创建 Request 对象 Request request = createRequest(started); String requestKey = createKey(request); //从内存缓存中获取图片,获取到则直接返回requestKey相应的缓存图片。 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()); } //创建 ImageViewAction 对象 Action action = new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId, errorDrawable, requestKey, tag, callback, noFade); picasso.enqueueAndSubmit(action); }
代码有点儿长~不过流程还算比较清晰:
1 . checkMain 检查当前线程是否是主线程
为啥要检查是否在主线程,因为通过分析可以发现,BitmapHunter 类下载完图片、解析完成后,会通过 handler message 机制向主线程发送消息,所以必须检查是否在主线程
2 . 设置图片的占位符、调整图片大小、图片裁切等–就是之前介绍的 RequestCreator 中的系列设置方法
3 . 创建 Request 对象
4 . 从内存中获取图片,获取到则直接返回
5 . 创建 ImageViewAction 对象,最后通过Picasso.enqueueActionSubmit(action) 提交请求,将上面的action对象提交到Picasso中。
接着看Picasso中的enqueueAndSubmit
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); }
最后又submit到之前初始化的调度器dispatcher了
void submit(Action action) { dispatcher.dispatchSubmit(action); }
我们来看Dispatcher中的dispatchSubmit:
void dispatchSubmit(Action action) { handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action)); }
终于等到你~熟悉Handler机制的都应该知道了;它就是调用handler去传递消息来调度action的,最后是交给了DispatcherHandler的handleMessage处理。具体怎么处理的呢;我们可以看到它在内部创建了BitmapHunter 对象。
BitmapHunter hunter = (BitmapHunter) msg.obj;
4、BitmapHunter
BitmapHunter 实现了Runnable接口(就是一个会被添加到线程池ExecutorService 的一个任务),所有的加载、解析图片的过程都发生在子线程。
我们看到它的run()方法里面result = hunt();
结果都来源于hunt()方法,所以我们重点看一下它的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(); exifOrientation = result.getExifOrientation(); bitmap = result.getBitmap(); //如果得到的不是图片而是InputStream,则转换成Bitmap if (bitmap == null) { InputStream is = result.getStream(); try { bitmap = decodeStream(is, data); } finally { Utils.closeQuietly(is); } } } //打印LOG if (bitmap != null) { if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_DECODED, data.logId()); } //使用stats做标记。 stats.dispatchBitmapDecoded(bitmap); //进行裁剪,旋转等变换 if (data.needsTransformation() || exifOrientation != 0) { synchronized (DECODE_LOCK) { if (data.needsMatrixTransform() || exifOrientation != 0) { bitmap = transformResult(data, bitmap, exifOrientation); 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"); } } } // 在这里根据用户自定义的图形转换规则,对Bitmap做进一步处理 if (bitmap != null) { stats.dispatchBitmapTransformed(bitmap); } } } // 加载、解析后返回被处理的bitmap。 return bitmap; }
最后把返回的结果交给它们处理:
Dispatcher.performComplete()
Picasso.comple()//从Hunter中取出结果,调用Action.complete,or Action.error
至此,整个Picasso的异步加载过程完成。
盗图以总结:
- Picasso源码分析
- Picasso 源码分析
- Picasso 源码流程分析
- Picasso源码分析
- Picasso 源码分析
- Picasso源码分析
- Picasso源码分析
- Picasso源码初步分析
- Picasso源码分析
- Picasso源码原理分析
- Picasso源码分析4
- Picasso开源库源码分析
- Picasso源码原理分析
- Picasso源码分析
- 从Zero分析Picasso源码
- Picasso 基本方法源码分析
- Android之图片加载库Picasso源码分析
- Picasso源码解析
- 20160729 其他小代码
- python3.3网页图片爬虫
- UVa Live (LA) 3644 典型并查集
- 机器学习(周志华) 参考答案 第三章 线性模型
- 无题
- Picasso源码分析
- android 坐标问题
- nyoj 325 zb的生日
- SPDA-CNN:Unifying Semantic Part Detection and Abstraction for Fine-grained Recognition
- 神经网络与深度学习读书笔记第二天
- 2D平移矩阵与3D平移矩阵
- 习题3-7 DNA序列 UVa1368
- POJ 2296 Map Labeler 2-SAT+二分答案
- Java多线程系列之线程状态图