写点什么

图片加载框架-Picasso 最详细的使用指南

  • 2021 年 11 月 07 日
  • 本文字数:7430 字

    阅读完需:约 24 分钟

Picasso.with(this).load(URL)


.placeholder(R.drawable.default_bg)


.error(R.drawable.error_iamge)


.resize(400,200)


.centerCrop()


.into(mImageView);


4,centerInside


上面的 centerCrop 是可能看不到全部图片的,如果你想让 View 将图片展示完全,可以用 centerInside,但是如果图片尺寸小于 View 尺寸的话,是不能充满 View 边界的。


Picasso.with(this).load(URL)


.placeholder(R.drawable.default_bg)


.error(R.drawable.error_iamge)


.resize(400,200)


.centerInside()


.into(mImageView);


5,fit


fit 是干什的呢?上面我们需要用 resize()来指定我们需要的图片的尺寸,那就是说在程序中需要我们计算我们需要的尺寸(固定大小的除外),这样很麻烦,fit 方法就帮我们解决了这个问题。fit 它会自动测量我们的 View 的大小,然后内部调用 reszie 方法把图片裁剪到 View 的大小,这就帮我们做了计算 size 和调用 resize 这 2 步。非常方便。代码如下:


Picasso.with(this).load(URL)


.placeholder(R.drawable.default_bg)


.error(R.drawable.error_iamge)


.fit()


.into(mImageView);


使用 fit 还是会出现拉伸扭曲的情况,因此最好配合前面的 centerCrop 使用,代码如下:


Picasso.with(this).load(URL)


.placeholder(R.drawable.default_bg)


.error(R.drawable.error_iamge)


.fit()


.centerCrop()


.into(mImageView);


看一下对比图:


fit(会拉伸):



image_fit.png


fit & centerCrop (不会拉伸):



fit_centerCrop.png


注意:特别注意,


1,fit 只对 ImageView 有效


2,使用 fit 时,ImageView 宽和高不能为 wrap_content,很好理解,因为它要测量宽高。

4. 图片旋转 Rotation()

在图片显示到 ImageView 之前,还可以对图片做一些旋转操作,调用rotate(int degree)方法


Picasso.with(this).load(URL)


.placeholder(R.drawable.default_bg)


.error(R.drawable.error_iamge)


.rotate(180)


.into(mImageView);


这个方法它是以(0,0)点旋转,但是有些时候我们并不想以(0,0)点旋转,还提供了另外一个方法可以指定原点:


  • rotate(float degrees, float pivotX, float pivotY) 以(pivotX, pivotY)为原点旋转


Picasso.with(this).load(URL)


.placeholder(R.drawable.default_bg)


.error(R.drawable.error_iamge)


.rotate(180,200,100)


.into(mImageView);

5. 转换器 Transformation

Transformation 这就是 Picasso 的一个非常强大的功能了,它允许你在 load 图片 -> into ImageView 中间这个过成对图片做一系列的变换。比如你要做图片高斯模糊、添加圆角、做度灰处理、圆形图片等等都可以通过 Transformation 来完成。


来看一个高斯模糊的例子:


1,首先定义一个转换器继承 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";


}


}


2, 加载图片的时候,在 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.png


上面为原图,下面为高斯模糊图


是不是很强大,任何复杂的变换都可以通过 Transformation 来做。


还不止于此,还有更强大的功能。可以在一个请求上应用多个 Transformation


比如:我想先做个度灰处理然后在做一个高斯模糊图:


1, 度灰的 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";


}


}


2, 如果是多个 Transformation 操作,有 2 种方式应用


方式一:直接调用多次 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 里


List<Transformation> transformations = 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);


效果图:



gray_blur.png


如上图,第一张为度灰操作,第二张为 度灰+高斯模糊


另外发现了一个开源库,专门写了很多好玩的 Transformation,有兴趣的可以看一下:


picasso-transformations

6. 请求优先级

Picasso 为请求设置有优先级,有三种优先级,LOW、NORMAL、HIGH。默认情况下都是 NORMAL,除了调用 fetch 方法,fetch 方法的优先级是 LOW。


public enum Priority {


LOW,


NORMAL,


HIGH


}


可以通过 priority 方法设置请求的优先级,这会影响请求的执行顺序,但是这是不能保证的,它只会往高的优先级靠拢。代码如下:


Picasso.with(this).load(URL)


.placeholder(R.drawable.default_bg)


.error(R.drawable.error_iamge)


.priority(Picasso.Priority.HIGH)


// .priority(Picasso.Priority.LOW)


.into(mImageView);

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 中添加如下代码:


Picasso.with(this).load(mData.get(position))


.placeholder(R.drawable.default_bg)


.error(R.drawable.error_iamge)


.tag("PhotoTag")


.into(holder.mImageView);


Activity 中为 RecyclerView 添加滑动监听:


mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {


@Override


public void onScrollStateChanged(RecyclerView recyclerView, int newState) {


final Picasso picasso = Picasso.with(MainActivity.this);


if (newState == SCROLL_STATE_IDLE) {


picasso.resumeTag("PhotoTag");


} else {


picasso.pauseTag("PhotoTag");


}


}


});


场景二: 比如一个照片流列表界面,在弱网环境下,加载很慢,退出这个界面时可能会有很多请求没有完成,这个时候我们就可 以通过 tag 来取消请求了。


@Override


protected void onDestroy() {


super.onDestroy();


Picasso.with(this).cancelTag("PhotoTag");


}

8. 同步/异步加载图片

Picasso 加载图片也有同步/异步两种方式


**1,get() 同步 **


很简单,同步加载使用 get() 方法,返回一个 Bitmap 对象,代码如下:


try {


Bitmap bitmap = Picasso.with(this).load(URL).get();


} catch (IOException e) {


e.printStackTrace();


}


注意:使用同步方式加载,不能放在主线程来做。


2,异步的方式加载图片,fetch()


一般直接加载图片通过 into 显示到 ImageView 是异步的方式,除此之外,还提供了 2 个异步的方法:


  • fetch() 异步方式加载图片

  • fetch(Callback callback) 异步方式加载图片并给一个回调接口。


Picasso.with(this).load(URL).fetch(new Callback() {


@Override


public void onSuccess() {


//加载成功


}


@Override


public void onError() {


//加载失败


}


});


这里就要吐槽一下接口设计了,回调并没有返回 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+ <em>or</em> 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 提供了这样的方法。


1,memoryPolicy 设置内存缓存策略


就像上面所说的,有时候我们不希望有内存缓存,我们可以通过 memoryPolicy 来设置。MemoryPolicy 是一个枚举,有两个值


**NO_CACHE:**表示处理请求的时候跳过检查内存缓存


**NO_STORE: ** 表示请求成功之后,不将最终的结果存到内存。


示例代码如下:


with(this).load(URL)


.placeholder(R.drawable.default_bg)


.error(R.drawable.error_iamge)


.memoryPolicy(MemoryPolicy.NO_CACHE,MemoryPolicy.NO_STORE) //静止内存缓存


.into(mBlurImage);


2,networkPolicy 设置磁盘缓存策略


和内存缓存一样,加载一张图片的时候,你也可以跳过磁盘缓存,和内存缓存策略的控制方式一样,磁盘缓存调用方法networkPolicy(NetworkPolicy policy, NetworkPolicy... additional) , NetworkPolicy 是一个枚举类型,有三个值:


NO_CACHE: 表示处理请求的时候跳过处理磁盘缓存


** NO_STORE:** 表示请求成功后,不将结果缓存到 Disk,但是这个只对 OkHttp 有效。


OFFLINE: 这个就跟 上面两个不一样了,如果 networkPolicy 方法用的是这个参数,那么 Picasso 会强制这次请求从缓存中获取结果,不会发起网络请求,不管缓存中能否获取到结果。


使用示例:


with(this).load(URL)


.placeholder(R.drawable.default_bg)


.error(R.drawable.error_iamge)


.memoryPolicy(MemoryPolicy.NO_CACHE,MemoryPolicy.NO_STORE)//跳过内存缓存


.networkPolicy(NetworkPolicy.NO_CACHE)//跳过磁盘缓存


.into(mBlurImage);


强制从缓存获取:


with(this).load(URL)


.placeholder(R.drawable.default_bg)


.error(R.drawable.error_iamge)


.networkPolicy(NetworkPolicy.OFFLINE)//强制从缓存获取结果


.into(mBlurImage);

10. Debug 和日志

1,缓存指示器


上一节说了,Picasso 有内存缓存和磁盘缓存,先从内存获取,没有再去磁盘缓存获取,都有就从网络加载,网络加载是比较昂贵和耗时的。因此,作为一个开发者,我们往往需要加载的图片是从哪儿来的(内存、Disk 还是网络),Picasso 让我们很容易就实现了。只需要调用一个方法setIndicatorsEnabled(boolean)就可以了,它会在图片的左上角出现一个带色块的三角形标示,有 3 种颜色,绿色表示从内存加载、蓝色表示从磁盘加载、红色表示从网络加载。


Picasso.with(this)


.setIndicatorsEnabled(true);//显示指示器


效果图:



cache_indicator.png


如上图所示,第一张图从网络获取,第二张从磁盘获取,第三张图从内存获取。


看一下源码中定义指示器的颜色:


/** 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;


}


}


可以很清楚看出,对应三种颜色代表着图片的来源。


2,日志


上面的指示器能够很好的帮助我们看出图片的来源,但是有时候我们需要更详细的信息,Picasso,可以打印一些日志,比如一些关键方法的执行时间等等,我们只需要调用setLoggingEnabled(true)方法,然后 App 在加载图片的过程中,我们就可以从 logcat 看到一些关键的日志信息。


Picasso.with(this)


.setLoggingEnabled(true);//开启日志打印

11. Picasso 扩展

到目前为止,Picasso 的基本使用已经讲得差不多了,但是在实际项目中我们这可能还满足不了我们的需求,我们需要对它做一些自己的扩展,比如我们需要换缓存的位置、我们需要扩大缓存、自定义线程池、自定义下载器等等。这些都是可以的,接下来我们来看一下可以做哪些方面的扩展。


1,用 Builder 自己构造一个 Picasso Instance


我们来回顾一下前面是怎么用 Picasso 加载图片的:


Picasso.with(this)


.load("http://ww3.sinaimg.cn/large/610dc034jw1fasakfvqe1j20u00mhgn2.jpg")


.into(mImageView);


总共 3 步:


1,用 with 方法获取一个 P


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


icasso 示例


2,用 load 方法加载图片


3,用 into 放法显示图片


首先 Picasso 是一个单例模式,我们每一次获取的示例都是默认提供给我们的实例。但是也可以不用它给的 Instance,我们直接用 builder 来构造一个 Picasso:


Picasso.Builder builder = new Picasso.Builder(this);


//构造一个 Picasso


Picasso picasso = builder.build();


//加载图片


picasso.load(URL)


.into(mImageView);


这样我们就构造了一个局部的 Picasso 实例,当然了,我们直接用 new 了一个 builder,然后 build()生成了一个 Picasso。这跟默认的通过 with 方法获取的实例是一样的。那么现在我们就可以配置一些自定义的功能了。


2, 配置自定义下载器 downLoader


如果我们不想用默认提供的 Downloader,那么我们可以自定义一个下载器然后配置进去。举个例子:


(1) 先自定义一个 Downloader(只是举个例子,并没有实现):


/**


  • Created by zhouwei on 17/2/26.


*/


public class CustomDownloader implements Downloader {


@Override


public Response load(Uri uri, int networkPolicy) throws IOException {


return null;


}


@Override


public void shutdown() {


}


}


(2) 然后通过 builder 配置:


//配置下载器


builder.downloader(new CustomDownloader());


//构造一个 Picasso


Picasso picasso = builder.build();


这样配置后,我们用 build()生成的 Picasso 实例来加载图片就会使用自定义的下载器来下载图片了。


** 3, 配置缓存**


前面说过,内存缓存是用的 LRU Cahce ,大小是手机内存的 15% ,如果你想缓存大小更大一点或者更小一点,可以自定义,然后配置。


//配置缓存


LruCache cache = new LruCache(510241024);// 设置缓存大小


builder.memoryCache(cache);


上面只是一个简单的举例,当然了你可以自定义,也可以使用 LRUCache,改变大小,改变存储路径等等。


提示: 很遗憾,好像没有提供改变磁盘缓存的接口,那就只能用默认的了。


4, 配置线程池


Picasso 默认的线程池的核心线程数为 3,如果你觉得不够用的话,可以配置自己需要的线程池,举个列子:


//配置线程池


ExecutorService executorService = Executors.newFixedThreadPool(8);


builder.executor(executorService);


**5, 配置全局的 Picasso Instance **


上面说的这些自定义配置项目都是应用在一个局部的 Picasso instance 上的,我们不可能每一次使用都要重新配置一下,这样就太麻烦了。我们希望我们的这些自定义配置能在整个项目都应用上,并且只配置一次。其实 Picasso 给我们提供了这样的方法。可以调用setSingletonInstance(Picasso picasso)就可以了,看一下这个方法的源码:


/**


  • Set the global instance returned from {@link #with}.

  • <p>

  • This method must be called before any calls to {@link #with} and may only be called once.


*/


public static void setSingletonInstance(Picasso picasso) {


synchronized (Picasso.class) {


if (singleton != null) {


throw new IllegalStateException("Singleton instance already exists.");


}


singleton = picasso;


}


}


设置一个通过 with 方法返回的全局 instance。我们只希望配置一次,所以,我们应该在 Application 的 onCreate 方法中做全局配置就可以了。app 一启动就配置好,然后直接和前面的使用方法一样,调用 with 方法获取 Picasso instance 加载图片就 OK 了。

评论

发布
暂无评论
图片加载框架-Picasso最详细的使用指南