图片的三级缓存

来源:互联网 发布:矩阵的最大奇异值 编辑:程序博客网 时间:2024/05/20 09:08

介绍

本文介绍了三级缓存的原理及操作,并且用策略模式的架子框起来。


1.图片的三级缓存是什么

图片的三级缓存顾名思义就是指三级图片的缓存技术,哦,不对,我在说什么。图片的三级缓存顾名思义就是指在图片加载过程中使用缓存技术,在加载网上的图片时,第一次加载时下载图片然后显示,然后会把图片进行缓存操作,之后如果再次使用这个图片时就不用再次下载了,这样子的话可以节约系统资源,也可以节省用户流量。

那么三级缓存是哪三级呢?

  • 网络缓存:这一步其实就是利用网络去下载图片的操作,严格说不能算“缓存操作”,但是为了名称的统一,大家就叫它网络缓存。
  • 本地缓存:就是说将图片保存在本地SD卡上,使用时直接从SD卡取出就好了。
  • 内存缓存:最麻烦的一步,就是将图片保存在内存中,要知道内存缓存是最快的,但是会占用用户内存,所以需要适当的释放内存,后文有具体描述。

2.流程图来一波

图片从百度拿来的

流程图

我觉得这张图不错,就直接拿来用了,图很好理解,优先从内存中加载,其次是SD卡,内存是最快的,最后才是从网络下载。记得获取到图片以后要相应的存储在缓存中。


3.上代码

代码使用了策略模式封装,如果不了解策略模式的可以去看看本人另一篇关于策略模式的博客。

缓存的抽象父类:

//缓存的抽象类public abstract class Cache {    abstract Bitmap get(String url) throws IOException;    abstract void put(String url,Bitmap bitmap);}

所有的缓存类应当有put和get方法,以便存和取。

文件缓存:

public class FileCache extends Cache {    //文件目录    private static final String LOCAL_CACHE_PATH = Environment            .getExternalStorageDirectory().getAbsolutePath() + "/Cache";    private static final String TAG = "ImageDown";    @Override    Bitmap get(String url) {        try {            File cacheFile = new File(LOCAL_CACHE_PATH, MD5Encoder.encode(url));            if (cacheFile.exists()) {                Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(                        cacheFile));                Log.e(TAG, "get: 图片来源文件");                return bitmap;            }        } catch (Exception e) {            e.printStackTrace();        }        return null;    }    @Override    void put(String url, Bitmap bitmap) {        File dir = new File(LOCAL_CACHE_PATH);        if (!dir.exists() || !dir.isDirectory()) {            boolean b = dir.mkdirs();// 创建文件夹            if (!b){                Log.e(TAG, "put: 创建文件夹失败");            }        }        try {            String fileName = MD5Encoder.encode(url);            File cacheFile = new File(dir, fileName);            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, new FileOutputStream(                    cacheFile));// 参1:图片格式;参2:压缩比例0-100; 参3:输出流        } catch (Exception e) {            e.printStackTrace();        }    }}

这就是一些文件的读写操作,需要注意几点
如果创建文件夹失败,可能是6.0权限的问题。
这里存的名字不是直接以url为名字,而是以url计算的MD5值来作为文件名的,因为有些url不能直接作为文件名

补充一下MD5的工具类:

public class MD5Encoder {    public static String encode(String string) throws Exception {        byte[] hash = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8"));        StringBuilder hex = new StringBuilder(hash.length * 2);        for (byte b : hash) {            if ((b & 0xFF) < 0x10) {                hex.append("0");            }            hex.append(Integer.toHexString(b & 0xFF));        }        return hex.toString();    }}

内存缓存:

先上代码:

public class MemoryCache extends Cache {    private static final String TAG = "ImageDown";    public MemoryCache() {        this.memortCache = new LruCache<String,Bitmap>((int) (Runtime.getRuntime().maxMemory()/1024/40)){            // 返回每个对象的大小            @Override            protected int sizeOf(String key, Bitmap value) {                return value.getRowBytes()*value.getHeight()/1024;            }        };    }    private LruCache<String,Bitmap> memortCache ;    @Override    Bitmap get(String url) {        Bitmap bitmap = memortCache.get(url);        if (bitmap != null){            Log.e(TAG, "get: 图片来源内存" );            return bitmap;        }else {            return null;        }    }    @Override    void put(String url, Bitmap bitmap) {        memortCache.put(url,bitmap);    }}

解释几个东西:

java的四种引用:、

  • 默认强引用, A a = new A(),当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。所以用完以后记得a = null;
  • 软引用SoftReference, 垃圾回收器会考虑回收, 如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。
    SoftReference<String> str=new SoftReference<String>("xxx");
  • 弱引用 WeakReference, 垃圾回收器更会考虑回收,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
    WeakReference<String> str= new WeakReference<String>("xxx");
  • 虚引用PhantomReference, 垃圾回收器最优先回收,就是形同虚设,在任何时候都可能被垃圾回收器回收。

由于内存缓存把图片放入内存中,倘若所有所有图片都强引用,图片越来越多,内存就爆炸了,所以以前很多都是把图片bitmap用成弱引用,然后放进hashmap中,这样可以适当的回收,后来出现了新东西LruCache,就不这么做了。

关于LruCache:

这个是谷歌提供的工具类,采用了一种叫最近最少使用算法,这个LruCache封装了一个LinkedHashMap,这里面放的引言它会根据情况,如果占用内存超出了预设值,就优先释放最近最少使用的引用。
初始化LruCache:
在构造方法里设置LruCache占用的内存应该多大,这里取的是虚拟机分配给本app的内存的1/4

        this.memortCache = new LruCache<String,Bitmap>((int) (Runtime.getRuntime().maxMemory()/1024/4)){            // 返回每个对象的大小            @Override            protected int sizeOf(String key, Bitmap value) {                return value.getRowBytes()*value.getHeight()/1024;            }        };

ImageUtils:

public class ImageUtils {    private ImageView imageView;    private Handler handler = new Handler() {        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);            if (msg.what == GOT_BITMAP) {                imageView.setImageBitmap((Bitmap) msg.obj);                Log.e(TAG, "handleMessage: 图片来源网络");            }        }    };    private static final int GOT_BITMAP = 0;    private static final String TAG = "ImageDown";    Cache mCache = new MemoryCache();    //创建一个线程池,线程数量为cpu的核心数    private ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());    //注入缓存类型    public void setImageCache(Cache imageCache) {        mCache = imageCache;    }    public void ShowPic(String url, ImageView imageView) throws IOException {        this.imageView = imageView;        Bitmap bitmap = mCache.get(url);        if (bitmap != null) {            imageView.setImageBitmap(bitmap);            return;        }        //没有缓存        LoadPic(url, imageView);    }    private void LoadPic(final String url, final ImageView imageView) {        executorService.submit(new Runnable() {            @Override            public void run() {                Bitmap bitmap = DownLoadImage(url);                Message message = handler.obtainMessage();                message.what = GOT_BITMAP;                message.obj = bitmap;                handler.sendMessage(message);                mCache.put(url, bitmap);            }        });    }    private Bitmap DownLoadImage(String url) {        Bitmap bitmap = null;        OkHttpClient okHttpClient = new OkHttpClient();        Request request = new Request.Builder().get().url(url).build();        Response response = null;        try {            response = okHttpClient.newCall(request).execute();            if (response.isSuccessful()) {                byte[] bytes = response.body().bytes();                bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);                Log.e(TAG, "DownandSet: decodeByteArray");            } else {                Log.e(TAG, "DownLoadImage: 从网络下载图片失败--" + response.message());            }            if (bitmap == null) {                Log.e(TAG, "DownandSet: bitmap==null");            }        } catch (IOException e) {            e.printStackTrace();        }        return bitmap;    }}

因为网络缓存是备用方案,所以放进这个类里面实现了。
在showPic方法中有缓存就使用缓存,没缓存就loadPic下载图片。

使用者:

先看一下xml布局:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="st.zlei.com.imageload.MainActivity"    android:orientation="vertical">    <ImageView        android:id="@+id/image"        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_weight="1"/>    <Button        android:id="@+id/down_button"        android:layout_height="wrap_content"        android:layout_width="match_parent" />    <Button        android:text="清空图片"        android:id="@+id/dclear_button"        android:layout_height="wrap_content"        android:layout_width="match_parent" /></LinearLayout>

布局长这样:
布局

具体调用:

protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        down_button = (Button) findViewById(R.id.down_button);        clear_button = (Button) findViewById(R.id.dclear_button);        image = (ImageView) findViewById(R.id.image);        imageUtils = new ImageUtils();        //这里传入使用的缓存类型        imageUtils.setImageCache(new MemoryCache());        down_button.setText("显示图片");        down_button.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                try {                    imageUtils.ShowPic(imageURL, image);                } catch (IOException e) {                    e.printStackTrace();                }            }        });        clear_button.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                image.setImageBitmap(null);            }        });    }

看一下运行结果

首先使用文件缓存:

采用文件缓存策略

首先点击显示图片,然后清空图片,再次点击显示图片:
打印结果
可以看到第一次加载是从网络下载的图片,第二次加载就直接从文件中获取图片了。

在手机内存卡上多了一个cache文件夹,里面存放的就是缓存的图片文件:
缓存文件夹

缓存的图片文件

使用内存缓存

采用内存缓存

打印结果


综合使用三级缓存

上面都只是单独使用的文件缓存或者内存缓存,现在我们综合一下,优先使用内存缓存,如果没有就使用文件缓存,都没有的话才去网络下载图片。

OverAllCache 缓存类,在调用的时候注入这个类即可

public class OverAllCache extends Cache {    private static final String TAG = "ImageDown";    MemoryCache memoryCache = new MemoryCache();    FileCache fileCache = new FileCache();    @Override    Bitmap get(String url) throws IOException {        Bitmap bitmap;        bitmap = memoryCache.get(url);        if (bitmap == null){            bitmap = fileCache.get(url);            if (bitmap != null){                memoryCache.put(url,bitmap);            }        }        return bitmap;    }    @Override    void put(String url, Bitmap bitmap) {        memoryCache.put(url,bitmap);        fileCache.put(url,bitmap);    }}

看看运行结果

调用

打印结果

由于之前加载过图片,SD卡上已经有缓存了,此时第一次加载就从文件中获取,之后每次调用都是从内存中获取了。


扩展缓存类型

这部分就是策略模式的架子带来的好处了,当以后想要自己定义怎么缓存,就可以这样调用了:
扩展缓存

这里直接传父类Cache,则必须重写get,put方法,于是用户就可以在这里自定义get,put的内容了。


总结

到这里图片的三级缓存就讲完了,一口气写了一天,一顿操作猛如虎,但是我还是得说,当我们真的需要图片加载框架的时候还是Picasso或者Glide什么的更靠谱,本文的内容只是让我们理解三级缓存的知识,跟Picasso什么的相比还是图样图森破。

另外本文所使用代码可在本人github上找到 –>传送门

原创粉丝点击