Picasso的使用

相信做Android开发的对Square公司一定不会陌生,大名鼎鼎的网络请求框架Retrofit就来源于它,今天学习的是该公司出品的图片加载框架Picasso。

项目地址

https://github.com/square/picasso

使用说明

http://square.github.io/picasso/

Gradle:

compile 'com.squareup.picasso:picasso:2.5.2'

ProGard混淆配置:

-dontwarn com.squareup.okhttp.**

简介

图片为Android应用增加必要的背景和视觉,Picasso使得你可以在应用中轻而易举地实现图片加载,只需一行代码!

Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);

许多在Android上图片加载常见的的陷阱都被Picasso自动的处理了:

  • adaper中处理ImageView循环和取消下载
  • 对复杂图像进行转换,使其占用最小的内存
  • 自动的内存和磁盘缓存

特性

在Adapter中下载

自动检测Adapter重用并取消之前的下载

@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);
}

图片转换

转换图片以更好地适配布局并减少内存使用

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()";
    }
}

传入该类的实例到transform方法

占位图

Picasso同时支持了下载和出错的占位图供用户选择

Picasso.with(context)
    .load(url)
    .placeholder(R.drawable.user_placeholder)
    .error(R.drawable.user_placeholder_error)
    .into(imageView);

出错占位图显示前一个请求会被重试三次

资源加载

支持Resourcesassetsfilescontent 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);

调试指示

开发时可以打开彩带显示来指示图片源,在Picasso实例调用setIndicatorsEnabled(true)即可

 

Picasso介绍

Picasso是Square公司开源的一个Android图形缓存库

A powerful image downloading and caching library for Android 
一个Android下强大的图片下载缓存库

Picasso实现了图片的异步加载,并解决了Android中加载图片时常见的一些问题,它有以下特点:

  • Adapter中取消了不在视图范围内的ImageView的资源加载,因为可能会产生图片错位;
  • 使用复杂的图片转换技术降低内存的使用
  • 自带内存和硬盘的二级缓存机制

为什么要用Picasso

  Android系统作为图片资源加载的主角,它是通过图像的像素点来把图像加载到内存中的;现在一张500W的摄像头拍出的照片(2592x1936),加载到内存中需要大约19M的内存;如果你加入了信号强度不一的网络中进行了复杂的网络请求,并进行图片的缓存与其他处理,你会耗费大量的时间与精力来处理这些问题,但如果用了Picasso, 这些问题都一消而散;

将Picasso加入到你的项目中

 目前Picasso的最新版本是2.5.2,你可以下载对应的Jar包,将Jar包添加到你的项目中,或者在build.gradle配置文件中加入

compile 'com.squareup.picasso:picasso:2.5.2'

注意如果你开启了混淆,你需要将以下代码添加到混淆规则文件中:

-dontwarn com.squareup.okhttp.**

小试牛刀:从网络加载一张图片

Picasso使用简单易用的接口,并有一个实现类Picasso,一个完整的功能请求至少需要三个参数;

  • with(Context context) - Context上下文在很多Android Api中都是必须的
  • load(String imageUrl) - 图片网络加载地址
  • into(ImageView targetImageView) - 想进行图片展示的ImageView

简单用例:

ImageView targetImageView = (ImageView) findViewById(R.id.imageView);
String internetUrl = "http://www.jycoder.com/json/Image/1.jpg";

Picasso
    .with(context)
    .load(internetUrl)
    .into(targetImageView);

  就是这么简单,如果你的 URL地址正确并且图片存在,在几秒中之内就能看到这张图片了;如果图片资源不存在,Picasso也会有错误的回调,现在你已经看到了只需3行代码就能加载图片了,当然这只是冰山一角,让我们继续揭开Picasso的神秘面纱;

图片的其他加载方式

  Picasso的图片不仅仅能加载网络资源,也能从本地文件,Android项目资源,以及URI地址进行图片加载,下面我们就对这三种方式进行实例说明;

从Android Resources 中加载

  代码也是三行,只需要将网络资源地址更改为一个int值地址即可,上代码:

ImageView targetImageView = (ImageView) findViewById(R.id.imageView);
int resourceId = R.mipmap.ic_launcher;

Picasso
    .with(context)
    .load(resourceId)
    .into(targetImageView);

注意: R.mipmapAndroid Studio中新的资源引用路径,这个老司机都知道.

从本地File文件中加载

  如果你让用户选择本地的一张图片进行展示的话,就需要用到这个加载方式了,当然,也是So Easy,只需要将地址更换为一个File即可,上代码:

ImageView targetImageView = (ImageView) findViewById(R.id.imageView);
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Running.jpg");

Picasso
    .with(context)
    .load(file)
    .into(targetImageView); 

注意:这个file并不一定非得是在你的设备中,可以是任意的路径,只要是File路径即可;

URI地址中加载

  这个请求方式相比其他也并没有什么不同,上代码:

public static final String ANDROID_RESOURCE = "android.resource://";
public static final String FOREWARD_SLASH = "/";

private static Uri resourceIdToUri(Context context, int resourceId) {
    return Uri.parse(ANDROID_RESOURCE + context.getPackageName() + FOREWARD_SLASH + resourceId);
}

Uri uri = resourceIdToUri(context, R.mipmap.future_studio_launcher);
ImageView targetImageView = (ImageView) findViewById(R.id.imageView);

Picasso  
    .with(context)
    .load(uri)
    .into(targetImageView);

注意:为了示范,只能用资源文件转换为URI,并不仅仅是这种方式, 它可以支持任意的URI地址;

OK,到此我们已经对Picasso有一个基本的认识和了解了,跟着我的脚步,继续发现Picasso更多好玩的功能,下面会介绍Picasso在ListViewGridView的用法

一个ListView的简单应用示例

1: 首先,需要先准备好一些网络图片资源

public static String[] imageUrls = {
            "http://i.imgur.com/rFLNqWI.jpg",
            "http://i.imgur.com/C9pBVt7.jpg",
            "http://i.imgur.com/rT5vXE1.jpg",
            "http://i.imgur.com/aIy5R2k.jpg",
            "http://i.imgur.com/MoJs9pT.jpg",
            "http://i.imgur.com/S963yEM.jpg",
            "http://i.imgur.com/rLR2cyc.jpg",
            "http://i.imgur.com/SEPdUIx.jpg",
            "http://i.imgur.com/aC9OjaM.jpg",
            "http://i.imgur.com/76Jfv9b.jpg",
            "http://i.imgur.com/fUX7EIB.jpg",
            "http://i.imgur.com/syELajx.jpg",
            "http://i.imgur.com/COzBnru.jpg",
            "http://i.imgur.com/Z3QjilA.jpg",
    };

2: 然后写一个简单的Activity,需要一个Adapter,并将Adapter设置到ListView中填充数据

public class MainActivity extends AppCompatActivity {

    private ListView lv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        lv = (ListView) findViewById(R.id.lv);
        lv.setAdapter(new ImageListAdapter(this,imageUrls));
    }
}

3:我们需要在Adapter中加载一个ListView子item的layout文件,当然也很简单

<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" 
    android:layout_height="200dp">

</ImageView>

4: 我们还需要一个自定义的Adapter,功能很简单,只显示一张图片即可

 public class ImageListAdapter extends ArrayAdapter{
        private Context context;

        private String[] imageUrls;

        public ImageListAdapter(Context context,String[] imageUrls){
            super(context,R.layout.item_picasso,imageUrls);

            this.context = context;
            this.imageUrls = imageUrls;

        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if (convertView==null){
                convertView = View.inflate(context,R.layout.item_picasso,null);
            }

            //加载图片
            Picasso
                    .with(context)
                    .load(imageUrls[position])
                    .into((ImageView) convertView);

            return convertView;
        }
    }

注意:

  • 我们一般会复用ConvertView来保持listview的快速平滑的滚动,而Picasso的一个优点就是会自动处理划出屏幕外的图片请求,并给对应的ImageView加载出正确的资源;
  • 另外,你会发现当你上下滚动后,会发现图片加载速度有了明显的提升,这正是因为Picasso的高速缓存,而且不需要再去从网络加载,Picasso所实现的缓存的大小取决于你自己的设备;
  • Picasso加载图片的资源会从三个地方进行获取, 内存,磁盘,和网络,这些操作都不需要你自己处理,Picasso已经能智能完成;

一个GridView的小示例

ListViewGridView的展示及使用上并没有什么区别,很简单,上代码:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.smallcheric.picasso.MainActivity">
    <android.support.v7.widget.AppCompatButton
        android:id="@+id/bt"
        android:layout_centerHorizontal="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="切换"/>
    <ListView
        android:id="@+id/lv"
        android:layout_below="@id/bt"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
         />
    <GridView
        android:id="@+id/grid"
        android:layout_below="@id/bt"
        android:visibility="gone"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:numColumns="2"/>
</RelativeLayout>

最后,附上Github地址点我

如果图片地址不存在或为空怎么处理

上面我们写的代码都是在正常的情况下,但是如果我们的图片地址错误将怎么处理呢,如果不去处理,网络可能会一直请求或者我们的屏幕上会出现一片空白,这都不是我们希望看到的.

Picasso给了我们两种解决方案:

  • 在判断为空的地址时,取消网络请求,调用cancelRequest(),然后调用imageView.setImageDrawable(null)
  • 或者调用Picasso的.placeHolder()方法进行图片的替换展示
  • 如果图片网址错误,我们也可以调用.error()方法进行图片替换
 @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if (convertView==null){
                convertView = View.inflate(context,R.layout.item_picasso,null);
            }
            ImageView imageView = (ImageView)convertView;
            if (TextUtils.isEmpty(imageUrls[position])){
                Picasso
                        .with(context)
                        .cancelRequest(imageView);
                imageView.setImageDrawable(null);
            }else {
                //加载图片
                Picasso
                        .with(context)
                        .load(imageUrls[position])
                        .placeholder(R.mipmap.ic_launcher)
                        .error(R.mipmap.ic_launcher)
                        .into((ImageView) convertView);
            }
            return convertView;
        }

注意:.placeholder().error()所传的参数与.load()相同

OK,到现在为止,我们已经基本掌握了Picasso的基本用法,后面将为大家分析到Picasso性能方面的特性

前面我们对Picasso的用法有了一定得了解,下面就分析一下一些特殊情况下,Picasso的用法.

调用.noFade()

  Picasso的默认图片加载方式有一个淡入的效果,如果调用了noFade(),加载的图片将直接显示在ImageView

Picasso
    .with(context)
    .load(UsageExampleListViewAdapter.eatFoodyImages[0])
    .placeholder(R.mipmap.ic_launcher)  
    .error(R.mipmap.future_studio_launcher)  
    .noFade()
    .into(imageViewFade);

调用.noPlaceholder()

  有一个场景,当你从网上加载了一张图片到Imageview上,过了一段时间,想在同一个ImageView上展示另一张图片,这个时候你就会去调用Picasso,进行二次请求,这时Picasso就会把之前的图片进行清除,可能展示的是.placeholder()的图片,给用户并不是很好的体验,如果调用了noPlaceholder(),就不会出现这种情况.

Picasso
    .with(context)
    .load(UsageExampleListViewAdapter.eatFoodyImages[0])
    .placeholder(R.mipmap.ic_launcher)  
    .into(imageViewNoPlaceholder, new Callback() {
        @Override
        public void onSuccess() {
            // 当上次加载完成后,进行二次加载
            Picasso
                .with(context)
               .load(UsageExampleListViewAdapter.eatFoodyImages[1])
               .noPlaceholder() 
               .into(imageViewNoPlaceholder);
        }

        @Override
        public void onError() {

        }
    });

调用resize(x, y)来自定义图片的加载大小

如果图片很大或者想自定义图片的显示样式,可以调用该API来解决这个问题;

Picasso
    .with(context)
    .load(UsageExampleListViewAdapter.eatFoodyImages[0])
    .resize(600, 200)  
    .into(imageViewResize);

调用`onlyScaleDown()来缩短图片的加载计算时间

如果我们调用了resize(x,y)方法的话,Picasso一般会重新计算以改变图片的加载质量,比如一张小图变成一张大图进行展示的时候,但是如果我们的原图是比我们从新resize的新图规格大的时候,我们就可以调用onlyScaleDown()来直接进行展示而不再重新计算.

Picasso
    .with(context)
    .load(UsageExampleListViewAdapter.eatFoodyImages[0])
    .resize(6000, 2000)
    .onlyScaleDown() // 如果图片规格大于6000*2000,将只会被resize
    .into(imageViewResizeScaleDown);

对拉伸图片的处理

如果图片被操作了,可能在展示的时候就会比较丑,我们是想改变这种情景的,Picasso给我们提供了两种选择进行图片展示,centerCrop() 或者centerInside().

  • centerCrop() - 图片会被剪切,但是图片质量看着没有什么区别

  • Inside()- 图片会被完整的展示,可能图片不会填充满ImageView`,也有可能会被拉伸或者挤压

Picasso
    .with(context)
    .load(UsageExampleListViewAdapter.eatFoodyImages[0])
    .resize(600, 200)
    .centerInside() 或者调用 .centerCrop()
    .into(imageViewResizeCenterInside);

调用.fit()来智能展示图片

如果调用了该API, Picasso会对图片的大小及ImageView进行测量,计算出最佳的大小及最佳的图片质量来进行图片展示,减少内存,并对视图没有影响;

Picasso
    .with(context)
    .load(UsageExampleListViewAdapter.eatFoodyImages[0])
    .fit()
    .into(imageViewHero);

调用.priority()设置图片加载的优先级

如果一个屏幕上顶部图片较大,而底部图片较小,因为Picasso是异步加载,所以小图会先加载出来,但是对于用户来说,更希望看到的是上面的图片先加载,底部的图片后加载,Picasso支持设置优先级,分为HIGHMEDIUM, 和 LOW,所有的加载默认优先级为MEDIUM;

Picasso
    .with(context)
    .load(UsageExampleListViewAdapter.eatFoodyImages[0])
    .fit()
    .priority(Picasso.Priority.HIGH)
    .into(imageViewHero);

注意:设置优先级并不能保证图片就一定会被优先加载,只是会偏向倾斜于先加载;

调用tag()为请求添加标记提升用户体验

  我们都知道,在一个ListView的子item中加载一张图片是很常见的,这些图片都来源于网络请求,如果这个listview有上千条数据,当用户快速滑动的时候,每个item会不断的被复用,当然Picasso的请求也不断地进行请求,取消请求,再次请求,再次取消的操作(对屏幕外的自动取消请求),但是如果有一个方案,可以在用户在快速滑动的时候全部停止请求,只有在滑动停止时再去请求,就非常完美了;

Picasso提供了三种设置Tag的方式

  • 暂停标记 pauseTag()
  • 可见标记 resumeTag()
  • 取消标记 cancleTag()

pauseTag() 和 resumeTag()的用法

在图片请求时添加标记

Picasso
    .with(context)
    .load(UsageExampleListViewAdapter.eatFoodyImages[0])
    .tag("Profile ListView") //参数为 Object
    .into(imageViewWithTag);

然后让listview实现滑动监听

@Override
  public void onScrollStateChanged(AbsListView view, int scrollState) {
    final Picasso picasso = Picasso.with(context);

    if (scrollState == SCROLL_STATE_IDLE || scrollState == SCROLL_STATE_TOUCH_SCROLL) {
          picasso.resumeTag("Profile ListView");
    } else {
          picasso.pauseTag("Profile ListView");
    }
  }

cancleTag()的使用场景

试想一下,当你在浏览购物车的时候,这个时候就会去展示所有被选中item的图片资源,如果这个时候用户点击了购买按钮,就会弹出一个progressdialog去请求数据以进行页面跳转,这个时候原来的请求就需要取消掉了;

public void buyButtonClick(View v) {
     showDiaolg();

    // 取消网络请求
    Picasso
        .with(context)
        .cancelTag("ShoppingCart");
}

注意:如果tag状态为pause或者resume的话,Picasso会对tag持有一个引用,如果此时用户退出了当前Activity,垃圾回收机制进行回收的时候,就会出现内存泄露,所以需要在onDestory()方法中进行相应处理;

.fetch() , .get() 及 Target之间的区别

  • .fetch() - 该方法会在后台异步加载一张图片,但是不会展示在ImageView上,也不会返回Bitmap,这个方法只是为了将获取到的资源加载到本地和内存中,为了后期加载缩短时间;
  • .get() - 该方法也是一个异步线程,不过加载完成后会返回一个Bitmap,但是需要注意,该方法不能在主线程中调用,因为会造成线程阻塞;
  • Target - 我们之前调用.into()方法,只是将获取到的资源加载到ImageView中,但我们还可以将资源作为回调放到Target中,上代码:
private Target target = new Target() {
    @Override
    public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
        //加载成功后会得到一个bitmap,可以自定义操作
    }

    @Override
    public void onBitmapFailed(Drawable errorDrawable) {
        // 加载失败进行相应处理
    }

    @Override
    public void onPrepareLoad(Drawable placeHolderDrawable) {

    }
};

Picasso
    .with(context)
    .load(UsageExampleListViewAdapter.eatFoodyImages[0])
    .into(target);

注意:你可以使用.get()或者Target获取图片的Bitmap,但是当你使用Target时,不能使用匿名内部类的方式,因为垃圾回收机制在你获取不到Bitmap的时候会把对象回收;

Picasso在自定义Notifications上的使用

Picasso有一个功能是可以加载图片到RemoteViews上,而RemoteViews是用在Widgets及自定义notification布局上的,下面通过一个小的示例来看Picasso是如何起作用的;

 private void testRemoteView() {
        RemoteViews remoteViews = new RemoteViews(getPackageName(),R.layout.item_picasso);
        remoteViews.setImageViewResource(R.id.iv_remoteview,R.mipmap.abc);

        remoteViews.setTextViewText(R.id.tv_title,"This Title");
        remoteViews.setTextViewText(R.id.tv_desc,"This desc");

        remoteViews.setTextColor(R.id.tv_title,getResources().getColor(android.R.color.black));
        remoteViews.setTextColor(R.id.tv_desc,getResources().getColor(android.R.color.holo_blue_bright));

        NotificationCompat.Builder builder = new NotificationCompat.Builder(MainActivity.this)
                                                .setSmallIcon(R.mipmap.notifation)
                                                .setContentTitle("Context Title")
                                                .setContentText("Content Text")
                                                .setContent(remoteViews)
                                                .setPriority(NotificationCompat.PRIORITY_MIN);

        Notification notification = builder.build();

        if (Build.VERSION.SDK_INT > 16){
            notification.bigContentView = remoteViews;
        }
        NotificationManager mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
        mNotificationManager.notify(NOTIFICATION_ID,notification);

        Picasso.with(MainActivity.this)
                .load("http://www.jycoder.com/json/Image/3.jpg")
                .into(remoteViews,R.id.iv_remoteview,NOTIFICATION_ID,notification);
    }

上面可以看到,Picasso的使用也是非常简单,只需要调用.into()的另一个重载方法即可: 
.into(Android.widget.RemoteViews remoteViews, int viewId, int notificationId, android.app.Notification notification)

效果如下

Picasso进行图片的旋转(Rotation)

简单的旋转,只需要增加一个角度即可,调用rotate(float degrees)

当我们对一张图片需要进行简单的旋转处理时,只需要调用传入旋转的角度,大于0小于360即可,上代码:

Picasso
    .with(context)
    .load(UsageExampleListViewAdapter.eatFoodyImages[0])
    .rotate(90f) //旋转90度
    .into(imageViewSimpleRotate);

根据某个点进行复杂的旋转操作

因为图片的旋转都是相对(0,0)进行操作,所以如果我们想自定义相对于某个点,也是可以的,只需要调用 
.rotate(float degrees, float pivotX, float pivotY)

Picasso
    .with(context)
    .load(R.drawable.floorplan)
    .rotate(45f, 200f, 100f)
    .into(imageViewComplexRotate);

对图片的转化(Transformation)

模糊一张图片

我们可以在一张图片进行展示之前,对其进行操作,然后在展示;这时我们需要定义一个类实现Transformation,然后重写里面重要的方法,直接上代码:

public class BlurTransformation implements Transformation {

    RenderScript rs;

    public BlurTransformation(Context context) {
        super();
        rs = RenderScript.create(context);
    }

    @Override
    public Bitmap transform(Bitmap bitmap) {
        // 创建一个Bitmap作为最后处理的效果Bitmap
        Bitmap blurredBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);

        // 分配内存
        Allocation input = Allocation.createFromBitmap(rs, blurredBitmap, Allocation.MipmapControl.MIPMAP_FULL, Allocation.USAGE_SHARED);
        Allocation output = Allocation.createTyped(rs, input.getType());

        // 根据我们想使用的配置加载一个实例
        ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
        script.setInput(input);

        // 设置模糊半径
        script.setRadius(10);

        //开始操作
        script.forEach(output);

        // 将结果copy到blurredBitmap中
        output.copyTo(blurredBitmap);

        //释放资源
        bitmap.recycle();

        return blurredBitmap;
    }

    @Override
    public String key() {
        return "blur";
    }
}

//然后调用即可

Picasso
    .with(context)
    .load(UsageExampleListViewAdapter.eatFoodyImages[0])
    .transform(new BlurTransformation(context))
    .into(imageViewTransformationBlur);

在图片被加载之前就会被我们先处理一遍;

对加载的资源进行复杂的操作(模糊+缩放)

我们应该注意到,Picasso给我们提供了一个这样的API,允许我们将参数设置为一个Transformations的集合 
transform(List<? extends Transformation> transformations),这就意味着我们可以对资源进行一系列的操作;上代码:

public class GrayscaleTransformation implements Transformation {

    private final Picasso picasso;

    public GrayscaleTransformation(Picasso picasso) {
        this.picasso = picasso;
    }

    @Override
    public Bitmap transform(Bitmap source) {
        Bitmap result = createBitmap(source.getWidth(), source.getHeight(), source.getConfig());
        Bitmap noise;
        try {
            noise = picasso.load(R.drawable.noise).get();
        } catch (IOException e) {
            throw new RuntimeException("Failed to apply transformation! Missing resource.");
        }

        BitmapShader shader = new BitmapShader(noise, REPEAT, REPEAT);

        ColorMatrix colorMatrix = new ColorMatrix();
        colorMatrix.setSaturation(0);
        ColorMatrixColorFilter filter = new ColorMatrixColorFilter(colorMatrix);

        Paint paint = new Paint(ANTI_ALIAS_FLAG);
        paint.setColorFilter(filter);

        Canvas canvas = new Canvas(result);
        canvas.drawBitmap(source, 0, 0, paint);

        paint.setColorFilter(null);
        paint.setShader(shader);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));

        canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), paint);

        source.recycle();
        noise.recycle();

        return result;
    }

    @Override
    public String key() {
        return "grayscaleTransformation()";
    }
}

//下面进行调用

List<Transformation> transformations = new ArrayList<>();

transformations.add(new GrayscaleTransformation(Picasso.with(context)));
transformations.add(new BlurTransformation(context));

Picasso
    .with(context)
    .load(UsageExampleListViewAdapter.eatFoodyImages[0])
    .transform(transformations)
    .into(imageViewTransformationsMultiple);

这个transformations我相信已经能够给你足够强大的功能供你处理图片资源,到这里,Picasso基本所有的高级功能都被你所掌握了,已经非常好了,下面我们就开始分析Picasso的缓存组件,让我们继续揭开Picasso的神秘面纱;

关于缓存机制

Picasso默认的缓存分配大小特点:

  • LRU缓存占应用程序可用内存的15%
  • 本地缓存占到硬盘空间的2%但不超过50M并且不小于5M(前提是这种情况只在4.0以上有效果,或者你能像OKHttp那样提供一个本地缓存库来支持全平台)
  • Picasso默认开启3个线程来进行本地与网络之间的访问
  • Picasso加载图片顺序, 内存–>本地–>网络

Memory Policy

可能有的时候你不想让Picasso去内存中进行读取而跳过此步骤,这时你可以在进行网络请求时调用memoryPolicy(MemoryPolicy policy, MemoryPolicy... additional),MemoryPolicy是一个枚举,只有两个值 NO_CACHE 和 ‘NO_STORE`

  • NO_CACHE - 让Picasso跳过从内存中读取图片这一操作

  • NO_STORE - 如果你的图片只加载一次就没用了,就调用该值,这样的话Picasso就不会在内存及本地进行缓存了

代码示例:

Picasso  
    .with(context)
    .load(UsageExampleListViewAdapter.eatFoodyImages[1])
    .memoryPolicy(MemoryPolicy.NO_CACHE)
    .into(imageViewFromDisk);

当然,你也可以这样调用:

Picasso  
    .with(context)
    .load(UsageExampleListViewAdapter.eatFoodyImages[1])
    .memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)
    .into(imageViewFromDisk);

注意:调用.memoryPolicy(MemoryPolicy.NO_CACHE)虽然能避免Picasso从内存中读取资源,但是并不能避免从本地读取资源,如果你也想跳过从本地读取这一过程,请看NetworkPolicy.

NetworkPolicy

就像MemoryPolicy负责管理内存缓存一样,NetworkPolicy就是负责管理本地缓存的,而且二者的用法一模一样,NetworkPolicy也是一个枚举,不过它有三个值:

  • NO_CACHE - 让Picasso跳过从本地读取资源这一过程
  • NO_STORE - 让Picasso不进行本地图片缓存
  • OFFLINE - 让Picasso加载图片的时候只从本地读取除非联网正常并且本地找不到资源的情况下

示例代码:

Picasso  
    .with(context)
    .load(UsageExampleListViewAdapter.eatFoodyImages[2])
    .networkPolicy(NetworkPolicy.NO_CACHE)
    .into(imageViewFromNetwork);

当然,你也可以配合MemoryPolicy一起使用

Picasso  
    .with(context)
    .load(UsageExampleListViewAdapter.eatFoodyImages[2])
    .memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)
    .networkPolicy(NetworkPolicy.NO_CACHE)
    .into(imageViewFromNetwork); 

查看图片都来源于何处,缓存指示器

作为一个有经验的猿猿,我们肯定想知道我们所加载的图片来源于何处,是内存,本地还是从网络加载的,当然这个动作Picasso已经为我们想好了,我们只需要在请求的时候调用.setIndicatorsEnabled(true);就行了,代码:

Picasso  
    .with(context)
    .setIndicatorsEnabled(true);

这样每张图片在显示的时候,左上角都会有一个小标记,分别又三种三色,蓝色,绿色,红色;

  • 蓝色 - 从内存中获取,是最佳性能展示
  • 绿色 - 从本地获取,性能一般
  • 红色 - 从网络加载,性能最差

查看图片加载用时

缓存指示器能帮助我们查看图片的加载来源,但是并不能精确的知道每张图片从网络加载时所用的时间,在请求开始我们就加上调用.setLoggingEnabled(true),来通过输出日志的方式查看每张网络请求的资源所用的时间; 上代码:

Picasso  
    .with(context)
    .setLoggingEnabled(true);

Picasso  
    .with(context)
    .load(UsageExampleListViewAdapter.eatFoodyImages[2])
    .memoryPolicy(MemoryPolicy.NO_CACHE)
    .networkPolicy(NetworkPolicy.NO_CACHE)
    .into(imageViewFromNetwork);

然后通过在控制台上查看日志信息,如下:

D/Picasso﹕ Main        created      [R0] Request{http://i.imgur.com/rT5vXE1.jpg}  
D/Picasso﹕ Dispatcher  enqueued     [R0]+21ms  
D/Picasso﹕ Hunter      executing    [R0]+26ms  
D/Picasso﹕ Hunter      decoded      [R0]+575ms  
D/Picasso﹕ Dispatcher  batched      [R0]+576ms for completion  
D/Picasso﹕ Main        completed    [R0]+807ms from NETWORK  
D/Picasso﹕ Dispatcher  delivered    [R0]+809ms  

整体加载分析StatsSnapshot

你可能有一个场景,需要看一个大图的加载在内存中占用了多大,你可以调用StatsSnapshot即可,代码:


StatsSnapshot picassoStats = Picasso.with(context).getSnapshot();  
//然后打印
Log.d("Picasso Stats", picassoStats.toString()); 

最后输出结果为:


D/Picasso Stats﹕ StatsSnapshot{
maxSize=28760941, 
size=26567204, 
cacheHits=30, 
cacheMisses=58, 
downloadCount=0, 
totalDownloadSize=0, 
averageDownloadSize=0, 
totalOriginalBitmapSize=118399432, 
totalTransformedBitmapSize=96928004, 
averageOriginalBitmapSize=2466654, 
averageTransformedBitmapSize=2019333, 
originalBitmapCount=48, 
transformedBitmapCount=41, 
timeStamp=1432576918067}

OK,到此关于Picasso的高级用法我们都有了一定得了解,后面我们将介绍Picasso.Builder来改变Picasso的实例,愿大家都有一个美好的一天.

在之前的四篇博客中,我们学习了所有的关于Picasso的主要方法,我们也对这个Picasso有了一个很深的认识,下面就主要对Picasso自身进行分析,这样的话,会让我们更了解Picasso的核心方法;

创建一个Picasso

Picasso有一个直接的方法去创建一个它的实例,就是Picasso.Builder,这样可以创建属于我们自己的Picasso,而不是使用一个标准的Picasso;

标准创建方式

我们最常用的就是直接调用Picasso.with(context);就能返回一个Picasso实例,这就是一个标准的Picasso,很简单;

Picasso picasso = Picasso.with(context);

自定义创建实例

自定义就是调用Picasso.Builder,这是一个我们自己的Picasso,当然,也默认也实现的是标准的Picasso的所有功能,我们也可以像用标准的Picasso一样进行使用;

Picasso.Builder picassoBuilder = new Picasso.Builder(context);
Picasso picasso = picassoBuilder.build();  
picasso  
    .load(UsageExampleListViewAdapter.eatFoodyImages[0])
    .into(imageView1);

将自定义Picasso变成全局使用

我们可以在应用启动的时候调用Picasso.setSingletonInstance(picasso);,这样的话后面所有的调用Picasso.with(context)返回的都是我们自定义的Picasso;

try{
    Picasso.Builder picassoBuilder = new Picasso.Builder(context);
    Picasso picasso = picassoBuilder.build();  
    Picasso.setSingletonInstance(picasso);
}catch(IllegalStateException ignored){
    //Picasso示例已经被设置过时会出现该异常
    //在Picasso.with(context)调用之后进行设置也会出现该异常
}

更改Picasso中的Downloader

如果你想更换默认的Downloader,你可以通过Picasso.Builder调用.downloader(Downloader downloader),传入一个实现Downloader的实现类,比如OkHttpDownloader,上代码:

Picasso.Builder picassoBuilder = new Picasso.Builder(context);

//在创建Picasso的示例之前更改downloader
picassoBuilder.downloader(new OkHttpDownloader(new OkHttpClient()));

// 创建实例
Picasso picasso = picassoBuilder.build();

picasso  
   .load(UsageExampleListViewAdapter.eatFoodyImages[2])
   .into(imageView3);

但是有一个需要注意的地方,如果你加载的图片是从自己服务器上获取的,而且服务器用的是HTTPS,而且需要一个签名,这个时候OKHttp就会因为SSL的问题而导致图片无法被加载,这时你可以选择使用UnsafeOkHttpClient,就可以避免该问题的产生;

picassoBuilder.downloader(  
    new OkHttpDownloader(
        UnsafeOkHttpClient.getUnsafeOkHttpClient()
    )
);

内存缓存自定义及Request Handlers

  • Memory Cache - 如果你不满足系统默认的15%的可用空间的内存缓存大小,可以根据Picasso.Builder来自定义设置

  • Request Handlers - 如果你习惯于一个自定义URI的形式(内容提供者)进行图片访问,这里会提供一个很强大的工具,Request Handlers

自定义Request Handlers

首先需要我们自定义一个类来继承抽象类RequestHandler,实现里面的两个抽象方法

  1. boolean canHandleRequest(Request data) : 该方法是为了让Picasso来判断当前的request handler是否能处理现在的请求,如果可以,就去调用load()方法

  2. Result load(Request request, int networkPolicy) : 处理请求

示例代码:

/**
* 自定义一个类来继承RequestHandler
*/
public class EatFoodyRequestHandler extends RequestHandler {  
    private static final String EAT_FOODY_RECIPE_SCHEME = "eatfoody";

    @Override
    public boolean canHandleRequest(Request data) {
        //判断当前请求是否是自定义的URI (eg:eatfoody://cupcake)  ,如果是就会调用load方法
        return EAT_FOODY_RECIPE_SCHEME.equals(data.uri.getScheme());
    }

    @Override
    public Result load(Request request, int networkPolicy) throws IOException {
       // 我们可以进行网络请求或者进行本地加载,然后返回一个Result

       // 从请求中获取一个key值
       // 如果自定义URI为: "eatfoody://cupcake", key的值为 "cupcake"
       String imageKey = request.uri.getHost();

       Bitmap bitmap;
        //加载本地图片
       if (imageKey.contentEquals("cupcake")) {
           bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.cupcake);
       }
       else if (imageKey.contentEquals("full_cake")) {
           bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.full_cake);
       }
       else {

           bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
       }

       // 返回一个result
       return new Result(bitmap, Picasso.LoadedFrom.DISK);
    }
}

//在Picasso中使用也很简单
Picasso.Builder picassoBuilder = new Picasso.Builder(context);

picassoBuilder.addRequestHandler(new EatFoodyRequestHandler());

Picasso picasso = picassoBuilder.build(); 

//这是我们之前写的请求图片代码
//  picasso  
//    .load(UsageExampleListViewAdapter.eatFoodyImages[0])
//    .into(imageView1);

//如果使用了自定义的URI进行请求,就可以这样写,是不是很简单
picasso  
    .load("eatfoody://cupcake")
    .into(imageView2);

picasso  
    .load("eatfoody://full_cake")
    .into(imageView3);

OK,到此,我们关于Picasso的用法已经全部介绍完毕,随后一篇我们将对Picasso的原理进行剖析,来看Picasso底层是怎么工作的,冰冻三尺非一日之寒,愿大家都有美好的一天~

原文地址:https://www.cnblogs.com/kim-liu/p/7457033.html