图片的三级缓存
来源:互联网 发布:矩阵的最大奇异值 编辑:程序博客网 时间: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上找到 –>传送门
- 图片的三级缓存
- 图片的三级缓存
- 图片的三级缓存
- 图片的三级缓存
- 图片的三级缓存
- 图片的三级缓存
- 图片的三级 缓存
- 图片的三级缓存
- 图片的三级缓存
- 图片的三级缓存
- 图片的三级缓存
- 图片的三级缓存
- 图片的三级缓存
- 图片的三级缓存
- 图片的三级缓存
- 图片的三级缓存
- 图片的三级缓存
- 图片的三级缓存
- Quartz官方文档翻译
- 包和导入
- 几种常见的微服务架构方案——ZeroC IceGrid、Spring Cloud、基于消息队列、Docker Swarm
- I2C从器件地址
- Qt 串口类QSerialPort 使用笔记
- 图片的三级缓存
- 代码块与构造函数的认识
- 网站性能优化
- xutils获取数据
- Unity中关于Rigidbody和Collider的问题
- 树状数组求逆序数
- 【bzoj2097】[Usaco2010 Dec]Exercise 奶牛健美操
- Ospf Sham-link 配置
- Cordova 手机App禁止横竖屏转换