Picasso 是Square 公司开源的Android 端的图片加载和缓存框架。Square 真是一家良心公司啊,为我们Android开发者贡献了很多优秀的开源项目有木有!像什么Rerefoit 、OkHttp、LeakCanary、Picasso等等都是非常火的开源项目。回到正题,除了使用简单方便,Picasso还能自动帮我们做以下事情:
- 处理Adapter 中ImageView的回收和取消下载。
- 使用最小的内存 来做复杂的图片变换。比如高斯模糊,圆角、圆形等处理。
- 自动帮我们缓存图片。内存和磁盘缓存。
以上只是列出了Picasso 比较核心的几点,其实它的优点远远不止这些,接下来就看一下如何使用Picasso。
本文目录
0,添加依赖
1, 加载显示图片
2,Placeholder & noPlaceholder & noFade
3 , 设置图片尺寸(Resize)、缩放(Scale)和裁剪(Crop)
4,图片旋转Rotation()
5 , 转换器Transformation
6,请求优先级
7,Tag管理请求
8,同步/异步加载图片
9,缓存(Disk 和 Memory)
10,Debug 和日志
11,Picasso 扩展
正文
0. 添加依赖
要使用Picasso,首先我们要添加版本依赖,去官网或者Github 看一下当前的最新版本(截止本文最新版本为2.5.2),然后在build.gradle中添加依赖:
1. 加载显示图片
将Picasso添加到项目之后,我们就可以用它来加载图片了,使用方法非常简单:
只需要一行代码就完成了加载图片到显示的整个过程,链式调用,非常简洁,其实有三步,一次调用了三个方法:
- with(Context) 获取一个Picasso单例,参数是一个Context上下文
- load(String) 调用load 方法加载图片
- into (ImageView) 将图片显示在对应的View上,可以是ImageView,也可以是实现了Target j接口的自定义View。
上面演示了加载一张网络图片,它还支持其它形式的图片加载,加载文件图片,加载本地资源图片,加载一个Uri 路径给的图片,提供了几个重载的方法:
load(Uri uri) 加载一个以Uri路径给的图片
load(File file) 加载File中的图片
load(int resourceId) 加载本地资源图片
提醒:上面介绍了load的几个重载方法,加载不同资源的图片,另外提醒注意一下load(String path)接受String 参数的这个方法,参数String 可以是一个网络图片url,也可以是file 路径、content资源 和Android Resource。看一下源码的注释。
要使用string 参数加载上面的几种资源,除了网络url,其它几种需要加上对应前缀,file文件路径前缀:file: , content 添加前缀:content: ,Android Resource 添加:android.resource:
2. placeholder& error & noPlaceholder & noFade
通过上面的第一步我们就可以通过Picasso 加载图片了,我们的项目中通常最常用的就是加载网络图片,但是由于网络环境的差异,有时侯加载网络图片的过程有点慢,这样界面上就会显示空ImageView什么也看不见,用户体验非常不好。其实以前用过ImageLoader的同学都知道,ImageLoader 是可以设置加载中显示默认图片的,Picasso当然也给我们提供了这个功能。
placeholder
placeholder提供一张在网络请求还没有完成时显示的图片,它必须是本地图片,代码如下:
设置placeholder之后,在加载图片的时候,就可以显示设置的默认图了,提升用户体验。
error
和placeholder 的用法一样,error 提供一张在加载图片出错的情况下显示的默认图
noPlaceholder
这个方法的意思就是:在调用into的时候明确告诉你没有占位图设置。根据这个方法签名的解释是阻止View被回收的时候Picasso清空target或者设置一个应用的占位图。需要注意的是placeholder和noPlaceholder 不能同时应用在同一个请求上,会抛异常。
noFade
无论你是否设置了占位图,Picasso 从磁盘或者网络加载图片时,into 显示到ImageView 都会有一个简单的渐入过度效果,让你的UI视觉效果更柔顺丝滑一点,如果你不要这个渐入的效果,就调用noFade方法。
3. 设置图片尺寸(Resize)、缩放(Scale)和裁剪(Crop)
Resize(int w,int h)
在项目中,为了带宽、内存使用和下载速度等考虑,服务端给我们的图片的size 应该和我们View 实际的size一样的,但是实际情况并非如此,服务端可能给我们一些奇怪的尺寸的图片,我们可以使用resize(int w,int hei) 来重新设置尺寸。
resize()方法接受的参数的单位是pixels,还有一个可以设置dp单位的方法,将你的尺寸写在dimens.xml文件中,然后用resizeDimen(int targetWidthResId, int targetHeightResId)
方法
onlyScaleDown
当调用了resize 方法重新设置图片尺寸的时候,调用onlyScaleDown 方法,只有当原始图片的尺寸大于我们指定的尺寸时,resize才起作用,如:
只有当原来的图片尺寸大于4000 x 2000的时候,resize 才起作用。
图片裁剪 centerCrop()
这个属性应该不陌生吧!ImageView 的ScaleType 就有这个属性。当我们使用resize 来重新设置图片的尺寸的时候,你会发现有些图片拉伸或者扭曲了(使用ImageView的时候碰到过吧),我要避免这种情况,Picasso 同样给我们提供了一个方法,centerCrop,充满ImageView 的边界,居中裁剪。
centerInside
上面的centerCrop是可能看不到全部图片的,如果你想让View将图片展示完全,可以用centerInside,但是如果图片尺寸小于View尺寸的话,是不能充满View边界的。
fit
fit 是干什的呢?上面我们需要用resize()来指定我们需要的图片的尺寸,那就是说在程序中需要我们计算我们需要的尺寸(固定大小的除外),这样很麻烦,fit 方法就帮我们解决了这个问题。fit 它会自动测量我们的View的大小,然后内部调用reszie方法把图片裁剪到View的大小,这就帮我们做了计算size和调用resize 这2步。非常方便。代码如下:
使用fit 还是会出现拉伸扭曲的情况,因此最好配合前面的centerCrop使用,代码如下:
看一下对比图:
fit(会拉伸):
fit & centerCrop (不会拉伸):
注意:特别注意,
1,fit 只对ImageView 有效
2,使用fit时,ImageView 宽和高不能为wrap_content,很好理解,因为它要测量宽高。
4. 图片旋转Rotation()
在图片显示到ImageView 之前,还可以对图片做一些旋转操作,调用rotate(int degree)
方法
这个方法它是以(0,0)点旋转,但是有些时候我们并不想以(0,0)点旋转,还提供了另外一个方法可以指定原点:
- rotate(float degrees, float pivotX, float pivotY) 以(pivotX, pivotY)为原点旋转
5. 转换器Transformation
Transformation 这就是Picasso的一个非常强大的功能了,它允许你在load图片 -> into ImageView 中间这个过成对图片做一系列的变换。比如你要做图片高斯模糊、添加圆角、做度灰处理、圆形图片等等都可以通过Transformation来完成。
来看一个高斯模糊的例子:
第一步, 首先定义一个转换器继承Transformation
public static class BlurTransformation implements Transformation{ RenderScript rs; public BlurTransformation(Context context) { super(); rs = RenderScript.create(context); } @Override public Bitmap transform(Bitmap bitmap) { // Create another bitmap that will hold the results of the filter. Bitmap blurredBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true); // Allocate memory for Renderscript to work with Allocation input = Allocation.createFromBitmap(rs, blurredBitmap, Allocation.MipmapControl.MIPMAP_FULL, Allocation.USAGE_SHARED); Allocation output = Allocation.createTyped(rs, input.getType()); // Load up an instance of the specific script that we want to use. ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); script.setInput(input); // Set the blur radius script.setRadius(25); // Start the ScriptIntrinisicBlur script.forEach(output); // Copy the output to the blurred bitmap output.copyTo(blurredBitmap); bitmap.recycle(); return blurredBitmap; } @Override public String key() { return "blur"; } }
第二步, 加载图片的时候,在into 方法前面调用 transform方法 应用Transformation
Picasso.with(this).load(URL) .placeholder(R.drawable.default_bg) .error(R.drawable.error_iamge) .transform(new BlurTransformation(this)) .into(mBlurImage);
看一下效果图:
上面为原图,下面为高斯模糊图
是不是很强大,任何复杂的变换都可以通过Transformation 来做。
还不止于此,还有更强大的功能。可以在一个请求上应用多个Transformation
比如:我想先做个度灰处理然后在做一个高斯模糊图:
第一步, 定义一个度灰的Transformation
public static class GrayTransformation implements Transformation{ @Override public Bitmap transform(Bitmap source) { int width, height; height = source.getHeight(); width = source.getWidth(); Bitmap bmpGrayscale = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); Canvas c = new Canvas(bmpGrayscale); Paint paint = new Paint(); ColorMatrix cm = new ColorMatrix(); cm.setSaturation(0); ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm); paint.setColorFilter(f); c.drawBitmap(source, 0, 0, paint); if(source!=null && source!=bmpGrayscale){ source.recycle(); } return bmpGrayscale; } @Override public String key() { return "gray"; } }
第二步, 如果是多个Transformation操作,有两种方式应用
方式一:直接调用多次transform 方法,不会覆盖的。它只是保存到了一个List 里面
Picasso.with(this).load(URL) .placeholder(R.drawable.default_bg) .error(R.drawable.error_iamge) .fit() .centerCrop() .transform(new GrayTransformation())//度灰处理 .transform(new BlurTransformation(this))//高斯模糊 .into(mBlurImage);
需要注意调用的顺序
方式二:接受一个List,将Transformation 放大list 里
Listtransformations = new ArrayList<>(); transformations.add(new GrayTransformation()); transformations.add(new BlurTransformation(this)); Picasso.with(this).load(URL) .placeholder(R.drawable.default_bg) .error(R.drawable.error_iamge) .fit() .centerCrop() .transform(transformations) .into(mBlurImage);
效果图:
如上图,第一张为度灰操作,第二张为 度灰+高斯模糊
另外发现了一个开源库,专门写了很多好玩的Transformation,有兴趣的可以看一下:
picasso-transformations
6. 请求优先级
Picasso 为请求设置有优先级,有三种优先级,LOW、NORMAL、HIGH。默认情况下都是NORMAL,除了调用fetch 方法,fetch 方法的优先级是LOW。
可以通过priority方法设置请求的优先级,这会影响请求的执行顺序,但是这是不能保证的,它只会往高的优先级靠拢。代码如下:
7. Tag管理请求
Picasso 允许我们为一个请求设置tag来管理请求,看一下对应的几个方法:
下面3个方法是Picasso这个类的:
- cancelTag(Object tag) 取消设置了给定tag的所有请求
- pauseTag(Object tag) 暂停设置了给定tag 的所有请求
- resumeTag(Object tag) resume 被暂停的给定tag的所有请求
还有一个方法是RequestCreator的:
- tag(Object tag) 为请求设置tag
几个方法的意思也很明确,就是我们可以暂停、resume、和取消请求,可以用在哪些场景呢?
场景一: 比如一个照片流列表,当我们快速滑动列表浏览照片的时候,后台会一直发起请求加载照片的,这可能会导致卡顿,那么我们就可以为每个请求设置一个相同的Tag,在快速滑动的时候,调用pauseTag暂停请求,当滑动停止的时候,调用resumeTag恢复请求,这样的体验是不是就会更好一些呢。
Adapter中添加如下代码:
Activity中为RecyclerView添加滑动监听:
场景二: 比如一个照片流列表界面,在弱网环境下,加载很慢,退出这个界面时可能会有很多请求没有完成,这个时候我们就可 以通过tag 来取消请求了。
8. 同步/异步加载图片
Picasso 加载图片也有同步/异步两种方式
同步get()
很简单,同步加载使用get() 方法,返回一个Bitmap 对象,代码如下:
注意:使用同步方式加载,不能放在主线程来做。
异步fetch()
一般直接加载图片通过into显示到ImageView 是异步的方式,除此之外,还提供了2个异步的方法:
- fetch() 异步方式加载图片
- fetch(Callback callback) 异步方式加载图片并给一个回调接口。
这里就要吐槽一下接口设计了,回调并没有返回Bitmap, 不知道作者是怎么考虑的,只是一个通知效果,知道请求失败还是成功。
**fetch 方法异步加载图片并没有返回Bitmap,这个方法在请求成功之后,将结果存到了缓存,包括磁盘和内存缓存。所以使用这种方式加载图片适用于这种场景:知道稍后会加载图片,使用fetch 先加载缓存,起到一个预加载的效果。 **
9. 缓存(Disk 和 Memory)
Picasso 有内存缓存(Memory)和磁盘缓存( Disk), 首先来看一下源码中对于缓存的介绍:
- LRU memory cache of 15% the available application RAM
- Disk cache of 2% storage space up to 50MB but no less than 5MB. (Note: this is only
available on API 14+ or if you are using a standalone library that provides a disk cache on all API levels like OkHttp) - Three download threads for disk and network access.
可以看出,内存缓存是使用的LRU 策略的缓存实现,它的大小是内存大小的15%,可以自定义它的大小,最后在扩展那一章节再讲,磁盘缓存是磁盘容量的2%但是不超过50M,不少于5M。处理一个请求的时候,按照这个顺讯检查:memory->disk->network 。先检查有木有内存缓存,如果命中,直接返回结果,否则检查磁盘缓存,命中则返回结果,没有命中则从网上获取。
默认情况下,Picasso 内存缓存和磁盘缓存都开启了的,也就是加载图片的时候,内存和磁盘都缓存了,但是有些时候,我们并不需要缓存,比如说:加载一张大图片的时候,如果再内存中保存一份,很容易造成OOM,这时候我们只希望有磁盘缓存,而不希望缓存到内存,因此就需要我们设置缓存策略了。Picasso 提供了这样的方法。
方式一:memoryPolicy 设置内存缓存策略
就像上面所说的,有时候我们不希望有内存缓存,我们可以通过 memoryPolicy 来设置。MemoryPolicy是一个枚举,有两个值
NO_CACHE:表示处理请求的时候跳过检查内存缓存
**NO_STORE: ** 表示请求成功之后,不将最终的结果存到内存。
示例代码如下:
方式二:networkPolicy 设置磁盘缓存策略
和内存缓存一样,加载一张图片的时候,你也可以跳过磁盘缓存,和内存缓存策略的控制方式一样,磁盘缓存调用方法networkPolicy(NetworkPolicy policy, NetworkPolicy... additional)
, NetworkPolicy是一个枚举类型,有三个值:
NO_CACHE: 表示处理请求的时候跳过处理磁盘缓存
NO_STORE:表示请求成功后,不将结果缓存到Disk,但是这个只对OkHttp有效。
OFFLINE: 这个就跟 上面两个不一样了,如果networkPolicy方法用的是这个参数,那么Picasso会强制这次请求从缓存中获取结果,不会发起网络请求,不管缓存中能否获取到结果。
使用示例:
强制从缓存获取:
10. Debug 和日志
缓存指示器
上一节说了,Picasso 有内存缓存和磁盘缓存,先从内存获取,没有再去磁盘缓存获取,都有就从网络加载,网络加载是比较昂贵和耗时的。因此,作为一个开发者,我们往往需要加载的图片是从哪儿来的(内存、Disk还是网络),Picasso让我们很容易就实现了。只需要调用一个方法setIndicatorsEnabled(boolean)
就可以了,它会在图片的左上角出现一个带色块的三角形标示,有3种颜色,绿色表示从内存加载、蓝色表示从磁盘加载、红色表示从网络加载。
效果图:
如上图所示,第一张图从网络获取,第二张从磁盘获取,第三张图从内存获取。
看一下源码中定义指示器的颜色:
可以很清楚看出,对应三种颜色代表着图片的来源。
日志
上面的指示器能够很好的帮助我们看出图片的来源,但是有时候我们需要更详细的信息,Picasso,可以打印一些日志,比如一些关键方法的执行时间等等,我们只需要调用setLoggingEnabled(true)
方法,然后App在加载图片的过程中,我们就可以从logcat 看到一些关键的日志信息。
11. Picasso 扩展
到目前为止,Picasso的基本使用已经讲得差不多了,但是在实际项目中我们这可能还满足不了我们的需求,我们需要对它做一些自己的扩展,比如我们需要换缓存的位置、我们需要扩大缓存、自定义线程池、自定义下载器等等。这些都是可以的,接下来我们来看一下可以做哪些方面的扩展。
用Builder 自己构造一个Picasso Instance
我们来回顾一下前面是怎么用Picasso 加载图片的:
总共3步:
(1) 用with方法获取一个Picasso 示例
(2) 用load方法加载图片
(3) 用into 放法显示图片
首先Picasso是一个单例模式,我们每一次获取的示例都是默认提供给我们的实例。但是也可以不用它给的Instance,我们直接用builder来构造一个Picasso:
这样我们就构造了一个局部的Picasso实例,当然了,我们直接用new 了一个builder,然后build()生成了一个Picasso。这跟默认的通过with方法获取的实例是一样的。那么现在我们就可以配置一些自定义的功能了。
配置自定义下载器 downLoader
如果我们不想用默认提供的Downloader,那么我们可以自定义一个下载器然后配置进去。举个例子:
总共5步:
(1) 先自定义一个Downloader(只是举个例子,并没有实现):
(2) 然后通过builder 配置:
这样配置后,我们用build()生成的Picasso 实例来加载图片就会使用自定义的下载器来下载图片了。
(3) 配置缓存
前面说过,内存缓存是用的LRU Cahce ,大小是手机内存的15% ,如果你想缓存大小更大一点或者更小一点,可以自定义,然后配置。
上面只是一个简单的举例,当然了你可以自定义,也可以使用LRUCache,改变大小,改变存储路径等等。
提示: 很遗憾,好像没有提供改变磁盘缓存的接口,那就只能用默认的了。
(4) 配置线程池
Picasso 默认的线程池的核心线程数为3,如果你觉得不够用的话,可以配置自己需要的线程池,举个列子:
(5) 配置全局的 Picasso Instance
上面说的这些自定义配置项目都是应用在一个局部的Picasso instance 上的,我们不可能每一次使用都要重新配置一下,这样就太麻烦了。我们希望我们的这些自定义配置能在整个项目都应用上,并且只配置一次。其实Picasso 给我们提供了这样的方法。可以调用setSingletonInstance(Picasso picasso)
就可以了,看一下这个方法的源码:
设置一个通过with方法返回的全局instance。我们只希望配置一次,所以,我们应该在Application 的onCreate方法中做全局配置就可以了。app一启动就配置好,然后直接和前面的使用方法一样,调用with方法获取Picasso instance 加载图片就OK了。
因此在Application 中添加如下代码:
然后应用这些自定义配置加载图片
用法和以前的一样,但是我们已经将我们的自定义配置应用上了。
结尾
以上就是对Picasso 用法的全部总结,如有什么问题,欢迎留言指正。Picasso真的是一个强大的图片加载缓存库,API 简单好用,而且是链式调用的(这点我特别喜欢)。官方文档写的比较简单,很多用法都要看源码和注释才知道。希望本文能给才开始使用Picasso 的同学一点帮助。
作者:依然范特稀西
链接:https://www.jianshu.com/p/c68a3b9ca07a
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。