Picasso的缓存理解及简单的封装

来源:互联网 发布:剑网3秀萝捏脸数据图片 编辑:程序博客网 时间:2024/06/08 08:25

Picasso的缓存理解及简单的封装

想必大家对Picasso的使用已经比较熟悉了,在此处我就不多说了,不熟悉的小伙伴可以看看Picasso官网。然后简单说说项目的依赖。

  • Picasso的缓存理解及简单的封装
    • 依赖
        • 权限
    • 缓存
        • LruCache
        • DiskLruCache
        • Picasso中的缓存操作
        • 缓存清除
    • 封装
        • 接口部分
        • 接口实现
        • 转换车间
        • 函数调用
    • Demo下载

依赖

一般情况下都是通过 GRADLE 进行依赖会方便很多

compile 'com.squareup.picasso:picasso:2.5.2' 进行依赖

但值得注意的是如果说想使用OkHttpDownloader来进行自定义缓存路径或者其他操作,则需引入okhttp的库,但不能使用最新版的okhttp3,因为包名发生了变化,加载的时候会出错。所以可以引入下面的版本。当然也可以使用okhttp3重写。

compile 'com.squareup.okhttp:okhttp:2.6.0'

权限

<uses-permission android:name="android.permission.INTERNET"></uses-permission><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>

缓存

大家最关心的可能就是Picasso的缓存机制,如何进行缓存路径更改和大小设置。

首先Picasso的缓存机制是LruCache和DiskLruCache的组合,也就是memory and disk caching。

Picasso的图片加载优先级是 memory > disk > network,可以设置setIndicatorsEnabled(true)来判断图片是通过什么渠道加载的,可用以调试缓存清理的API。readfrom

LruCache

LruCache的主要核心实现是LinkHashMap,LinkHashMap是一个能按顺序储存的链表,其中有一个叫Lru的算法排序,能将最近添加或使用的文件放在表头,而很少用的文件放在表尾,当缓存的文件达到设置的阈值,则将表尾不常用的缓存文件删除。因此大多数情况下可以不用删除缓存,除非需要更新某个图片列如更换头像之类的。

LruCache将缓存放于Memory中,因此当该应用进程结束的时候会自动被释放,为了数据的持久化,加快缓存读取因此还有个DiskLruCache。

DiskLruCache

和LruCache类似,只不过是将缓存文件放到了Disk上,当一个图片被缓存下来了之后,缓存目录下回看到3个文件,分别是xxxxxxxx.0,xxxxxxxx.1,journal,对应的目标文件是网络请求头文件,缓存文件,日志。

journal在DiskLruCache还是扮演了比较重要的作用,它保存了所有对缓存操作的记录包括数据是否成功写入,读取记录,及缓存文件的大小。同样当缓存文件超出了设定范围,DiskLruCache也会删除比较旧的数据,以及journal中旧的操作记录。

由于网上关于LruCache和DiskLruCache源码的解析很多,所以这里我只是简单的提一下它们的作用。

Picasso中的缓存操作

使用过Picasso的小伙伴应该知道Picasso暴露在外层对缓存处理的API,没办法对disk上的缓存文件产生作用,只是将LruCache中的缓存进行删除操作,一般用于图片的更新。在下面我将介绍自定义缓存及删除disk上缓存的方法。

自定义缓存路径

自定义缓存路径最常规的办法就是不让Picasso使用默认的Downloder,OkHttpDownloader中封装了可以更改缓存路径和缓存区大小的而设置,我们可以先看看默认的OkHttpDownloader的缓存位置。

static Downloader createDefaultDownloader(Context context) {  try {    Class.forName("com.squareup.okhttp.OkHttpClient");    return OkHttpLoaderCreator.create(context);  } catch (ClassNotFoundException ignored) {  }  return new UrlConnectionDownloader(context);}

根据是否有com.squareup.okhttp.OkHttpClient这个包,来判断是否使用OkHttpDownloader,否则使用UrlConnectionDownloader。OkHttpLoaderCreator.create(context)这个方法调用的是OkHttpDownloader的默认构造器,只传入Context,其他数据默认。

/** * 默认调用的构造器,依次调用下方的其他构造器 */public OkHttpDownloader(final Context context) {  this(Utils.createDefaultCacheDir(context));}/**  * @param cacheDir 缓存指定的路径,File类型的参数 */public OkHttpDownloader(final File cacheDir) {  this(cacheDir, Utils.calculateDiskCacheSize(cacheDir));}/**  * 最终调用的构造函数,同时实例化默认的OkHttpClient  * @param 缓存指定的路径,File类型的参数  * @param 允许缓存的最大值  */ public OkHttpDownloader(final File cacheDir, final long maxSize) {    this(defaultOkHttpClient());    try {      client.setCache(new com.squareup.okhttp.Cache(cacheDir, maxSize));    } catch (IOException ignored) {    }  }

通过上面的构造方法可以看出,默认的缓存路径和缓存空间的最大值是由picasso包下的Utils执行创建的。

默认的缓存路径

static File createDefaultCacheDir(Context context) {//该路径的位置在 /data/data/packagename/cache/picasso-cache,也就是手机自身内存中。  File cache = new File(context.getApplicationContext().getCacheDir(), PICASSO_CACHE);  if (!cache.exists()) {    //noinspection ResultOfMethodCallIgnored    cache.mkdirs();  }  return cache;}

默认的缓存空间大小

static long calculateDiskCacheSize(File dir) {  long size = MIN_DISK_CACHE_SIZE;  try {    StatFs statFs = new StatFs(dir.getAbsolutePath());    long available = ((long) statFs.getBlockCount()) * statFs.getBlockSize();    // 空间总大小的2%    size = available / 50;  } catch (IllegalArgumentException ignored) {  }  //返回值得范围在MIN_DISK_CACHE_SIZE和MAX_DISK_CACHE_SIZE之间,也就是说大于5MB小于50MB的值有效。  return Math.max(Math.min(size, MAX_DISK_CACHE_SIZE), MIN_DISK_CACHE_SIZE);}

大家可以去翻翻源码Utils中还有很多方法值得取出来在日后使用。

ok说说正经的,自定义缓存的路径从上方的构造方法就可以看出,只要重新实例化一个OkHttpDownloader,使用第二个或者第三个构造器即可。具体的代码如下

Picasso singeltonPicasso = new Picasso.Builder(context)        //重新定义下载器,指定缓存路径        .downloader(new OkHttpDownloader(cacheFile))        //log日志开启        .loggingEnabled(true)        .build();        //设置到全局的Picasso中 Picasso.setSingletonInstance(singeltonPicasso);

这样即可在指定的路径下创建缓存区,一般来说缓存位置都放在SD卡中,避免占用手机自身内存,不过现在手机大多数是手机一体化的,因此需要做一些判断,在我的Demo中会有相应的代码。

缓存清除

Picasso只提供的对LruCache清除的方法,没有对DiskLruCache清除的方法,其实也没有必要将起删除,因为达到阈值会自动删除最旧的数据。但为了一些业务需求,以及缓存空间设置过大的情况,还是可以考虑将其删除,我这里有两种方式。

1.直接删除的方式:

通过保存的缓存路径,遍历该路径下的所有文件,将其删除,可以参考我的demo。这种方法较笨,但也比较直观。

2.利用OkHttpDownloader:

喜欢翻源码的朋友可以发现,其实OkHttpClient中的Cache类封装了对DiskLruCache的操作,包括删除所有,计算缓存大小。

注意一定要将Downloader传入Picasso中,如上面自定义缓存路径的做法否则无法生效。

使用OkHttpDownloader该构造方法

public OkHttpDownloader(OkHttpClient client) {  this.client = client;}

该构造方法可以自定义一个OkHttpClient,然后设置它的Cache就可以达到预期的效果,在外部将自定义的OkHttpClient或者Cache保存下来,调用Cache实现的方法即可。

//实例化一个OkHttpClient为其设置自定义的缓存OkHttpClient okHttpClient = new OkHttpClient();okHttpClient.setCache(new Cache(new File(getExternalCacheDir(),"my-cache"),50*1024*1024));//实例化OkHttpDownloaderOkHttpDownloader downloader = new OkHttpDownloader(okHttpClient);//将downloader构建进Picasso中//-----------------------------------------------------------------------------------------------//获取缓存操作的对象Cache cache = okHttpClient.getCache();try {//删除所以缓存目录下的文件    cache.delete();} catch (IOException e) {    e.printStackTrace();}

再或者重写OkHttpDownloader,调用父类的getClient(),在子类中将OkHttpClient的Cache对象返回。

protected final OkHttpClient getClient() {return client;}
public class MyDownloader extends OkHttpDownloader {    public MyDownloader(Context context) {        super(context);    }    /**     * 获取Cache对象     * @return Cache     */   public Cache getCache(){       return getClient().getCache();   }}

再或者那么就是java的反射机制啦,这就不多说了,大家有兴趣可以自己尝试。能够将OkHttpClient中的Cache拿到操作disk中的缓存自然就不是问题了。顺便提一下如果想获取Picasso图片加载的进度也可以通过重新DownLoader进行实现。

封装

Picasso自身本来已经就是全局的单例模式了,通过Picasso.with(contenxt)即可拿到对象,操作非常的方便,所以封装也没有多大的意思,就是将常用的功能模块化,让代码的可读性更高一些。

下面就是我简单的封装了一些业务常用的功能。

接口部分

package com.pmlee.picassodemo.presenter;import android.widget.ImageView;import com.squareup.picasso.Callback;import com.squareup.picasso.RequestCreator;/** * Created by liyunshuang on 2016/11/18. * <p> * Email 522940943@qq.com or liyunshuang21@gmail.com */public interface INetworkImageLoadPresenter {    /**     * 步加载图片     * 加载默认原图片大小     * @param iv     * @param url     */    void loadImage(ImageView iv, String url);    /**     * 步加载图片     * 自定义Picasso请求参数     * @param iv     * @param requestCreator     */    void loadImage(ImageView iv, RequestCreator requestCreator);    /**     * 异步加载图片     *     * @param iv 目标ImageView     * @param url 图片URL地址     * @param height 指定高度     * @param width 指定宽度  单位 px     *     *              缩放方式为CenterCrop     */    void loadImage(ImageView iv, String url, int width, int height);    /**     * 带有监听回调的 图片加载     * com.squareup.picasso.callback     * @param iv     * @param url     * @param width     * @param height     * @param callback     */    void loadImage(ImageView iv, String url, int width, int height, Callback callback);    void loadImage(ImageView iv, String url, boolean showError);    /**     * 加载并处理为圆形图片     * @param iv     * @param url     */    void loadCircleImage(ImageView iv, String url);    /**     * 计算缓存大小     * @return 带单位的数值 最小单位为 bit 最大为 GB     */    String calculateCacheSize();    /**     * 清除所有的缓存     */    void cleanCacheAll();    /**     * 删除请求过的缓存     * 针对于头像更新等操作     * @param requestedUrl     */    void cleanCache(String requestedUrl);    /**     * 获取Picasso请求构建器     * @param url     * @return     */    RequestCreator getRequestCreator(String url);}

接口实现

其中缓存计算和删除都是使用工具类的最笨方法,大家可以尝试我上述通过重新Downloader之类的方法较好。利用okhttp可以扩展很多的功能。

package com.pmlee.picassodemo.presenter;import android.content.Context;import android.support.annotation.NonNull;import android.text.TextUtils;import android.widget.ImageView;import com.pmlee.picassodemo.PicassoInfo;import com.pmlee.picassodemo.util.CircleTransform;import com.pmlee.picassodemo.util.FileUtils;import com.squareup.okhttp.OkHttpClient;import com.squareup.picasso.Callback;import com.squareup.picasso.NetworkPolicy;import com.squareup.picasso.Picasso;import com.squareup.picasso.RequestCreator;import java.io.File;/** * Created by liyunshuang on 2016/11/18. * <p> * Email 522940943@qq.com or liyunshuang21@gmail.com */public class NetworkImageLoadPresenter implements INetworkImageLoadPresenter {    //测试标签    private static final String TAG = "NetworkImageLoad";    private static Context mContext;    //缓存信息    private Picasso mSingletonPicasso;    private PicassoInfo mPicassoInfo;    private static NetworkImageLoadPresenter customLoader;    /**     * 传入 自定义Picasso的构造器     *     * @param context      Context     * @param mPicassoInfo PicassoInfo     */    private NetworkImageLoadPresenter(Context context, PicassoInfo mPicassoInfo) {        mContext = context;        this.mPicassoInfo = mPicassoInfo;        this.mSingletonPicasso = mPicassoInfo.mPicasso;        Picasso.setSingletonInstance(mSingletonPicasso);    }    /**     * 默认数据加载     */    private static class SingletonImageLoadHolder {        //获取默认的Picasso参数        public static PicassoInfo info = PicassoInfo.getDefaultInfo(mContext);        //实例化NetworkImageLoadPresenter类        public static NetworkImageLoadPresenter loader = new NetworkImageLoadPresenter(mContext, info);    }    /**     * 构建NetworkImageLoad实例     *     * @param context     * @return NetworkImageLoad     */    public static NetworkImageLoadPresenter create(Context context) {        if (context != null)            mContext = context;        else return null;        return SingletonImageLoadHolder.loader;    }    /**     * 构建NetworkImageLoad实例     *     * @param context     * @param picassoInfo 自定义PicassoInfo     * @return NetworkImageLoad     */    public static NetworkImageLoadPresenter create(Context context, PicassoInfo picassoInfo) {        if (customLoader == null)            customLoader = new NetworkImageLoadPresenter(context, picassoInfo);        return customLoader;    }    @Override    public void loadImage(@NonNull ImageView iv, String url) {        if (TextUtils.isEmpty(url)) {            return;        }        getRequestCreator(url)                .into(iv);    }    @Override    public void loadImage(@NonNull ImageView iv, @NonNull RequestCreator requestCreator) {        requestCreator.into(iv);    }    @Override    public void loadImage(@NonNull ImageView iv, String url, int width, int height) {        if (TextUtils.isEmpty(url)) {            return;        }        RequestCreator requestCreator = getRequestCreator(url);        requestCreator.resize(width, height)                .centerCrop()                .into(iv);    }    @Override    public void loadImage(@NonNull ImageView iv, String url, int width, int height, Callback callback) {        if (TextUtils.isEmpty(url)) {            return;        }        RequestCreator requestCreator = getRequestCreator(url);        if (callback != null)            requestCreator.fetch(callback);        requestCreator.resize(width, height)                .centerCrop()                .into(iv);    }    @Override    public void loadImage(ImageView iv, String url, boolean showError) {        if (TextUtils.isEmpty(url)) {            return;        }        RequestCreator requestCreator = getRequestCreator(url);        if (showError) {//            requestCreator.error(mContext.getResources().getDrawable(R.mipmap.photo_fault));        }        requestCreator.networkPolicy(NetworkPolicy.NO_CACHE, NetworkPolicy.NO_STORE).into(iv);    }    @Override    public void loadCircleImage(ImageView iv, String url) {        if (TextUtils.isEmpty(url)) {//            iv.setImageResource(R.mipmap.ic_launcher);            return;        }        RequestCreator requestCreator = getRequestCreator(url);        requestCreator.networkPolicy(NetworkPolicy.NO_CACHE, NetworkPolicy.NO_STORE).transform(                new CircleTransform())                .into(iv);    }    @Override    public RequestCreator getRequestCreator(String url) {        return mSingletonPicasso.load(url);    }    @Override    public String calculateCacheSize() {        long total = FileUtils.getTotalSizeOfFilesInDir(mPicassoInfo.mCacheFile);        return total >= 1024 && total < (1024 * 1024) ? (total >> 10) + " KB" :                total >= (1024 * 1024) && total < (1024 * 1024 * 1024) ? (total >> 20) + " MB" :                        total >= (1024 * 1024 * 1024) ? (total >> 30) + " GB" :                                total + "B";    }    @Override    public void cleanCacheAll() {        if (mPicassoInfo.mCacheFile.exists() && mPicassoInfo.mCacheFile.isDirectory()) {            for (File target : mPicassoInfo.mCacheFile.listFiles()) {                target.delete();            }        }    }    @Override    public void cleanCache(String requestedUrl) {        if (mSingletonPicasso != null)            mSingletonPicasso.invalidate(requestedUrl);    }    /**     * 将Picasso进行返回 用于参数设置     * @return     */    public Picasso getPicasso(){        return mSingletonPicasso==null?Picasso.with(mContext):mSingletonPicasso;    }}

然后就是用于信息同步的及Picasso构造的PicassoInfo类

package com.pmlee.picassodemo;import android.content.Context;import com.pmlee.picassodemo.util.FileUtils;import com.squareup.picasso.Downloader;import com.squareup.picasso.OkHttpDownloader;import com.squareup.picasso.Picasso;import java.io.File;/** * Created by liyunshuang on 2017/4/18. * <p> * Email 522940943@qq.com or liyunshuang21@gmail.com * * 该类用于保存创建的Picasso和部分设置,好同步NetworkImageLoadPresenter中的删除和查询功能 */public class PicassoInfo {    //缓存名    private static final String MY_CACHE_NAME = "my-image-cache";    private static final String DEFAULT_CACHE = "picasso-cache";    //自定义Picasso    public Picasso mPicasso;    //下载器中缓存的文件    public File mCacheFile;    //自定义下载器    public Downloader mDownloader;    private PicassoInfo(Picasso mPicasso, File cacheFile, Downloader downloader) {        this.mPicasso = mPicasso;        this.mCacheFile = cacheFile;        this.mDownloader = downloader;    }    /**     * 构建默认的 PicassoInfo 包含默认缓存路径     *     * @param context     * @return PicassoInfo     */    public static PicassoInfo getDefaultInfo(Context context) {        //定义缓存路径        File cacheFile = new File(FileUtils.getCacheDir(context), MY_CACHE_NAME);        //内置下载器        OkHttpDownloader downloader = new OkHttpDownloader(cacheFile);        Picasso singeltonPicasso = new Picasso.Builder(context)                //重新定义下载器,指定缓存路径                .downloader(downloader)                //log日志开启                .loggingEnabled(true)                .build();        return new PicassoInfo(singeltonPicasso, cacheFile, downloader);    }    /**     * 传入自定义的Picasso相关设置     * 包括缓存的位置 自定义下载器 监听器     *     * @param context     * @param downloader     * @param cacheFile 该缓存路径为Downloader中所设置的,如果Downloader为null则该cacheFile无效,还是     *                  为Picasso默认的路径,也会导致NetworkImageLoadPresenter中清理缓存和查询缓存失效.     * @param listener     * @return     */    public static PicassoInfo createPicassoInfo(Context context, Downloader downloader,                                                File cacheFile, Picasso.Listener listener) {        Picasso.Builder builder = new Picasso.Builder(context);        if (listener != null)            builder.listener(listener);        if (downloader != null)            builder.downloader(downloader);        if (cacheFile == null||downloader== null) {            cacheFile = new File(context.getCacheDir(), DEFAULT_CACHE);        }        return new PicassoInfo(builder.build(), cacheFile, downloader);    }}

如果开启了Picasso的log可以在logcat增加名为“Picasso”进行信息过滤,就可以看到执行的信息。

转换车间

也就是Picasso中的Transformation,可以将获取到得图片按自己的需求进行转换,在Demo中我只用了圆形图片的转换,因为很多头像都需要切成圆形的。

package com.pmlee.picassodemo.util;import android.graphics.Bitmap;import android.graphics.BitmapShader;import android.graphics.Canvas;import android.graphics.Paint;import com.squareup.picasso.Transformation;/** * Created by liyunshuang on 2016/12/1. * <p> * Email 522940943@qq.com or liyunshuang21@gmail.com */public class CircleTransform 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 squaredBitmap = Bitmap.createBitmap(source, x, y, size, size);        if (squaredBitmap != source) {            source.recycle();        }        Bitmap bitmap = Bitmap.createBitmap(size, size, source.getConfig());        //利用BitmapShader绘制圆角及圆形图片        Canvas canvas = new Canvas(bitmap);        Paint paint = new Paint();        BitmapShader shader = new BitmapShader(squaredBitmap,                BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP);        paint.setShader(shader);        //抗锯齿        paint.setAntiAlias(true);        float r = size / 2f;        canvas.drawCircle(r, r, r, paint);        squaredBitmap.recycle();        return bitmap;    }    @Override    public String key() {        return "toCircle";    }}

函数调用

实例化

//默认加载//imageLoader = NetworkImageLoadPresenter.create(this);//自定义NetworkImageLoadPresenterFile cacheFile = new File(getExternalCacheDir(),"cache-test");imageLoader = NetworkImageLoadPresenter.create(this,PicassoInfo.createPicassoInfo(this,        new OkHttpDownloader(cacheFile),cacheFile,null));

函数方法都是一步调用,具体的演示可以参考Demo。

代码都非常的简单,如果有什么问题或者更好的使用方法可以发邮箱给我相互学习。

Demo下载

demo下载

邮箱:liyunshuang21@gmail.com

1 0
原创粉丝点击