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


    0.前言  

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


    6. 集合类导致内存泄漏

    很常见的一个例子就是图片的三级缓存结构,为了更好的用户体验,缓存机制必不可少,三级缓存分别为网络缓存,本地缓存以及内存缓存。在内存缓存逻辑类中,通常会定义这样的集合类。

    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;
      }
    }; 
    

    7. Bitmap优化

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

    对于图片,当然也可以使用压缩以及回收的策略来尽量避免内存溢出。


    7. 1 Bitmap压缩

    压缩即把图片的体积缩小,一方面可以减小APK的大小,另一方面就是将图片加载入内存后减少内存的占用,从而间接地减少内存溢出的可能性。对部分图片压缩的知识已经在Android开发——减小APK大小中介绍过了,这里就不再赘述。

    这里主要说一下通过设置参数进行压缩的方法。使用BitmapFactory.Options设置inSampleSize(表示缩略图大小为原始图片大小的几分之一)就可以缩小图片。如果值为2,则缩略图的宽和高都是原始图片的1/2,图片的大小就为原始大小的1/4(小于等于1不缩放)。

    使用BitmapFactory.Options设置inJustDecodeBoundstrue后,再使用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  
        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));  
    


    7. 2 Bitmap回收

    2.3以下的系统中Bitmap像素数据存储在native中,Bitmap对象存储在Java中的,所以在回收Bitmap时,需要回收两个部分的空间:nativeJava堆。 即先调用recycle()释放nativeBitmap的像素数据,再对Bitmap对象置null以保证GCBitmap对象的回收

    if(bitmap != null && !bitmap.isRecycled()){ 
            // 回收并且置为null
            bitmap.recycle(); 
            bitmap = null; 
    } 
    System.gc();//并不能保证立即开始回收,而是加快回收的到来
    

    3.0以上的系统中Bitmap的像素数据和对象本身都是存储在Java堆中的,无需主动调用recycle(),只需将对象置null,由GC自动管理


    8. 属性动画导致内存泄漏

    Android3.0开始支持的属性动画中有一类无限循环的动画,它会通过View间接持有Activity的引用,如果没有在onDestroy中停止动画,就会泄漏当前的Activity

     

    解决方案:

    onDestroy方法中调用animator.cancel();来停止动画。


    9.资源未关闭导致内存泄漏

    当我们打开资源时,一般都会使用缓存。比如读写文件资源、打开数据库资源等等。当我们不再使用时,应该及时地关闭它们,使得缓存内存区域及时回收。虽然有些对象,如果我们不去关闭,它自己在finalize()函数中会自行关闭。但是这得等到GC回收时才关闭,这样会导致其在缓存中驻留一段时间。如果我们频繁的打开资源,内存泄漏带来的影响就比较明显了。

     

    解决方案:

    及时关闭我们不再使用的资源。比如查询数据库后没有关闭游标cursor、构造Adapter时没有使用convertView重用控件、使用Bitmap及时调用recycle()


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

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


  • 相关阅读:
    angular入门--绑定字符串
    mongodb安装与mongo vue的使用
    css3-pointer-events_demo
    面向对象的六大原则
    AutoMapper简明教程(学习笔记)
    jquery cookie的用法
    MVC 异常处理机制
    查询最近修改的脚本
    运行page页面时的事件执行顺序
    游标简单的使用
  • 原文地址:https://www.cnblogs.com/qitian1/p/6461543.html
Copyright © 2020-2023  润新知