android 关于listview bitmap导致的oom解决方案

来源:互联网 发布:淘宝怎么卖游戏币 编辑:程序博客网 时间:2024/06/04 06:20

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<cacheItemImg> cacheBitCol = new ArryList<cacheItemImg>();

/* 当前队列缓存的元素最大个数   @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虚拟机这些导致出现的问题。上述方案已经过实施。




0 0
原创粉丝点击