参考资料
Android DiskLruCache完全解析,硬盘缓存的最佳方案
目录
- Bitmap的高效加载
-
- Android的缓存策略
- 2.1)LruCache
- 2.2)DiskLruCache
-
- ImageLoader的使用
- 3.1)ImageLoader的实现
- 3.2)优化列表的卡顿
1)Bitmap的高效加载
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// 源图片的高度和宽度
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// 计算出实际宽高和目标宽高的比率
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高 ,一定都会大于等于目标的宽和高。
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; // 第一次解析将inJustDecodeBounds设置为true,禁止为bitmap分配内存,从而来获取图片大小
BitmapFactory.decodeResource(res, resId, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // 调用上面定义的方法计算inSampleSize值
options.inJustDecodeBounds = false; // 使用获取到的inSampleSize值再次解析图片 ,false为bitmap分配内存
return BitmapFactory.decodeResource(res, resId, options);
}
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100)); //将任意一张图片压缩成100*100的缩略图,并在ImageView上展示
当inSampleSize为2时,采样后的图片宽高为原来的1/2,而像素数为原图的1/4,内存大小也为原图的1/4。 例如ARGB8888(每个像素4Byte)格式存储的1024x1024图片,内存为1024x1024x4=4M。采样后位512x512x4=1M。
2)Android的缓存策略
2.1)LruCache (Least Recently Used)
主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除
LruCache中将LinkedHashMap的顺序设置为LRU顺序来实现LRU缓存,每次调用get(也就是从内存缓存中取图片),则将该对象移到链表的尾端。调用put插入新的对象也是存储在链表尾端,这样当内存缓存达到设定的最大值时,将链表头部的对象(近期最少用到的)移除
名称 | 说明 |
---|---|
强引用 | 直接的对象引用 |
软引用 | 当一个对象只有软引用存在时,系统内存不足时会回收此对象 |
弱引用 | 当一个对象只有弱引用存在时,随时会回收此对象 |
private LruCache<String, Bitmap> mMemoryCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
// 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。LruCache通过构造函数传入缓存值,以KB为单位
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8; // 使用最大可用内存值的1/8作为缓存的大小。
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount() / 1024; // 重写此方法来衡量每张图片的大小,默认返回图片数量。
}
};
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap); //添加一个缓存对象
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key); //获取一个缓存对象
}
public void loadBitmap(int resId, ImageView imageView) {
final String imageKey = String.valueOf(resId);
final Bitmap bitmap = getBitmapFromMemCache(imageKey);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(R.drawable.image_placeholder);
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
task.execute(resId);
}
}
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { //异步下载图片
// 在后台加载图片。
@Override
protected Bitmap doInBackground(Integer... params) {
final Bitmap bitmap = decodeSampledBitmapFromResource(
getResources(), params[0], 100, 100);
addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
return bitmap;
}
}
2.2)DiskLruCache
//提供了open方法创建自身
public static DiskLurCache open(
File directory, //存储路径
int appVersion, //应用版本号,一般设为1
int valueCount, //单个节点对应数据个数,一般为1
long maxSize) //缓存总大小
- 实例代码
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private DiskLruCache mDiskLruCache = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i(TAG, "onCreate: ");
ImageView img = (ImageView) findViewById(R.id.img);
//文件路径
File cacheDir = getDiskCacheDir("bitmap");
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
try {
//创建
mDiskLruCache = DiskLruCache.open(cacheDir, 1, 1, 10 * 1024 * 1024);
String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";
//将url进行md5编码后,成为一个合法的文件命名
String key = hashKeyForDisk(imageUrl);
//写入缓存类
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
//远程请求数据 并通过outputstream写入本地
downloadUrltoStream(imageUrl, outputStream);
editor.commit();
}
//读取缓存类
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
if (snapShot != null){
InputStream is = snapShot.getInputStream(0);
Bitmap bitmap = BitmapFactory.decodeStream(is);
img.setImageBitmap(bitmap);
}
} catch (IOException e) {
e.printStackTrace();
}
}
//获取创建路径
private File getDiskCacheDir(String name) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) {
cachePath = getExternalCacheDir().getPath();
} else {
cachePath = getCacheDir().getPath();
}
return new File(cachePath + File.pathSeparator + name);
}
//远程请求公共方法
public static void sendRequestPost(String url, Map<String, String> param, Callback callback) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
client.newCall(request).enqueue(callback);
}
//远程获取数据并写入outPutStream
private void downloadUrltoStream(String url, final OutputStream outputStream) {
sendRequestPost(url, null, new Callback() {
BufferedInputStream in = null;
BufferedOutputStream out = null;
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
InputStream inputStream = response.body().byteStream();
in = new BufferedInputStream(inputStream, 8 * 1024);
out = new BufferedOutputStream(outputStream, 8 * 1024);
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
}
});
}
//md5编码
public String hashKeyForDisk(String key) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
private String bytesToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
}
adb shell查看添加disk缓存结果
- 其他API
API | 说明 |
---|---|
remove(key) | 根据key删除缓存数据 |
size() | 当前缓存路径下所有缓存数据得字节数Byte |
flush() | 将内存中的操作记录同步到日志文件(也就是journal文件)当中,不用频繁调,比较标准的做法就是在Activity的onPause()调用 |
close() | 关闭DiskLruCache,只应该在Activity的onDestroy()方法中去调用close()方法 |
delete() | 清除全部缓存数据 |
3)ImageLoader的使用
3.1)ImageLoader的实现
- 内存缓存 LruCache
- 磁盘缓存 DiskLruCache
- 从内存缓存中读取
- 交给线程池执行
- 从磁盘缓存读取,并存入内存缓存
- 从网络读取,并压缩后存入磁盘缓存
3.2)优化列表的卡顿
- 异步加载图片
使用glide框架或imageLoader等 - 滑动停止后加载
listview.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE){
Glide.with(SchoolListActivity.this).resumeRequests();
}else{
//glide提供了暂停和恢复的方法
Glide.with(SchoolListActivity.this).pauseRequests();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
});
- 硬件加速可以解决莫名卡顿问题
android:hardwareAccelerated="true"
网友评论