一、了解三级缓存
三级缓存一般分为:内存,磁盘和网络
重要步骤:
① UI:请求数据,使用唯一的Key值索引Memory Cache中的Bitmap。
② 内存缓存:缓存搜索,如果能找到Key值对应的Bitmap,则返回数据。否则执行第三步。
③ 硬盘存储:使用唯一Key值对应的文件名,检索SDCard上的文件。
④ 如果有对应文件,使用BitmapFactory.decode*方法,解码Bitmap并返回数据,同时将数据写入缓存。如果没有对应文件,执行第五步。
⑤ 下载图片:启动异步线程,从数据源下载数据(Web)。
⑥ 若下载成功,将数据同时写入硬盘和缓存,并将Bitmap显示在UI中。
二、有效的显示位图
如果使用bitmap的消耗过大,就会出现
java.lang.OutofMemoryError: bitmap size exceeds VM budget.的问题–OOM
导致bitmap的OOM的原因有如下几个:
1、Android的系统为每个应用提供至少16MB的内存,但是现在好点的设备会有所增加
2、Bitmap会消耗更多的内存,特别是像这样丰富的图片显示,当配置好点的显示的时候,设备会消耗掉每个应用的限制
3、Android的UI经常会立即加载几张位图,例如ListView,GridView和ViewPager等等
①计算图片的缩放比例(使用inSampleSize)
/**
* 获取缩放的比例
*
* @param _options
* @param _reWidth
* @param _reHeight
* @return
*/
public int calculateInSampleSize(BitmapFactory.Options _options, int _reWidth, int _reHeight) {
//获取图片的高和宽
int _width = _options.outWidth;
int _height = _options.outHeight;
int _inSampleSize = 1;
//计算缩放比例,inSampleSize>1说明图片是被压缩的
if (_width > _reWidth || _height > _reHeight) {
int _halfWidth = _width / 2;
int _halfHeight = _height / 2;
while ((_halfWidth / _inSampleSize) > _reWidth && (_halfHeight / _inSampleSize) > _reHeight) {
_inSampleSize *= 2;
}
}
return _inSampleSize;
}
②根据缩放比例进行缩放图片,返回bitmap
将inJustDecodeBounds设置为true,可以解码的时候避免分配内存
/**
* 获取缩放之后的图片Bitmap
*
* @param _resources
* @param _res
* @param _reWidth
* @param _reHeight
* @return
*/
public Bitmap getResourceBitmap(Resources _resources, int _res, int _reWidth, int _reHeight) {
BitmapFactory.Options _options = new BitmapFactory.Options();
//设置为true则解码器不计算
_options.inJustDecodeBounds = true;
//解码图片资源
BitmapFactory.decodeResource(_resources, _res, _options);
//根据图片的信息进行计算缩放比例
_options.inSampleSize = this.calculateInSampleSize(_options, _reWidth, _reHeight);
//设置为false则解码器开始计算
_options.inJustDecodeBounds = false;
//返回解码缩放之后的bitmap
return BitmapFactory.decodeResource(_resources, _res, _options);
}
③使用
Bitmap _bitmap = getResourceBitmap(getResources(), params[0], 200, 100);
mImageBitmapCache.setImageBitmap(bitmap);
三、内存缓存(LruCache的使用)
内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache(此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or
WeakReference)。但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。并没有一个指定的缓存大小可以满足所有的应用程序,这是由你决定的。你应该去分析程序内存的使用情况,然后制定出一个合适的解决方案。一个太小的缓存空间,有可能造成图片频繁地被释放和重新加载,这并没有好处。而一个太大的缓存空间,则有可能还是会引起 java.lang.OutOfMemory 的异常。
①计算能使用的最大内存
int _maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
②计算分配的缓存的大小
int _cacheSize = _maxMemory / 8;
③创建一个LruCache对象
private LruCache<String, Bitmap> mMemoryCache;
mMemoryCache = new LruCache<String, Bitmap>(_cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount() / 1024;
}
};
④两个方法用于分别存取缓存数据
/**
* 将bitmap添加到内存中
*
* @param _key
* @param _bitmap
*/
public void addBitmapToMemoryCache(String _key, Bitmap _bitmap) {
if (null == getBitmapFromMemoryCache(_key))
mMemoryCache.put(_key, _bitmap);
}
/**
* 获取内存中的bitmap
*
* @param _key
* @return
*/
public Bitmap getBitmapFromMemoryCache(String _key) {
return mMemoryCache.get(_key);
}
⑤使用内存缓存,这里使用了AsyncTask在子线程中进行解码图片
/**
* 点击按钮加载图片
*
* @param _res
*/
public void loadImage(int _res) {
String _resKey = String.valueOf(_res);
Bitmap _bitmap = getBitmapFromMemoryCache(_resKey);
if (null != _bitmap) {
mImageBitmapCache.setImageBitmap(_bitmap);
} else {
BitmapWorkerTask _bitmapWorkerTask = new BitmapWorkerTask();
_bitmapWorkerTask.execute(_res);
}
}
/**
* 使用AsyncTask来异步对图片的处理
*/
public class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
@Override
protected Bitmap doInBackground(Integer... params) {
Bitmap _bitmap = getResourceBitmap(getResources(), params[0], 200, 100);
addBitmapToMemoryCache(String.valueOf(params[0]), _bitmap);
return _bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (null != bitmap)
mImageBitmapCache.setImageBitmap(bitmap);
}
}
使用LruCache内存缓存代码:
public class BitmapCacheActivity extends BaseActivity {
//UI
private ImageView mImageBitmapCache;
private Button mButtonBitmapCache;
//LruCache
private LruCache<String, Bitmap> mMemoryCache;
@Override
public void initData(Bundle savedInstanceState) {
//计算最大的内存
int _maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
//使用最大内存的1/8来进行内存缓存
int _cacheSize = _maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(_cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount() / 1024;
}
};
}
@Override
public void initView(Bundle savedInstanceState) {
setContentView(R.layout.activity_bitmap_cache);
this.mImageBitmapCache = (ImageView) findViewById(R.id.activity_bitmap_cache_image);
this.mButtonBitmapCache = (Button) findViewById(R.id.activity_bitmap_cache_button);
}
@Override
public void loadData(Bundle savedInstanceState) {
this.mButtonBitmapCache.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loadImage(R.mipmap.bitmap_cache);
}
});
}
/**
* 将bitmap添加到内存中
*
* @param _key
* @param _bitmap
*/
public void addBitmapToMemoryCache(String _key, Bitmap _bitmap) {
if (null == getBitmapFromMemoryCache(_key))
mMemoryCache.put(_key, _bitmap);
}
/**
* 获取内存中的bitmap
*
* @param _key
* @return
*/
public Bitmap getBitmapFromMemoryCache(String _key) {
return mMemoryCache.get(_key);
}
/**
* 点击按钮加载图片
*
* @param _res
*/
public void loadImage(int _res) {
String _resKey = String.valueOf(_res);
Bitmap _bitmap = getBitmapFromMemoryCache(_resKey);
if (null != _bitmap) {
mImageBitmapCache.setImageBitmap(_bitmap);
} else {
BitmapWorkerTask _bitmapWorkerTask = new BitmapWorkerTask();
_bitmapWorkerTask.execute(_res);
}
}
/**
* 使用AsyncTask来异步对图片的处理
*/
public class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
@Override
protected Bitmap doInBackground(Integer... params) {
Bitmap _bitmap = getResourceBitmap(getResources(), params[0], 200, 100);
addBitmapToMemoryCache(String.valueOf(params[0]), _bitmap);
return _bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (null != bitmap)
mImageBitmapCache.setImageBitmap(bitmap);
}
}
/**
* 获取缩放之后的图片Bitmap
*
* @param _resources
* @param _res
* @param _reWidth
* @param _reHeight
* @return
*/
public Bitmap getResourceBitmap(Resources _resources, int _res, int _reWidth, int _reHeight) {
BitmapFactory.Options _options = new BitmapFactory.Options();
//设置为true则解码器不计算
_options.inJustDecodeBounds = true;
//解码图片资源
BitmapFactory.decodeResource(_resources, _res, _options);
//根据图片的信息进行计算缩放比例
_options.inSampleSize = this.calculateInSampleSize(_options, _reWidth, _reHeight);
//设置为false则解码器开始计算
_options.inJustDecodeBounds = false;
//返回解码缩放之后的bitmap
return BitmapFactory.decodeResource(_resources, _res, _options);
}
/**
* 获取缩放的比例
*
* @param _options
* @param _reWidth
* @param _reHeight
* @return
*/
public int calculateInSampleSize(BitmapFactory.Options _options, int _reWidth, int _reHeight) {
//获取图片的高和宽
int _width = _options.outWidth;
int _height = _options.outHeight;
int _inSampleSize = 1;
//计算缩放比例,inSampleSize>1说明图片是被压缩的
if (_width > _reWidth || _height > _reHeight) {
int _halfWidth = _width / 2;
int _halfHeight = _height / 2;
while ((_halfWidth / _inSampleSize) > _reWidth && (_halfHeight / _inSampleSize) > _reHeight) {
_inSampleSize *= 2;
}
}
return _inSampleSize;
}
}
四、使用内存缓存和磁盘缓存(LruCache和DiskLruCache)
①先查看SD卡是否挂载
/**
* 获取磁盘缓存的地址,如果有SD卡,则创建SD卡上的缓存目录,如果没有SD卡,则创建内部缓存目录
*
* @param _context
* @param _name
* @return
*/
public File getDiskCacheDir(Context _context, String _name) {
String _cachePath;
//判断SD卡是否挂载和SD卡非移除状态
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) {
_cachePath = _context.getExternalCacheDir().getPath();
} else {
_cachePath = _context.getCacheDir().getPath();
}
return new File(_cachePath + File.separator + _name);
}
②添加到Disk缓存中(key对应的OutPutStream)
synchronized (mDiskCacheLock) {
try {
if (null != mDiskLruCache && null == mDiskLruCache.get(_key)) {
DiskLruCache.Editor _editor = mDiskLruCache.edit(_key);
//将bitmap写入到outputstream中去
_bitmap.compress(Bitmap.CompressFormat.JPEG, 30, _editor.newOutputStream(0));
//提交
_editor.commit();
}
} catch (IOException e) {
e.printStackTrace();
}
}
③获取Disk缓存中数据(根据key获取inputStream)
/**
* 获取disk中的缓存
*
* @param _key
* @return
*/
public Bitmap getBitmapFromDiskCache(String _key) {
Bitmap _bitmap = null;
while (mDiskCacheStaring) {
try {
//mDiskLruCache没有初始化的时候,不能进行获取流
mDiskCacheLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (null != mDiskLruCache) {
try {
DiskLruCache.Snapshot _snapShot = mDiskLruCache.get(_key);
if (null != _snapShot) {
//将获取到的inputstream转为bitmap
InputStream _is = _snapShot.getInputStream(0);
_bitmap = BitmapFactory.decodeStream(_is);
return _bitmap;
}
} catch (IOException e) {
e.printStackTrace();
}
}
return _bitmap;
}
④在子线程中初始化DiskLruCache对象
/**
* 在子线程中初始化DiskLruCache对象
*/
public class InitDiskCache implements Runnable {
@Override
public void run() {
synchronized (mDiskCacheLock) {
try {
mDiskLruCache = DiskLruCache.open(mCacheDir, 1, 1, DISK_CACHE_SIZE);
mDiskCacheStaring = false;
mDiskCacheLock.notifyAll();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
⑤执行操作
1、初始化DiskLruCache
//初始化DiskLruCache
mCacheDir = getDiskCacheDir(this, DISK_CACHE_NAME);
new Thread(new InitDiskCache()).start();
2、执行
Bitmap _diskBitmap = getBitmapFromDiskCache(_resKey);
if (null != _diskBitmap) {
mImageBitmapCache.setImageBitmap(_diskBitmap);
⑥附加
//DiskLruCache
private DiskLruCache mDiskLruCache;
private Object mDiskCacheLock = new Object();//创建对象锁
private boolean mDiskCacheStaring = true;
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10;//磁盘缓存的文件大小
private static final String DISK_CACHE_NAME = "Millet";//磁盘缓存的名字
private File mCacheDir;
完整代码:
public class BitmapCacheActivity extends BaseActivity {
//UI
private ImageView mImageBitmapCache;
private Button mButtonBitmapCache;
//LruCache
private LruCache<String, Bitmap> mMemoryCache;
//DiskLruCache
private DiskLruCache mDiskLruCache;
private Object mDiskCacheLock = new Object();//创建对象锁
private boolean mDiskCacheStaring = true;
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10;//磁盘缓存的文件大小
private static final String DISK_CACHE_NAME = "Millet";//磁盘缓存的名字
private File mCacheDir;
@Override
public void initData(Bundle savedInstanceState) {
//计算最大的内存
int _maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
//使用最大内存的1/8来进行内存缓存
int _cacheSize = _maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(_cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount() / 1024;
}
};
//初始化DiskLruCache
mCacheDir = getDiskCacheDir(this, DISK_CACHE_NAME);
new Thread(new InitDiskCache()).start();
}
@Override
public void initView(Bundle savedInstanceState) {
setContentView(R.layout.activity_bitmap_cache);
this.mImageBitmapCache = (ImageView) findViewById(R.id.activity_bitmap_cache_image);
this.mButtonBitmapCache = (Button) findViewById(R.id.activity_bitmap_cache_button);
}
@Override
public void loadData(Bundle savedInstanceState) {
this.mButtonBitmapCache.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loadImage(R.mipmap.bitmap_cache);
}
});
}
/**
* 将bitmap添加到内存中和Disk缓存中
*
* @param _key
* @param _bitmap
*/
public void addBitmapToCache(String _key, Bitmap _bitmap) {
if (null == getBitmapFromMemoryCache(_key))
mMemoryCache.put(_key, _bitmap);
synchronized (mDiskCacheLock) {
try {
if (null != mDiskLruCache && null == mDiskLruCache.get(_key)) {
DiskLruCache.Editor _editor = mDiskLruCache.edit(_key);
//将bitmap写入到outputstream中去
_bitmap.compress(Bitmap.CompressFormat.JPEG, 30, _editor.newOutputStream(0));
//提交
_editor.commit();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 获取内存中的bitmap
*
* @param _key
* @return
*/
public Bitmap getBitmapFromMemoryCache(String _key) {
return mMemoryCache.get(_key);
}
/**
* 获取disk中的缓存
*
* @param _key
* @return
*/
public Bitmap getBitmapFromDiskCache(String _key) {
Bitmap _bitmap = null;
while (mDiskCacheStaring) {
try {
//mDiskLruCache没有初始化的时候,不能进行获取流
mDiskCacheLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (null != mDiskLruCache) {
try {
DiskLruCache.Snapshot _snapShot = mDiskLruCache.get(_key);
if (null != _snapShot) {
//将获取到的inputstream转为bitmap
InputStream _is = _snapShot.getInputStream(0);
_bitmap = BitmapFactory.decodeStream(_is);
return _bitmap;
}
} catch (IOException e) {
e.printStackTrace();
}
}
return _bitmap;
}
/**
* 点击按钮加载图片
*
* @param _res
*/
public void loadImage(int _res) {
String _resKey = String.valueOf(_res);
Bitmap _memoryBitmap = getBitmapFromMemoryCache(_resKey);
Bitmap _diskBitmap = getBitmapFromDiskCache(_resKey);
if (null != _memoryBitmap) {
mImageBitmapCache.setImageBitmap(_memoryBitmap);
} else if (null != _diskBitmap) {
mImageBitmapCache.setImageBitmap(_diskBitmap);
} else {
BitmapWorkerTask _bitmapWorkerTask = new BitmapWorkerTask();
_bitmapWorkerTask.execute(_res);
}
}
/**
* 使用AsyncTask来异步对图片的处理
*/
public class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
@Override
protected Bitmap doInBackground(Integer... params) {
Bitmap _bitmap = getResourceBitmap(getResources(), params[0], 200, 100);
addBitmapToCache(String.valueOf(params[0]), _bitmap);
return _bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (null != bitmap)
mImageBitmapCache.setImageBitmap(bitmap);
}
}
/**
* 获取缩放之后的图片Bitmap
*
* @param _resources
* @param _res
* @param _reWidth
* @param _reHeight
* @return
*/
public Bitmap getResourceBitmap(Resources _resources, int _res, int _reWidth, int _reHeight) {
BitmapFactory.Options _options = new BitmapFactory.Options();
//设置为true则解码器不计算
_options.inJustDecodeBounds = true;
//解码图片资源
BitmapFactory.decodeResource(_resources, _res, _options);
//根据图片的信息进行计算缩放比例
_options.inSampleSize = this.calculateInSampleSize(_options, _reWidth, _reHeight);
//设置为false则解码器开始计算
_options.inJustDecodeBounds = false;
//返回解码缩放之后的bitmap
return BitmapFactory.decodeResource(_resources, _res, _options);
}
/**
* 获取缩放的比例
*
* @param _options
* @param _reWidth
* @param _reHeight
* @return
*/
public int calculateInSampleSize(BitmapFactory.Options _options, int _reWidth, int _reHeight) {
//获取图片的高和宽
int _width = _options.outWidth;
int _height = _options.outHeight;
int _inSampleSize = 1;
//计算缩放比例,inSampleSize>1说明图片是被压缩的
if (_width > _reWidth || _height > _reHeight) {
int _halfWidth = _width / 2;
int _halfHeight = _height / 2;
while ((_halfWidth / _inSampleSize) > _reWidth && (_halfHeight / _inSampleSize) > _reHeight) {
_inSampleSize *= 2;
}
}
return _inSampleSize;
}
/**
* 获取磁盘缓存的地址,如果有SD卡,则创建SD卡上的缓存目录,如果没有SD卡,则创建内部缓存目录
*
* @param _context
* @param _name
* @return
*/
public File getDiskCacheDir(Context _context, String _name) {
String _cachePath;
//判断SD卡是否挂载和SD卡非移除状态
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) {
_cachePath = _context.getExternalCacheDir().getPath();
} else {
_cachePath = _context.getCacheDir().getPath();
}
return new File(_cachePath + File.separator + _name);
}
/**
* 在子线程中初始化DiskLruCache对象
*/
public class InitDiskCache implements Runnable {
@Override
public void run() {
synchronized (mDiskCacheLock) {
try {
mDiskLruCache = DiskLruCache.open(mCacheDir, 1, 1, DISK_CACHE_SIZE);
mDiskCacheStaring = false;
mDiskCacheLock.notifyAll();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}