三级缓存原理:就是当 App 需要引用缓存时,首先到内存缓存中读取,读取不到再到本地缓存中读取,还获取不到就到网络异步读取,读取成功之后再保存到内存和本地缓存中。
内存缓存:优先加载,加载速度快
本地缓存: 次优先加载,速度一般
网络缓存:加载优先级最低,消耗流量。使用时注意异步加载。
首先创建一个内存缓存类MemoryCacheUtil ,改类中有存储数据和获取数据方法
public class MemoryCacheUtil {
private static final String TAG = "MemoryCacheUtil";
private LruCache<String, Bitmap> mLruCache;
public MemoryCacheUtil() {
// maxMemory 是允许的最大值 ,超过这个最大值,则会回收
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
/**
* 计算每张图片的大小
* @param key
* @param bitmap
* @return
*/
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount();
}
};
}
/**
* 通过url从内存中获取图片
*
* @param url
*/
public Bitmap getBitmapFromMemory(String url) {
Log.e(TAG, "getBitmapFromMemory: mLruCache.get(url)="+mLruCache.get(url)+",个数="+mLruCache.size() );
return mLruCache.get(url);
}
/**
* 设置Bitmap到内存
*
* @param url
* @param bitmap
*/
public void setBitmapToMemory(String url, Bitmap bitmap) {
Log.e(TAG, "setBitmapToMemory: getBitmapFromMemory(url)="+getBitmapFromMemory(url));
if (getBitmapFromMemory(url) == null) {
Log.e(TAG, "setBitmapToMemory: == null" );
mLruCache.put(url, bitmap); // 设置图片
}
}
/**
* 从缓存中删除指定的Bitmap
*
* @param key
*/
public void removeBitmapFromMemory(String key) {
mLruCache.remove(key);
}
}
之后创建一个本地缓存类LocalCacheUtil ,主要将图片压缩后保存到本地内存中,在这其中通过MD5加密文件名
public class LocalCacheUtil {
private String cachePath;
public LocalCacheUtil(Context context, String uniqueName) {
cachePath = getCacheDirString(context, uniqueName);
}
/**
* 设置Bitmap数据到本地
*
* @param url
* @param bitmap
*/
public void setBitmapToLocal(String url, Bitmap bitmap) {
FileOutputStream fos = null;
try {
String fileName = MD5Encoder.encode(url);
File file = new File(cachePath, fileName);
File parentFile = file.getParentFile();//获取上级所有目录
if (!parentFile.exists()) {
// 如果文件不存在,则创建文件夹
parentFile.mkdirs();
}
fos = new FileOutputStream(file);
// 将图片压缩到本地
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
} catch (Exception e) {
e.printStackTrace();
}finally {
if (fos != null) {
try {
fos.close();//关闭流
fos = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 通过url获取Bitmap
*
* @param url
*/
public Bitmap getBitmapFromLocal(String url) {
try {
File file = new File(cachePath, MD5Encoder.encode(url));
if (file.exists()) {
// 如果文件存在
Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(file));
//Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
return bitmap;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/*获取缓存目录的路径:String类型*/
private String getCacheDirString(Context context, String uniqueName) {
File file = null;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
file = new File(context.getExternalCacheDir(), uniqueName);
//file = new File(Environment.getExternalStorageDirectory(), uniqueName);
} else {
file = new File(context.getCacheDir(), uniqueName);
}
if (!file.exists()) {
file.mkdirs();
}
return file.getAbsolutePath();
}
}
然后新建一个网络获取数据类NetCacheUtil,通过AsyncTask 去网络下载图片,然后将图片分别保存到内存中和本地种
public class NetCacheUtil {
private MemoryCacheUtil mMemoryCacheUtil;
private LocalCacheUtil mLocalCacheUtil;
public NetCacheUtil(MemoryCacheUtil mMemoryCacheUtil, LocalCacheUtil mLocalCacheUtil) {
this.mMemoryCacheUtil = mMemoryCacheUtil;
this.mLocalCacheUtil = mLocalCacheUtil;
}
/**
* 获取服务端数据
*
* @param ivPhoto
* @param url
*/
public void getBitmapFromInternet(ImageView ivPhoto, String url) {
new BitmapAsyncTask().execute(ivPhoto, url); // 开启AsyncTask
}
/**
* 第一个泛型:参数类型 第二个泛型:更新进度的泛型, 第三个泛型是OnPostExecute的结果
* Object : 传入的参数类型
* Void : 进度
* Bitmap : 返回类型
*/
private class BitmapAsyncTask extends AsyncTask<Object, Void, Bitmap> {
private ImageView ivPhoto;
private String url;
/**
* 运行在子线程中:请求数据
*
* @param params
* @return
*/
@Override
protected Bitmap doInBackground(Object... params) {
ivPhoto = (ImageView) params[0]; // 获取两个参数
url = (String) params[1];
Bitmap bitmap = downloadBitmap(url); // 从网络上加载图片
return bitmap;
}
/**
* 在主线程中执行 用于更新界面
*
* @param bitmap
*/
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if (bitmap != null) {
ivPhoto.setImageBitmap(bitmap); // 为ImageView设置图片
System.out.println("从网络获取图片...");
T.showShort(App.sContext,"从网络获取图片...");
// 将获取到的图片加载到本地
mLocalCacheUtil.setBitmapToLocal(url, bitmap);
// 将获取到的图片加载到内存
mMemoryCacheUtil.setBitmapToMemory(url, bitmap);
}
}
}
/**
* 根据url从网络上获取图片
*
* @param imageUrl 图片路径
* @return
*/
private Bitmap downloadBitmap(String imageUrl) {
HttpURLConnection conn = null;
try {
URL url = new URL(imageUrl);
conn = (HttpURLConnection) url.openConnection(); // 打开连接
conn.setReadTimeout(5000); // 设置读取超时时间
conn.setConnectTimeout(5000); // 设置连接超时时间
conn.setRequestMethod("GET"); // 设置请求方式
conn.connect(); // 开始连接
if (conn.getResponseCode() == 200) {
// 访问成功
InputStream is = conn.getInputStream(); // 获取流数据
// 对图片进行压缩处理
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2; // 图片的宽高都为原来的一半(分辨率)
Bitmap bitmap = BitmapFactory.decodeStream(is, null, options); // 将流数据转成Bitmap对象
return bitmap;
} else {
// 访问失败
return null;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect(); // 断开连接
}
}
return null;
}
}
最后新建一个三级缓存工具类MyBitmapUtil.通过网址去判断地址是否存在缓存中,存在就返回展示,指导网络获取结束,
public class MyBitmapUtil {
private static final String TAG ="MyBitmapUtil";
private MemoryCacheUtil mMemoryCacheUtil = null; // 内存缓存(lrucache)
private LocalCacheUtil mLocalCacheUtil = null; // 本地缓存(file)
private NetCacheUtil mNetCacheUtil = null; // 网络缓存
public MyBitmapUtil(Context context, String uniqueName) {
mMemoryCacheUtil = new MemoryCacheUtil();
mLocalCacheUtil = new LocalCacheUtil(context,uniqueName);
mNetCacheUtil = new NetCacheUtil(mMemoryCacheUtil, mLocalCacheUtil);
}
/**
* 将图片资源设置给控件
*
* @param url
* @param ivPhoto
*/
public void display(String url, ImageView ivPhoto) {
Bitmap bitmap = null;
// 1.判断内存中是否有缓存
bitmap = mMemoryCacheUtil.getBitmapFromMemory(url); // 从内存中获取Bitmap
Log.e(TAG, "display: bitmap="+bitmap );
if (bitmap != null) {
ivPhoto.setImageBitmap(bitmap); // 设置图片
System.out.println("从内存获取图片...");
T.showShort(App.sContext,"从内存获取图片...");
return;
}
// 2.判断本地是否有缓存
bitmap = mLocalCacheUtil.getBitmapFromLocal(url); // 从本地缓存中获取Bitmap
if (bitmap != null) {
ivPhoto.setImageBitmap(bitmap); // 设置本地图片
mMemoryCacheUtil.setBitmapToMemory(url, bitmap); // 设置图片到内存
System.out.println("从本地获取图片...");
T.showShort(App.sContext,"从本地获取图片....");
return;
}
// 3.从网络获取数据
mNetCacheUtil.getBitmapFromInternet(ivPhoto, url); // 设置图片
}
}
方法展示:
// 三级缓存
MyBitmapUtil myBitmapUtil =new MyBitmapUtil(this,"wudi");
myBitmapUtil.display("http://c.hiphotos.baidu.com/image/h%3D300/sign=cfce96dfa251f3dedcb2bf64a4eff0ec/4610b912c8fcc3ce912597269f45d688d43f2039.jpg",image);
//点击后直接从内存中获取
image2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
myBitmapUtil.display("http://c.hiphotos.baidu.com/image/h%3D300/sign=cfce96dfa251f3dedcb2bf64a4eff0ec/4610b912c8fcc3ce912597269f45d688d43f2039.jpg",image2);
}
});
Bitmap 获取
decodeFile:从文件系统加载 Bitmap 对象
decodeResource:从资源文件中加载 Bitmap
decodeStream:从输入流加载 Bitmap
decodeByteArray:从字节数组中加载 Bitmap
bitmap高效加载
获取加载图片的宽高,内存大小,
主要使用 BitmapFactory.Options 的 inSampleSize 来设置采样率
nSampleSize 为1,原始图片
inSampleSize 为2,宽高均为原来的 1/2,像素为原来的 1/4
inSampleSize 为4,宽高均为原来的 1/4,像素为原来的 1/16
采样压缩方法和步骤
将 BitmapFactory.Options 的 inJustDecodeBounds 参数设置为 true;
从 BitmapFactory.Options 中取出图片的原始宽高信息,也就是 outWidth 和 outHeight 参数;
结合目标 View 所需大小来计算所需采样率 inSampleSize;
将 BitmapFactory.Options 的 inJustDecodeBounds 设置为 false,重新加载图片。
设置 inJustDecodeBounds 参数为 true 时只会解析图片的宽/高信息,并不会去加载图片,所以该操作是轻量级的。
1:设置图片
ImageView iv_decode = (ImageView)findViewById(R.id.iv_decode);
iv_decode.setImageBitmap(BitmapUtils.decodeSampledBitmapFromResoruce(getResources(),R .drawable.mz,160,200));
2采样率的计算和图片读取
public static Bitmap decodeSampledBitmapFromResoruce(Resources res,int resId,
int reqWidth,int reqHeight){
// 获取 BitmapFactory.Options,这里面保存了很多有关 Bitmap 的设置
final BitmapFactory.Options options = new BitmapFactory.Options();
// 设置 true 轻量加载图片信息
options.inJustDecodeBounds = true;
// 由于上方设置false,这里轻量加载图片
BitmapFactory.decodeResource(res,resId,options);
// 计算采样率
options.inSampleSize = calculateInSampleSize(options,reqWidth,reqHeight);
// 设置 false 正常加载图片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res,resId,options);
}
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth,int reqHeight){
final int width = options.outWidth;
final int height = options.outHeight;
int inSampleSize = 1;
// 宽或高大于预期就将采样率 *=2 进行缩放
if(width > reqWidth || height > reqHeight){
final int halfHeight = height/2;
final int halfWidth = width/2;
while((halfHeight / inSampleSize) >= reqHeight &&
(halfWidth / inSampleSize) >= reqWidth){
inSampleSize *= 2;
}
}
return inSampleSize;
}
LruCache
内部采用 LinkedHashMap 以强引用的方式储存缓存对象,并提供 get/put 来完成获取和添加操作。当缓存满时,会移除较早使用的缓存对象,然后添加新的缓存对象。
LruCache 通常被用来缓存图片,但是也可以缓存其它内容到内存中
//图片缓存
public static LruCache<String, Bitmap> mMemoryCache;
private MyHandler handler;
// handler 避免内存泄漏
static class MyHandler extends Handler {
private WeakReference<Activity> weakReference;
public MyHandler(Activity activity) {
this.weakReference = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Activity activity = weakReference.get();
if (activity != null) {
switch (msg.what) {
case 1:
Log.e(TAG, "handleMessage: Main创建开始" );
break;
case 1001:
Log.e(TAG, "handleMessage: 异步加载" );
Bitmap bitmap =BitmapFactory.decodeResource(activity.getResources(), R.mipmap.guide_icon01);
// Bitmap bitmap =BitmapUtils.decodeSampledBitmapFromResoruce(activity,R .drawable.mz,160,200)
Log.e(TAG, "handleMessage: String.valueOf(bitmap)="+String.valueOf(bitmap)+",bitmap="+bitmap );
addBitmapToMemoryCache(String.valueOf(bitmap), bitmap);
break;
}
}
}
}
public void loadBitmap(int resId, ImageView imageView) {
final String imageKey = String.valueOf(resId);
final Bitmap bitmap = getBitmapFromMemCache(imageKey);
if (bitmap != null) {
Log.e(TAG, "loadBitmap: bitmap!=null" );
imageView.setImageBitmap(bitmap);
} else {
Log.e(TAG, "loadBitmap: bitmap==null" );
imageView.setImageResource(R.drawable.image_placeholder);
Message message = Message.obtain();
message.what = 1001;
handler.handleMessage(message);
}
}
public static void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
upDataUI(key);
}
}
private static void upDataUI(String key) {
final String imageKey = String.valueOf(key);
final Bitmap bp = getBitmapFromMemCache(imageKey);
image_forth.setImageBitmap(bp);
}
public static Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
//onCreate()
// 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
// LruCache通过构造函数传入缓存值,以KB为单位。
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
Log.e(TAG, "initData: maxMemory="+maxMemory );
// 使用最大可用内存值的1/8作为缓存的大小。
int cacheSize = maxMemory / 8;
Log.e(TAG, "initData: cacheSize="+cacheSize );
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
protected int sizeOf(String key, Bitmap bitmap) {
// 重写此方法来衡量每张图片的大小,默认返回图片数量。
return bitmap.getByteCount() / 1024;
}
};
handler = new MyHandler(this);
Message message = Message.obtain();
message.what = 1;
handler.handleMessage(message);
loadBitmap(R.mipmap.guide_icon01,image_forth);
private LruCache<String, BitmapDrawable> mMemoryCache;
public ImageAdapter(Context context, int resource, String[] objects) {
super(context, resource, objects);
// 获取应用程序最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, BitmapDrawable>(cacheSize) {
@Override
protected int sizeOf(String key, BitmapDrawable drawable) {
return drawable.getBitmap().getByteCount();
}
};
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
String url = getItem(position);
View view;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(R.layout.image_item, null);
} else {
view = convertView;
}
ImageView image = (ImageView) view.findViewById(R.id.image);
BitmapDrawable drawable = getBitmapFromMemoryCache(url);
if (drawable != null) {
image.setImageDrawable(drawable);
} else {
BitmapWorkerTask task = new BitmapWorkerTask(image);
task.execute(url);
}
return view;
}
/**
* 将一张图片存储到LruCache中。
*
* @param key
* LruCache的键,这里传入图片的URL地址。
* @param drawable
* LruCache的值,这里传入从网络上下载的BitmapDrawable对象。
*/
public void addBitmapToMemoryCache(String key, BitmapDrawable drawable) {
if (getBitmapFromMemoryCache(key) == null) {
mMemoryCache.put(key, drawable);
}
}
/**
* 从LruCache中获取一张图片,如果不存在就返回null。
*
* @param key
* LruCache的键,这里传入图片的URL地址。
* @return 对应传入键的BitmapDrawable对象,或者null。
*/
public BitmapDrawable getBitmapFromMemoryCache(String key) {
return mMemoryCache.get(key);
}
/**
* 异步下载图片的任务。
*
* @author guolin
*/
class BitmapWorkerTask extends AsyncTask<String, Void, BitmapDrawable> {
private ImageView mImageView;
public BitmapWorkerTask(ImageView imageView) {
mImageView = imageView;
}
@Override
protected BitmapDrawable doInBackground(String... params) {
String imageUrl = params[0];
// 在后台开始下载图片
Bitmap bitmap = downloadBitmap(imageUrl);
BitmapDrawable drawable = new BitmapDrawable(getContext().getResources(), bitmap);
addBitmapToMemoryCache(imageUrl, drawable);
return drawable;
}
@Override
protected void onPostExecute(BitmapDrawable drawable) {
if (mImageView != null && drawable != null) {
mImageView.setImageDrawable(drawable);
}
}
/**
* 建立HTTP请求,并获取Bitmap对象。
*
* @param imageUrl
* 图片的URL地址
* @return 解析后的Bitmap对象
*/
private Bitmap downloadBitmap(String imageUrl) {
Bitmap bitmap = null;
HttpURLConnection con = null;
try {
URL url = new URL(imageUrl);
con = (HttpURLConnection) url.openConnection();
con.setConnectTimeout(5 * 1000);
con.setReadTimeout(10 * 1000);
bitmap = BitmapFactory.decodeStream(con.getInputStream());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (con != null) {
con.disconnect();
}
}
return bitmap;
}
}
}