介绍
从流程逻辑上来说,三级分为:
1.内存缓存,优先加载的缓存,读取速度最快
2.本地缓存,次优先加载的缓存,存在文件中,读取本地文件
3.网络缓存,最后才加载的缓存,不优先加载,需要联网,消耗流量,当本地缓存和内存都没有的情况下再加载,加载速度慢,网络访问后需下载到本地。
原理
首次加载图片时,要通过网络交互来获取图片,可用任意一种网络请求框架下载该图,之后我们可以将图片保存至本地SD卡和内存中
之后运行软件,优先访问内存中的图片缓存,若内存中没有,则加载本地SD卡中的图片,本地SD卡再存储到内存中,内存有的话直接读取该图片
只在初次访问新内容时,才通过网络获取图片资源,其他情况下取内存以及本地缓存,顺序的优先级为内存>本地存储>网络存储
实现代码例子(参考https://www.jianshu.com/p/2cd59a79ed4a)感谢作者wanbo_
1. 自定义的图片缓存工具类(MyBitmapUtils)
通过new MyBitmapUtils().display(ImageView ivPic, String url) 提供给外部方法进行图片缓存的接口
参数含义:ivPic 用于显示图片的ImageView,url 获取图片的网络地址
package ****************************************
import android.graphics.Bitmap;
import android.widget.ImageView;
import com.hanweb.android.platform.utils.BitmapUtils;
/**
* Created by fppon 2018/8/21.
*/
public class MyBitmapUtils {
private NetCacheUtils mNetCacheUtils;
private MemoryCacheUtils mMemoryCacheUtils;
private LocalCacheUtils mLocalCacheUtils;
public MyBitmapUtils(){
mMemoryCacheUtils=new MemoryCacheUtils();
mLocalCacheUtils=new LocalCacheUtils();
mNetCacheUtils=new NetCacheUtils(mLocalCacheUtils,mMemoryCacheUtils);
// new MyBitmapUtils().disPlay(ivPic,url);
}
public void disPlay(ImageView ivPic,String url){
Bitmap bitmap;
bitmap=mMemoryCacheUtils.getBitmapFromMemory(url);
if(bitmap!=null){
ivPic.setImageBitmap(bitmap);
return;
}
bitmap=mLocalCacheUtils.getBitmapFromLocal(url);
if(bitmap!=null){
ivPic.setImageBitmap(bitmap);
mMemoryCacheUtils.setBitmapToMemory(url,bitmap);
return;
}
mNetCacheUtils.getBitmapFronNet(ivPic,url);
}
}
2. 网络缓存(NetCacheUtils)
网络缓存中主要用到了AsyncTask来进行异步数据的加载
简单来说,AsyncTask可以看作是一个对handler和线程池的封装,通常,AsyncTask主要用于数据简单时,handler+thread主要用于数据量多且复杂时
也可以使用线程池ExecutorService
同时,为了避免内存溢出的问题,我们可以在获取网络图片后。对其进行图片压缩
例子如下:包含线程池,注释掉的代码
/**
* Created by fpp on 2018/8/21.
* 三级缓存之网络缓存
*/
public class NetCacheUtils {
private MemoryCacheUtils mMemoryCacheUtils;
private LocalCacheUtils mLocalCacheUtils;
//线程池的方式下载
// ExecutorService executorService;
// Handler handler=new Handler(){
// @Override
// public void handleMessage(Message msg) {
// super.handleMessage(msg);
// //listener传输值
// }
// };
public NetCacheUtils(LocalCacheUtils localCacheUtils, MemoryCacheUtils memoryCacheUtils){
mMemoryCacheUtils=memoryCacheUtils;
mLocalCacheUtils=localCacheUtils;
//构造方法传一个listener进来
// executorService= Executors.newFixedThreadPool(5);
// executorService.execute(new Runnable() {
// @Override
// public void run() {
// Message message=handler.obtainMessage();
// Bitmap resultbitmap=downLoadBitmap("");
// message.obj=resultbitmap;
// //从网络获取后保存至本地缓存
// mLocalCacheUtils.setBitmapToLocal("",resultbitmap);
// //从网络获取后保存至内存中
// mMemoryCacheUtils.setBitmapToMemory("",resultbitmap);
// handler.sendMessage(message);
// }
// });
}
//从网络下载图片
public void getBitmapFronNet(ImageView ivPic,String url){
new BitmapTask().execute(ivPic,url);
}
class BitmapTaskextends AsyncTask{
private ImageViewivPic;
private Stringurl;
@Override
protected BitmapdoInBackground(Object... objects) {
ivPic=(ImageView) objects[0];
url=(String)objects[1];
return downLoadBitmap(url);
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(Bitmap resultbitmap) {
if(resultbitmap!=null){
ivPic.setImageBitmap(resultbitmap);
//从网络获取后保存至本地缓存
mLocalCacheUtils.setBitmapToLocal(url,resultbitmap);
//从网络获取后保存至内存中
mMemoryCacheUtils.setBitmapToMemory(url,resultbitmap);
}
}
}
private BitmapdownLoadBitmap(String url){
HttpURLConnection conn=null;
try {
conn=(HttpURLConnection)new URL(url).openConnection();
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
conn.setRequestMethod("GET");
int responseCode=conn.getResponseCode();
if(responseCode==200){
//图片压缩
BitmapFactory.Options options=new BitmapFactory.Options();
options.inSampleSize=2;
//压缩为原来的1/2
options.inPreferredConfig= Bitmap.Config.ARGB_4444;
Bitmap bitmap=BitmapFactory.decodeStream(conn.getInputStream(),null,options);
return bitmap;
}
}catch (IOException e) {
e.printStackTrace();
}finally {
conn.disconnect();
}
return null;
}
/**
* 定义一个接口,里面有一个方法,
* 这里有一个Bitmap对象参数,作用是让调用这接收这个Bitmap对象,实际这bitmap对象就是缓存中的对象
*/
public interface ImageLoadListener {
public void loadImage(Bitmap bmp);
}
3. 本地缓存(LocalCacheUtils)
在初次通过网络获取图片后,我们可以在本地SD卡中将图片保存起来
可以使用MD5加密图片的网络地址,来作为图片的名称保存
/**
* Created by fpp on 2018/8/21.
* 三级缓存之本地缓存
*/
public class LocalCacheUtils {
private static final StringCACHE_PATH= Environment.getExternalStorageDirectory().getAbsolutePath()+"/WerbNews";
/**
* 从本地读取图片
*/
public BitmapgetBitmapFromLocal(String url){
String fileName=null;
//把图片的url当做文件名,并进行MD5加密
fileName= MD5.md5(url);
File file=new File(CACHE_PATH,fileName);
try {
Bitmap bitmap= BitmapFactory.decodeStream(new FileInputStream(file));
return bitmap;
}catch (FileNotFoundException e) {
e.printStackTrace();
}
return null;
}
/**
* 从网络获取图片后保存至本地缓存
*/
public void setBitmapToLocal(String url,Bitmap bitmap){
String fileName=MD5.md5(url);
//把图片的url当做文件名,并进行MD5加密
File file=new File(CACHE_PATH,fileName);
//通过得到文件的父文件,判断父文件是否存在
File parentFile=file.getParentFile();
if(!parentFile.exists()){
parentFile.mkdir();
}
try {
bitmap.compress(Bitmap.CompressFormat.JPEG,100,new FileOutputStream(file));
}catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
4. 内存缓存(MemoryCacheUtils)
这是本文中最重要且需要重点介绍的部分
进行内存缓存,就一定要注意一个问题,那就是内存溢出(OutOfMemory)
为什么会造成内存溢出?
Android 虚拟机默认分配给每个App 16M的内存空间,真机会比16M大,但任会出现内存溢出的情况
Android 系统在加载图片时是解析每一个像素的信息,再把每一个像素全部保存至内存中
图片大小 = 图片的总像素 * 每个像素占用的大小
单色图:每个像素占用1/8个字节,16色图:每个像素占用1/2个字节,256色图:每个像素占用1个字节,24位图:每个像素占用3个字节(常见的rgb构成的图片)
例如一张1920x1080的JPG图片,在Android 系统中是以ARGB格式解析的,即一个像素需占用4个字节,图片的大小=1920x1080x4=7M
实现方法:
通过HashMap键值对的方式保存图片,key为地址,value为图片对象,但因是强引用对象,很容易造成内存溢出,可以尝试SoftReference软引用对象
通过HashMap>SoftReference 为软引用对象(GC垃圾回收会自动回收软引用对象),但在Android2.3+后,系统会优先考虑回收弱引用对象,官方提出使用LruCache
通过LruCacheleast recentlly use 最少最近使用算法
会将内存控制在一定的大小内, 超出最大值时会自动回收, 这个最大值开发者自己定
/**
* Created by fpp on 2018/8/21.
* 三级缓存之内存缓存
*/
public class MemoryCacheUtils {
// private HashMap mMemoryCache=new HashMap<>();//1.因为强引用,容易造成内存溢出,所以考虑使用下面弱引用的方法
private HashMap>mMemoryCache =new HashMap<>();//2.因为在Android2.3+后,系统会优先考虑回收弱引用对象,官方提出使用LruCache
// private LruCache mMemoryCache;
public MemoryCacheUtils(){
long maxMemory=Runtime.getRuntime().maxMemory()/8;
//得到手机最大允许内存的1/8,超过指定内存进行回收
// mMemoryCache=new LruCache((int)maxMemory){
// @Override
// protected int sizeOf(String key, Bitmap value) {
// int byteCount=value.getByteCount();
// return byteCount;
// }
// };
}
//从内存中读取图片
public BitmapgetBitmapFromMemory(String url){
//强引用方法
// Bitmap bitmap=mMemoryCache.get(url);
//弱引用方法
SoftReference bitmapSoftReference=mMemoryCache.get(url);
if(bitmapSoftReference!=null){
Bitmap bitmap1=bitmapSoftReference.get();
return bitmap1;
}
// return bitmap;
return null;
}
//往内存中写图片
public void setBitmapToMemory(String url,Bitmap bitmap){
//强引用
// mMemoryCache.put(url,bitmap);
//弱引用
mMemoryCache.put(url,new SoftReference<>(bitmap));
}
}