Picasso的基本使用

Picasso笔者在项目里面,对图片的加载一般使用的是Picasso或者Glide,今天首先介绍的是Picasso,后续再介绍Glide。Picasso是Square公司开源的一个Android图片加载和缓存库,通过它,只需要简单的一行代码即可实现图片下载和缓存功能。它的github项目地址为:https://github.com/square/picasso

Picass流程图:

基本配置

如果大家使用AndroidStudio的话,可以直接compile:

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

当然,如果还在使用eclipse,它也提供了相应的jar包下载:
Picasso Jar

还可以使用maven的方式:

<dependency>
  <groupId>com.squareup.picasso</groupId>
  <artifactId>picasso</artifactId>
  <version>2.5.2</version>
</dependency>

因为在Picasso进行build时,创建Downloader时,会用反射去检测是否项目中使用了okhttp,如果项目集成了okhttp,则用okhttp去创建一个OkHttpDownloader.java下载器,所以当你对项目代码混淆的时候,需要添加ProGuard规则:

-dontwarn com.squareup.okhttp.**

具体可以看OkHttp3Downloader.java

使用方法

Picasso的基本使用

我们只需要一句话(使用链式调用),即可完成异步加载图片:

Picasso.with(imageView.getContext()).load(url).into(imageView);

它的load方法提供了四种形式,每个都是返回一个RequestCreator:

  • load(Uri uri)
  • load(String path)
  • load(File file)
  • load(int resourceId)

从参数我们就可以看出,有的是加载本地的,有的是从网络上取的。

Picasso加载网络图片

加载网络图片,只需要在load的参数里传入一个url就行:

Picasso.with(imageView.getContext())
                 .load(url)
                 .error(R.drawable.photo_holder_72dp)
                 .into(imageView);

error方法设置一个本地图片,当网络加载出错的时候,就会显示这张本地的图片。

这样的加载,一进入页面时,我们是看不到imageview上显示有图片的,为了更友好的显示效果,我们可以通过它的placeholder方法,先设置一张默认的图片,等网络加载完成后再显示新的图片。

Picasso.with(imageView.getContext())
                        .load(url)
                        .placeholder(R.drawable.photo_holder_72dp)
                        .error(R.drawable.photo_holder_72dp)
                        .networkPolicy(NetworkPolicy.NO_CACHE, NetworkPolicy.NO_STORE)
                        .into(imageView);

加载网络图片,并且设置了不检查硬盘中是否存在,最后的下载图片结果也不保存在硬盘中。对于本地硬盘缓存,如果用okhttp,可以设这两者,如果是默认的downloader,只能设置NO_CACHE:

/** Skips checking the disk cache and forces loading through the network. */
  NO_CACHE(1 << 0),

  /**
   * Skips storing the result into the disk cache.
   * <p>
   * <em>Note</em>: At this time this is only supported if you are using OkHttp.
   */
  NO_STORE(1 << 1),

  /** Forces the request through the disk cache only, skipping network. */
  OFFLINE(1 << 2);

Picasso常用设置

1、 如果想按等比例的压缩图片,可以使用fit()方法。

Picasso.with(imageView.getContext())
                        .load(url)
                        .fit()
                        .into(imageView);

2、 如果想按特定值去压缩图片,可用使用resize()方法。

Picasso.with(imageView.getContext())
                        .load(url)
                        .resize(100, 100)
                        .into(imageView);

3、 如果想旋转图片,可以使用rotate()方法。

Picasso.with(imageView.getContext())
                        .load(url)
                        .rotate(180)
                        .into(imageView);

4、 我们还可以设置图片的显示拉伸样式为由中间向外,即ImageView的scaleType的centerCrop。

Picasso.with(imageView.getContext())
                        .load(url)
                        .centerCrop()
                        .into(imageView);

5、 Picasso并没有直接使用传入的context,而是通过它获取ApplicationContext,因为它内部使用了单例模式,这样可以防止内存泄漏。

public static Picasso with(Context context) {
    if (singleton == null) {
      synchronized (Picasso.class) {
        if (singleton == null) {
          singleton = new Builder(context).build();
        }
      }
    }
    return singleton;
  }

// Start building a new {@link Picasso} instance.
    public Builder(Context context) {
      if (context == null) {
        throw new IllegalArgumentException("Context must not be null.");
      }
      this.context = context.getApplicationContext();
    }

6、 Picasso还可以调节加载图片的优先顺序

它提供了三种优先级:HIGH,MEDIUM,LOW。默认不设置的话,就是MEDIUM级别。

Picasso.with(imageView.getContext())
                        .load(url)
                        .priority(Picasso.Priority.HIGH)
                        .into(imageView);

Picasso实例

下面我们来看一个实例:


@Override
        public View getView(final int position, View convertView, ViewGroup container) {
            // TODO Auto-generated method stub
            final ViewHolder holder;
            final BannerItem item = getItem(position);
            if (convertView == null) {
                holder = new ViewHolder();
                convertView = LayoutInflater.from(container.getContext()).inflate(R.layout.account_banner, container, false);
                holder.mImageView = (ImageView)convertView.findViewById(R.id.iv_banner);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
            if (mImageLoader != null) {
                mImageLoader.loadImage(holder.mImageView, item.getUrl());//下载图片
            }
            holder.mImageView.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    // TODO Auto-generated method stub
                }
            });
            return convertView;
        }

//下载图片接口
public interface ImageLoader {
        void loadImage(ImageView imageView, String url);
    }

//设置imageview的图片下载器
setImageLoadder(new ImageLoader(){
            @Override
            public void loadImage(ImageView imageView, String url) {
                Picasso.with(imageView.getContext())
                        .load(url)
						.placeholder(R.drawable.photo_holder_72dp)
                        .error(R.drawable.photo_holder_72dp)
                        .centerCrop()
                        .into(imageView);

            }
        });

Picasso 内存

我们可以通过使用以下几点来尽可能的减少内存消耗。

大图不使用memory cache

由于Picasso默认会使用设备的15%的内存作为内存图片缓存,对于比较大的图片,我们可以考虑不将它缓存在内存中,用完就及时回收,需要用的时候再加载,这样可以大大减少内存消耗。

Android中一张图片(BitMap)占用的内存主要和以下几个因数有关:图片长度,图片宽度,单位像素占用的字节数。一张图片(BitMap)占用的内存=图片长度图片宽度单位像素占用的字节数。

虽然Picasso使用了LruCache,但是大图存在内存还是没必要的。
LruCache.java

Picasso.with(imageView.getContext())
                        .load(url)
                        .memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)
                        .into(imageView);

对于大图请求,memoryPolicy内存存储规则,有两种,如上设置了不在内存缓存中查找,也不将最终结果存储于内存。

/** Skips memory cache lookup when processing a request. */
  NO_CACHE(1 << 0),
  /**
   * Skips storing the final result into memory cache. Useful for one-off requests
   * to avoid evicting other bitmaps from the cache.
   */
  NO_STORE(1 << 1);

列表页滑动优化

tag(Object object) 可以接受java任何类型的参数。因而,你可以根据任何逻辑来build your image groups。你有以下选择:

  • 使用pauseTag()pause 请求
  • 使用resumeTag() resume请求
  • 使用cancelTag() cancel请求

基本上,无论你何时需要pause 或者cancel加载一张或多张图片,使用tag,并且调用相应的方法。这听起来可能有点抽象,让我们看个例子。

picasso可以对多个加载请求设置相同的tag,即

Object tag = new Object();
Picasso.with(imageView.getContext())
                        .load(url)
                        .tag(tag)
                        .into(imageView);

例如在RecyclerView滑动时监听,处理不同的表现:

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
                    @Override
                    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                            Picasso.with(context).resumeTag(tag);
                        } else {
                            Picasso.with(context).pauseTag(tag);
                        }
                    }
                });

用户通过快速的下滑列表来查找一条旧的消息。RecyclerView 是快速的回收利用items的。如果你能正确的实现adapter,滑动RecyclerView的时候那将非常流畅。然而,Picasso将会尝试为每一行都开始一个新的请求,但是它又立即取消这个请求。因为用户滑动listview太快了。

当RecyclerView的滑动状态变成fling的时候,他将会取消所有的请求,这将是非常高效的。用户不会注意到任何变化,但是你的app却有效的减少了大量的请求。

当RecyclerView的滑动状态变成fling的时候,他将会取消所有的请求。当滑动状态变成idle或者正常的滑动位置的时候,他重新开始请求。(resume the request)

RecyclableImageView

重写ImageView的onDetachedFromWindow方法,在它从屏幕中消失时回调,去掉drawable引用,能加快内存的回收。

public class RecyclerImageView extends ImageView {
        @Override
        protected void onDetachedFromWindow() {
            super.onDetachedFromWindow();
            setImageDrawable(null);
        }
    }

RGB_565

因为RGB_565呈现结果与ARGB_8888接近,所以对于不透明的图片可以使用RGB_565来优化内存。

Picasso.with(imageView.getContext())
                        .load(url)
                        .config(Bitmap.Config.RGB_565)
                        .into(imageView);

默认情况下,Android使用ARGB_8888

Android中有四种,分别是:
ALPHA_8:每个像素占用1byte内存
ARGB_4444:每个像素占用2byte内存
ARGB_8888:每个像素占用4byte内存
RGB_565:每个像素占用2byte内存

Picasso清缓存

新版本的Picasso已经不支持以下方法清缓存了:

Picasso.with(context).invalidate(imagePath);

但我们可以通过下面的方法来清缓存

1、清除自己应用的cache目录

/**
 * Deletes a directory tree recursively.
 */
public static void deleteDirectoryTree(File fileOrDirectory) {
    if (fileOrDirectory.isDirectory()) {
        for (File child : fileOrDirectory.listFiles()) {
            deleteDirectoryTree(child);
        }
    }

    fileOrDirectory.delete();
}

2、通过自己设置downloader,以便调用它的shutdown方法,达到清除缓存的目的。

public static class PicassoUtil {
        private static Picasso sInstance;
        private static OkHttpDownloader sDownloader;
        public  static Picasso getPicasso(Context context){
            if(sInstance == null) {
                sDownloader = new OkHttpDownloader(context);
                Picasso.Builder builder = new Picasso.Builder(context);
                builder.downloader(sDownloader);
                sInstance = builder.build();
            }
            return sInstance;
        }
        public static void clearCache(){
            if(sDownloader != null){
                sDownloader.shutdown();
            }
        }
    }

3、自己设置Picasso.LruCache

Picasso.Builder builder = new Picasso.Builder(this);
LruCache picassoCache = new LruCache(this);
builder.memoryCache(picassoCache);
Picasso.setSingletonInstance(builder.build());
//需要清除缓存时调用
picassoCache.clear();

4、网友提供的通过自定义的LurCache来管理内存,支持根据bitmap的key删除指定bitmap。

PicassoTools:

public class PicassoTools {
    private static Picasso picasso = null;
    private static CustomLruCache lruCache = null;

    private static CustomLruCache getCache() {
        if (lruCache == null)
            lruCache = new CustomLruCache(MainApp.getAppContext());
        return lruCache;
    }

    public static Picasso getPicasso() {
        if (picasso == null)
            picasso = new Picasso.Builder(MainApp.getAppContext()).memoryCache(getCache()).build();
        return picasso;
    }

    public static void clear(Uri uri) {
        getCache().remove(getKey(uri));
    }

    public static void clearCache() {
        getCache().clear();
        // Picasso.with(MainApp.getAppContext()).cache.clear();
    }

    public static void clearCache(Context c) {
        getCache().clear();
        // Picasso.with(c).cache.clear();
    }

    private static final int KEY_PADDING = 50; // Determined by exact science.

    private static String getKey(Uri uri) {
        return getKey(uri, null, 0, 0, false, false, null);
    }

    private static String getKey(Uri uri, Integer resourceId, int targetWidth, int targetHeight, boolean centerCrop, boolean centerInside, List<Transformation> transformations) {
        StringBuilder builder = new StringBuilder();
        if (uri != null) {
            String path = uri.toString();
            builder.ensureCapacity(path.length() + KEY_PADDING);
            builder.append(path);
        } else {
            builder.ensureCapacity(KEY_PADDING);
            builder.append(resourceId);
        }
        builder.append('\n');

        if (targetWidth != 0) {
            builder.append("resize:").append(targetWidth).append('x').append(targetHeight);
            builder.append('\n');
        }
        if (centerCrop) {
            builder.append("centerCrop\n");
        } else if (centerInside) {
            builder.append("centerInside\n");
        }

        if (transformations != null) {
            // noinspection ForLoopReplaceableByForEach
            for (int i = 0, count = transformations.size(); i < count; i++) {
                builder.append(transformations.get(i).key());
                builder.append('\n');
            }
        }

        return builder.toString();
    }
}

CustomLruCache:

public class CustomLruCache extends LruCache {
    public CustomLruCache(Context context) {
        super(context);
    }

    public CustomLruCache(int value) {
        super(value);
    }

    @Override
    public Bitmap get(String key) {
        L.d(this, key);
        return super.get(key);
    }

    public void remove(String key) {
        try {
            Bitmap value = map.remove(key);

            Field fieldSize = LruCache.class.getDeclaredField("size");
            fieldSize.setAccessible(true);
            Integer size = (Integer) fieldSize.get(this);
            size -= Utils.getBitmapBytes(value);
            fieldSize.set(this, size);

            Field fieldEvictionCount = LruCache.class.getDeclaredField("evictionCount");
            fieldEvictionCount.setAccessible(true);
            Integer evictionCount = (Integer) fieldEvictionCount.get(this);
            evictionCount++;
            fieldEvictionCount.set(this, evictionCount);

        } catch (IllegalArgumentException e) {
            L.e(this, e);
        } catch (IllegalAccessException e) {
            L.e(this, e);
        } catch (NoSuchFieldException e) {
            L.e(this, e);
        }
    }
}

参考

  1. http://blog.csdn.net/u011337574/article/details/51586714
  2. http://www.jianshu.com/p/6b746c904a49
  3. Clear Cache memory of Picasso
  4. JakeWharton的避免OOM建议
  5. Picasso and Context
  6. clear-cache-memory-of-picasso
  7. android-picasso-clear-cache
文章目录
  1. 1. 基本配置
  2. 2. 使用方法
    1. 2.1. Picasso的基本使用
    2. 2.2. Picasso加载网络图片
    3. 2.3. Picasso常用设置
    4. 2.4. Picasso实例
  3. 3. Picasso 内存
    1. 3.1. 大图不使用memory cache
    2. 3.2. 列表页滑动优化
    3. 3.3. RecyclableImageView
    4. 3.4. RGB_565
  4. 4. Picasso清缓存
  5. 5. 参考
|