最近写了一个图片的三级缓存,当然现在有很多的开源库都有这个功能,比如xUtils3等,那么我们为什么要自己去写呢,第一个是进一步熟悉它的原理,第二个是使用LruCache最近最少算法,LruCache的好处是可以指定你在手机缓存空间使用多大,在缓存的文件大小超出了你指定的大小,系统会自动回收最少使用的对象。LruCache声明的是一个强引用,它是不会被系统回收的。

图片的三级缓存,顾名思义有三级

按照我们使用的优先级分为:

        第一级  手机缓存,优先使用,手机缓存加载速度最快
        第二级 手机本地存储,当没有缓存的时候,使用本地存储,本地存储的调用也快于联网,如果二级有,说明联网加载过了,而一级还没有缓存,在调用二级时,需要缓存到一级, 在下次再次调用时,就直接调用一级了

        第三级 联网,最后的选择是联网加载,当然有一种特殊情况,如果检测手机没有SD卡,就得跳过二级,直接使用三级了,如果调用到三级了,说明一级和二级都没有,那么在三级联网加载过后,需要存储到二级并缓存到一级。在下次调用时就直接一级了。如果在没有网络时,当用户打开之前打开过的界面时,就可以从二级中加载,不必使用网络也可浏览之前加载过的

代码如下,我写的是分为了四个类,一个主类,三个缓存工具类

一、调用方法的主类

/**
 * Created by Star-梦回 on 2016/3/30.
 * 获取一个bitmap对象。设置显示图片
 */
public class BitmapCacheUtils {

    public final Context context;
    private final Handler handler;
    /**
     * 手机缓存工具类
     */
    public MemoryCacheUtils memoryCacheUtils;

    /**
     * 网络获取资源工具类
     */
    public NetCacheUtils netCacheUtils;

    public LocalCacheUtils localCacheUtils;

    public BitmapCacheUtils(Context context, Handler handler) {
        this.context = context;
        this.handler = handler;
        memoryCacheUtils = new MemoryCacheUtils(context);
        localCacheUtils = new LocalCacheUtils(memoryCacheUtils);
        netCacheUtils = new NetCacheUtils(handler, memoryCacheUtils,localCacheUtils);
    }

    /**
     * 根据图片的网络地址获取为内存的bitmap对象
     *
     * @param imageUrl
     * @return
     */
    public Bitmap getBitmap(String imageUrl, int position) {
        //1、内存中获取,最快
        Bitmap bitmap = memoryCacheUtils.getBitmap(imageUrl);
        if (bitmap != null) {
            LogUtil.e("手机缓存文件获取成功");
            return bitmap;
        }
        //2、本地存储中,次之
        bitmap = localCacheUtils.getBitmap(imageUrl);
        if (bitmap != null) {
            LogUtil.e("手机本地存储文件获取成功");
            return bitmap;
        }
        //3、联网获取,最后
        netCacheUtils.getBitmapFromNet(imageUrl, position);
        return null;
    }
}



二、一级缓存工具类:这个类需要特殊说明

 我们在缓存中使用的是LruCache最近最少算法缓存的,具体的可以看方法注释,获取内存有三种,我选择的是当前可用内存,取的是1/8,当前你如果感觉不够用你可以自己更改,除的数变小一点,表示缓存可使用的空间更大一点。

/**
 * Created by Star-梦回 on 2016/3/30.
 * 手机缓存工具类
 */
public class MemoryCacheUtils {

    /**
     * 手机缓存的容器,最近最少算法集合,底部是LinkedHashMap形式存储
     */
    private LruCache<String, Bitmap> lruCache;
    private Context context;

    public MemoryCacheUtils(Context context) {
        this.context = context;
        //获取ActivityManager对象
        ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        //获取手机当前可用内存的大小,单位是Mb
        int memoryClass = manager.getMemoryClass();
        //换算成byte,获取可用空间的1/8作为图片的缓存,在空间不足时,系统会自动回收
        int size = memoryClass * 1024 * 1024 / 8;
        lruCache = new LruCache<String, Bitmap>(size) {
            //之所以说需要换算成byte,是因为,该方法的返回值是byte,两者需要统一
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight();
            }
        };
    }

    /**
     * 保存bitmap对象到手机缓存中
     *
     * @param imageUrl
     * @param bitmap
     */
    public void putBitmap(String imageUrl, Bitmap bitmap) {
        LogUtil.e("保存到手机缓存成功");
        lruCache.put(imageUrl, bitmap);
    }

    /**
     * 获取bitmap对象
     *
     * @param imageUrl
     * @return
     */
    public Bitmap getBitmap(String imageUrl) {
        return lruCache.get(imageUrl);
    }
}

三、二级缓存工具类


/**
 * Created by Star-梦回 on 2016/3/30.
 * 本地存储工具类
 */
public class LocalCacheUtils {
    /**
     * 手机缓存工具类
     */
    private MemoryCacheUtils memoryCacheUtils;

    public LocalCacheUtils(MemoryCacheUtils memoryCacheUtils) {
        this.memoryCacheUtils = memoryCacheUtils;
    }

    /**
     * 获取bitmap文件
     *
     * @param imageUrl
     * @return
     */
    public Bitmap getBitmap(String imageUrl) {
        String fileName = MD5.md5(imageUrl);
        File file = new File(Environment.getExternalStorageDirectory() + "/xinxinnews", fileName);
        try {
            if (file.exists()) {
                FileInputStream fis = new FileInputStream(file);
                Bitmap bitmap = BitmapFactory.decodeStream(fis);
                fis.close();
                //添加到手机缓存中
                memoryCacheUtils.putBitmap(imageUrl, bitmap);
                return bitmap;
            }
        } catch (Exception e) {
            LogUtil.e("本地存储文件获取失败");
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 保存一个bitmap对象到手机本地存储中
     *
     * @param imageUrl
     * @param bitmap
     */
    public void putBitmap(String imageUrl, Bitmap bitmap) {
        //对图片路径进行md5加密,作为文件名,因为这样,长度一样切唯一
        String fileName = MD5.md5(imageUrl);
        File file = new File(Environment.getExternalStorageDirectory() + "/xinxinnews", fileName);
        try {
            File parentFile = file.getParentFile();//获取图片路径
            if (!parentFile.exists()) {//如果该文件路径不存在
                parentFile.mkdirs();//创建
            }
            FileOutputStream fos = new FileOutputStream(file);
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
            LogUtil.e("本地存储文件成功");
            fos.flush();//刷新
            fos.close();//关闭
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

四、三级缓存工具类


/**
 * Created by Star-梦回 on 2016/3/30.
 * 联网下载图片工具类  分线程使用线程池
 */
public class NetCacheUtils {

    /**
     * 成功
     */
    public static final int SUCCESS = 1;

    /**
     * 失败
     */
    public static final int FAIL = 2;

    private Handler handler;

    /**
     * 手机缓存工具类
     */
    private MemoryCacheUtils memoryCacheUtils;

    /**
     * 手机本地存储
     */
    private LocalCacheUtils localCacheUtils;

    /**
     * 线程池接口
     */
    private ExecutorService service;

    public NetCacheUtils(Handler handler, MemoryCacheUtils memoryCacheUtils, LocalCacheUtils localCacheUtils) {
        this.handler = handler;
        this.memoryCacheUtils = memoryCacheUtils;
        this.localCacheUtils = localCacheUtils;
        service = Executors.newFixedThreadPool(10);
    }

    /**
     * 线程池的使用
     */
    class MyRunnable implements Runnable {
        private String imageUrl;
        private int position;

        public MyRunnable(String imageUrl, int position) {
            this.position = position;
            this.imageUrl = imageUrl;
        }

        @Override
        public void run() {
            Bitmap bitmap = null;
            InputStream inputStream = null;
            try {
                URL url = new URL(imageUrl);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");//默认就是get,参数必须大写
                conn.setReadTimeout(10000);
                conn.setConnectTimeout(10000);
                conn.connect();
                int responseCode = conn.getResponseCode();
                if (responseCode == 200) {
                    inputStream = conn.getInputStream();
                    bitmap = BitmapFactory.decodeStream(inputStream);
                    Message msg = new Message();
                    msg.what = SUCCESS;
                    msg.obj = bitmap;
                    msg.arg1 = position;//显示图片的位置
                    handler.sendMessage(msg);
                    LogUtil.e("网络获取图片资源成功" + position);
                    //资源获取成功后,加入到一级和二级缓存中
                    //1、缓存到手机缓存中
                    memoryCacheUtils.putBitmap(imageUrl, bitmap);
                    //2、缓存到手机本地存储中
                    localCacheUtils.putBitmap(imageUrl, bitmap);
                }
                //关闭
                conn.disconnect();
            } catch (Exception e) {
                //如果失败了
                handler.sendEmptyMessage(FAIL);
                LogUtil.e("网络获取图片资源失败");
                e.printStackTrace();
            } finally {
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * 根据 图片的网络地址,获取bitmap对象
     *
     * @param imageUrl
     * @param position
     * @return
     */
    public void getBitmapFromNet(final String imageUrl, final int position) {
        service.execute(new MyRunnable(imageUrl, position));
    }

}



那么在第三级联网时,使用到了一个Hanlder, 是因为这是在分线程联网加载的,一级和二级没有执行分线程,所以可以直接返回Bitmap对象,但是三级是分线程不能直接返回对象,这时候我们就需要用到Hanlder,那么又有一个问题,那联网后怎么知道这个Bitmap对象是要装配到ListView中的哪个Item上呢,这是时候我们就需要用到Item的position了,在联网成功后,我们都做什么了,看代码

Message msg = Message.obtain();
msg.what = SUCCESS;
msg.obj = bitmap;
msg.arg1 = position;//显示图片的位置
handler.sendMessage(msg);

把这个position再传回去,在主线程处理消息时,我们就可以根据这个位置去设置图片的显示位置了

private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case NetCacheUtils.SUCCESS://成功
                    Bitmap bitmap = (Bitmap) msg.obj;
                    int position = msg.arg1;
                    ImageView image = (ImageView) listView.findViewWithTag(position);//注意这行代码
                    image.setImageBitmap(bitmap);
                    break;
                case NetCacheUtils.FAIL://失败
                    break;
            }
        }
    };

如果要执行这一行代码

listView.findViewWithTag(position)

还必须要在adapter中的getView()中给每一个item的图片设置Tag

myViewHolder.iv_item.setTag(position);

这样就搞定了,最后就是在设置图片的时候调用主类的getBitmap()方法了,该方法有两个参数,一个是图片的url,一个是position

另外再说明下,一级和二级是在调用方法时,直接返回的Bitmap对象,三级并没有返回Bitmap,而是通过Hanlder发送回来的,三级的图片设置是在hanlder处理消失中进行装配的。

完了!谢谢大家!