2015年7月份开始做android项目(之前是做Unix*c和平台研发)。项目是cs模式,使用http post通讯,其中有文章列表,图片是服务器端的,使用listview展示。某一天因为服务器端数据重复,观察app内存使用率时突然发现报oom错误,最后查了一下是bitmap释放不干净,这是异常现场。
观察了一下堆区,在加载新图片时,堆区占用一直持续增长,查了一下发现解释是说 android的bitmap 是一部分用c开辟的,要释放要用bitmap的recyced。因为bitmap没有释放,引用还在被view的imageview抓着,所以每次listview滑动加载新item时都会创建新的view不会循环使用旧的view,当然bitmap也不会释放。这就是导致oom的原因。知道了原因,之后就做了一个三层的缓存(说是三层,实际就2层,我把第1层屏幕正在显示的认为是在显存中)。具体如下:
两层缓存是 内存和本地磁盘,内存存放当前正在显示图片(同时缓存一部分+n 和 -n 位置的图片),磁盘存放已下载的图片。关于内存是做一个队列结构。在适配器的getview和lisview的onscrolled中做以下处理:先查询内存队列中是否有当前item的缓存图片,如果有则使用,如果没有则读取本地图片加载到队列同时将图片bitmap返回,这些处理之后都要做----遍历当前队列缓存,如果当前缓存图片个数超出则释放队列头/尾的bitmap资源,关于释放头还是尾可以根据当前滑动的方向判断。
具体代码demo如下:
/* 适配器中 */
adpter :
/* 队列缓存元素类 @2016.01.22 by djy */
public class cacheItemImg{
/* 元素编号 */
int itemId = null ;
Bitmap itemImag = null ;
public cacheItemImg ( int _id , Bitmap _bit ){
itemId = _id ;
itemImag = _bit ;
}
}
/* 图片缓存队列 用来存储当前需要放在内存中的位图集合 @2016.01.22 by djy */
private List
/* 当前队列缓存的元素最大个数 @2016.01.22 by djy*/
private int MAX_CACHE = 5;
/**********************
*
* function : 从缓存中获取指定id的图片位图
*
* item_id :元素编号
* img_path :对应图片的本地路径
*
*********************/
private cacheItemImag getCacheBit(int item_id,string img_path){
/* 清理掉超出缓存个数的图片,默认从队列头部清理,具体使用可以自定 */
/* 如果队列长度超出 */
while( cacheBitCol.size() > MAX_CACHE ){
/* 如果位图已实例化并且未被释放 则释放 */
if( null !=cacheBitCol.get(0).itemImag && !cacheBitCol.get(0).itemImag.isRecyced() ) cacheBitCol.get(0).itemImag.recyced();
/* 从队列清掉刚才释放的头部元素 */
cacheBitCol.Remove(cacheBitCol.get(0));
}
/* 遍历查询当前缓存队列是否存在 指定id的元素 */
foreach( cacheItemImg var : cacheBitCol ){
/* 如果找到 则返回对象 */
if( var.itemId == item_id ) return var;
}
/* 在缓存队列中没有,则从本地加载实例化改位图 */
cacheItemImag tmpCacheItem = new cacheItemImag( item_id , BitmapFactory.decodeStream(new FileInputStream(filePath),null, null) ) ;
/* 将位图加入队列缓存 */
cacheBitCol .Add( tmpCacheItem );
/* 将对象返回 */
return tmpCacheItem;
}
在geview 和 onscrolled中 关键代码: imageview.setBitmap( getCacheBit( viewItem.id , viewItem.imgPath ) );
ps:上述代码没有网络图片, 如果有网络图片,关键在异步,图片下载下来之后,当要设置图片时要先判断当前的 viewItem是否和当前下载的图片匹配。可以在getview时将imagView设置tag为id ,然后在下载完成异步设置图片时 判断当前 imageview tag的id是否一致,一致则设置,不一致则不做工。
上面只是自己实现的一个简单的demo,具体实施的较复杂,但理论是一致的,这个本身很简单,但因不太熟悉android开发及jvm虚拟机这些导致出现的问题。上述方案已经过实施。