[置顶] Android开发——常见的内存泄漏以及解决方案(二)

11581人阅读 评论(2) 收藏 举报
分类:

0.前言  

上一篇常见的内存泄漏以及解决方案(一) 中已经对部分可能会引发内存泄漏的情况进行了阐述,此篇将从图片、动画等资源角度介绍可能会造成内存泄漏的情况以及应对方法。


8.  用缓存避免内存泄漏

很常见的一个例子就是图片的三级缓存结构,分别为网络缓存,本地缓存以及内存缓存。在内存缓存逻辑类中,通常会定义这样的集合类。

private HashMap<String, Bitmap> mMemoryCache = new HashMap<String, Bitmap>();//String类为该图片对应url

三级缓存结构过程介绍:

在用户切换到展示图片的界面时,当然是优先判断内存缓存是否为Null,不为空直接展示图片,若为空,同样的逻辑去判断本地缓存(不为空便设置内存缓存并展示图片),本地缓存再为空才会根据该图片的url用网络下载类去下载该图片并展示图片(当然了,下载到图片后会有设置本地缓存以及内存缓存的操作)。

内存泄漏的问题就出现在内存缓存中:只要HashMap对象实例被引用,而Bitmap对象又都是强引用,Bitmap中图片越来越多,即便是内存溢出了,垃圾回收器也不会处理。


解决方案:

1)我们可以选择使用软引用,从而在内存不足时,垃圾回收器更容易回收Bitmap垃圾。

private HashMap<String, SoftReference<Bitmap>> mMemoryCache = new HashMap<String, SoftReference<Bitmap>>();

2Android2.3以后,SoftReference不再可靠。垃圾回收期更容易回收它,不再是内存不足时才回收软引用。那么缓存机制便失去了意义。

Google官方建议使用LruCache作为缓存的集合类其实内部封装了LinkedHashMap。内部原理是一直判断集合大小是否超出给定的最大值,超出就把最早最少使用的对象踢出集合

private LruCache<String, Bitmap> mMemoryCache = new LruCache<String, Bitmap>
((int)(Runtime.getRuntime().maxMemory()/8)){ 
//用最大内存的1/8分配给这个集合使用
//让这个集合知道每个图片的大小
@Override
protected int sizeOf(String key, Bitmap value){
int byteCount = value.getRowBytes() * value.getHeight();//计算图片大小,每行字节数*高度
return byteCount;
  }
}; 

9.  优化Bitmap避免内存泄漏

Android中很多控件比如ListView/GridView/ViewPaper通常都会包含很多图片,特别是快速滑动的时候可能加载大量的图片,因此对图片进行优化处理显得尤为重要。


9.1  图片质量压缩

public static Bitmap compressImage(Bitmap bitmap){  
    ByteArrayOutputStream baos = new ByteArrayOutputStream();  
    //质量压缩方法,参数100表示不压缩,把压缩后的数据存放到baos中  
    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);  
    int options = 100;  
    //循环判断如果压缩后图片大小>50kb就继续压缩  
    while ( baos.toByteArray().length/1024 > 50) {  
         //清空baos  
         baos.reset();  
         bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);  
         options -= 10;//每次都减少10  
    }  
    //把压缩后的数据baos存放到ByteArrayInputStream中  
    ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());  
    //把ByteArrayInputStream数据生成图片  
    Bitmap newBitmap = BitmapFactory.decodeStream(isBm, null, null);  
    return newBitmap;          
}


9.2  图片尺寸裁剪

如果说没有裁剪,下载下来是200*200,而ImageView本身是100*100的,具体原因可以参考这篇文章,这样就浪费了一部分内存。

使用BitmapFactory.Options设置inSampleSize就可以缩小图片。如果值为2,则缩略图的宽和高都是原始图片的1/2,图片的大小就为原始大小的1/4(小于等于1不缩放)。具体方法如下:


既然有了inSampleSize的概念,我们就要对比实际图片大小和ImageView控件的大小,如果使用内存直接处理实际图片的Bitmap从而得到实际大小的话,就失去了图片尺寸裁剪的意义,因为内存已经被消耗了。因此BitmapFactory.Options提供了inJustDecodeBounds标志位,当它被设置为true后,再使用decode系列方法时,并不会真正的分配内存空间,这样解码出来的Bitmapnull但是可以计算出原始图片的真实宽高,即options.outWidthoptions.outHeight。通过这两个值,就可以知道图片是否过大了。

BitmapFactory.Options options = new BitmapFactory.Options();  
options.inJustDecodeBounds = true;  
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);  
int imageHeight = options.outHeight;  
int imageWidth = options.outWidth;  
String imageType = options.outMimeType; 


这里提供了一个calculateInSampleSize()工具方法来帮我们根据实际情况动态计算合适的inSampleSize

public static int calculateInSampleSize( //参2和3为ImageView期待的图片大小
            BitmapFactory.Options options, int reqWidth, int reqHeight) {  
    // 图片的实际大小
    final int height = options.outHeight;  
    final int width = options.outWidth;  
    //默认值
    int inSampleSize = 1;  
    //动态计算inSampleSize的值
    if (height > reqHeight || width > reqWidth) {  
        final int halfHeight = height/2;
        final int halfWidth = width/2;
        while( (halfHeight/inSampleSize) >= reqHeight && (halfWidth/inSampleSize) >= reqWidth){
            inSampleSize *= 2;
        }
    }  
    return inSampleSize;  
}  


创建一个完整的缩略图方案:

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,  
        int reqWidth, int reqHeight) {  

    final BitmapFactory.Options options = new BitmapFactory.Options();  
    options.inJustDecodeBounds = true;  
    BitmapFactory.decodeResource(res, resId, options);  
  
    // 计算inSampleSize,因为前面已经设置过标志位并调用了decode方法,所以参数option包含了真实宽高信息
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);  
  
    // 别忘记将opts.inJustDecodeBound设置回false,否则获取的bitmap对象还是null 
    options.inJustDecodeBounds = false;  
    //重新加载图片
    return BitmapFactory.decodeResource(res, resId, options);  
}  


当我们在使用ImageView进行设置图片资源时:

mImageView.setImageBitmap( //ImageView所期望的图片大小为100*100像素
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));  


9.3  改变图片颜色模式


Android默认的颜色格式是ARGB_8888,在不要求透明度的情况下可以改成RGB_565,这样每个像素占用的内从可从4byte将为2byte。

一张分辨率为1920x1080的图片,如果Bitmap使用ARGB_8888格式显示的话,占用的内存将是1920x1080x4个字节,将近8M内存。


10.  及时回收资源

(1)当界面不可见时我们应当将所有和界面相关的资源进行释放。

我们可以在Activity中重写onTrimMemory()方法,通过switch这个方法中的level参数,判断它是不是等于TRIM_MEMORY_UI_HIDDEN,就说明所有UI组件被隐藏,此时就可以进行UI相关资源释放操作了,如下所示:

@Override  
public void onTrimMemory(int level) {  
    super.onTrimMemory(level);  
    switch (level) {  
    case TRIM_MEMORY_UI_HIDDEN:  
        // 进行资源释放操作  
        break;  
    }  
}  
比如Android3.0开始支持的属性动画中有一类无限循环的动画,它会通过View间接持有Activity的引用,如果没有在onDestroy中停止动画(animator.cancel()),就会泄漏当前的Activity。说起动画,还有一点就是减少帧动画的使用。

(2)Google也建议在onStop()方法中释放资源,但是和上面的释放UI资源是有区别的,因为onStop()方法只是当一个Activity不可见的时候就会调用,比如说用户打开了我们程序中的另一个ActivityB。在onStop()方法中适合去关闭一些读写文件的资源、网络连接、解注册广播接收者

但是像UI相关的资源应该一直要等到onTrimMemory(TRIM_MEMORY_UI_HIDDEN)这个回调之后才去释放,否则从ActivityB回到ActivityAUI相关的资源会重新加载。


至此关于Android内存泄漏的内容总结完毕。

转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52351062


5
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:1228529次
    • 积分:11929
    • 等级:
    • 排名:第1313名
    • 原创:137篇
    • 转载:27篇
    • 译文:2篇
    • 评论:461条
    个人说明