Android 不止是OOM!深入内存级别的反思与提高

OOM是Android 开发常见的bug,每当遇到这个,总是笑笑 表示手到擒来,

因为无非就是以下几种造成了OOM

1.Bitmap加载过多
如listview gridview等类似的viewgroup 需要展示大量的bitmap

解决方案也很老套了:

  1. convertview和viewholder结合,
  2. lrucache配合本地缓存,网络加载 得三级缓存机制
  3. 从单个bitmap入手 进行 压缩质量 压缩尺寸操作

2.一些流,指针没有关闭,释放,如 数据库操作的cursor,stream类有没有close?bitmap有没有recycler? 复杂的多级引用 不使用的时候 有没有置为空,亦或者 手动释放掉?

加班得末尾暂时想到这些情况,日后补充,这些不是重点

重点是今天遇到的一个bug,让我对自己平时的工作 进行了足够深刻的反思。

一个耐人寻味的bug

如图所示,log中打印出了在箭头所指之处造成的OOM异常,

  • 先说明一下是在什么情况下崩溃:
  • 跳转进入当前activity 的时候,直接崩溃,

  • bug发生前提得补充:而且跳转时候崩溃这个情况不是必然发生得,一般不会OOM,只是偶尔应用撑不住了才会OOM

  • 再解释一下 logOOM所指 该部分三行代码的意思:目的是使用viewpager创建 左右滑动的引导页,

  • for循环中 是为了生成包含bitmap的imageview,并且存储到 一个list集合中,viewpager 进行setAdapter加载适配的时候 需要用到list,这个大家都知道。

ok,为什么会出现在这里报错呢,

创建imageview对象会造成OOM崩溃?不至于,那是因为viewpager加载适配器?

可饮用viewpager 使用 container复用就足够了,只保留三页,剩下它自己就释放回收了,且不用考虑。

到底为什么会出现一个这样的错误呢? 居然是imageview.setBackgroundResource(resId);

  • 各位请按着我的引导,第二个箭头标记之处,

  • createFromResourceStream() 创建了一个跟资源文件有关的流,重点是流,

  • 接着往上看, BitmapFactory 必会的 decode系列 都跟stream有关,

  • 一看到流 想到释放的问题,但是BitmapFactory系列的stream是系统提供的api,肯定不可能由咱们手动释放,那会是什么问题呢,

  • 揭露答案前我也想了些许时间,因为是修改过其他的地方bug 才暴漏到这个问题的,其他代码不展示了,先说说这个地方的问题

一个小小的imageview为什么 会造成OOM?

思考之余,就在某一瞬间,我起我的大学老师杨顺民经常说的一句话:

创建了一个对象,申请了一个内存,啪啪啪。 ——杨顺民

对象,内存!

又突然想起 前几天整理笔记的一句话

如果分配出去的内存得不到释放,及时回收,就会引起系统运行速度下降,甚至导致程序瘫痪

这里是imageview的问题,但是单纯imageview 占用的内存并不大,大的原因是 从resource中加载 图片资源时,启动了流去加载,造成了字节数 溢出,表现为 OOM异常,程序崩溃

解决办法也很简单了,删除这些消耗内存的使用 或者 去检查其他地方的代码有没有内存泄露, 这里crash 是因为 内存不够用,不一定必然崩溃,so 得保证 项目 整体 不要出现过多的内存泄露。
PS:对于必须使用imageview setImageSource 的情况,不可避免申请内存, 那我也提供一种解决方案 在本篇文章得 案例部分

今日总结关键字之字节数

这里提炼出一个关键字,也是我今天才意识到,想起来的—-字节数
【其实这个bug之前 表现得事bytearrystream 什么什么的异常,我忘记了,改了改,改到这里,暴露出imageview的问题】

字节数 才是内存的根本,我们new 出来的对象,在内存区域中开辟了一个空间 给这个对象,它就占用了内存,内存的形式 应该就是字节形式【计算机中数据存储 都是以 字节存在的】

所以今天乃至之前经常看到得bytearraystream 之类的bug 就是因为Android 系统分配给应用的内存【内存大小其实就是 字节数量】不够用了,所以项目使用图片越多,对于一些细节应该扣得越细,之前做其他项目的琐碎的经验 以及 对内存管理掌握得不到位,导致我没有意识到字节数这个根本的问题。

还好 苦口婆心得 杨老师提醒了我哈哈。

可能这个问题对于大多数有经验的人来说 手到擒来,对于我 也没有费太多时间,但是 思考过程大于结果,学习就是不断思考的过程,哪怕是错的,都会有收获。

总结经验:

1.内存根本就是字节,OOM 就是要抠代码细节,对于每个对象 是否会造成内存申请过大 没有释放造成泄露 都要铭记于心

更新一个追踪内存的案例

今天做项目,发现需要显示一张超大图片,处理过后,还有561Kb

 加载的时候,就crash --- OOM

 shortMsg:java.lang.OutOfMemoryError

longMsg:java.lang.OutOfMemoryError: bitmap size exceeds VM budget

 stackTrace:java.lang.OutOfMemoryError: bitmap size exceeds VM budget
 at android.graphics.Bitmap.nativeCreate(Native Method)
 at android.graphics.Bitmap.createBitmap(Bitmap.java:477)
 at android.graphics.Bitmap.createBitmap(Bitmap.java:444)
 at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:349)
 at android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:512)
 at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:487)
 at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:336)
  代码如下:
 detailView=(ImageView)findViewById(R.id.detailView);

      detailView.setBackgroundResource(R.drawable.more_info);//this line will lead to OOM 
   换成这种:
      detailView.setImageResource(R.drawable.more_info); //也同样会OOM
   后来找到了solution:

        /** * 以最省内存的方式读取本地资源的图片 * @param context *@param resId * @return */  
    public static Bitmap readBitMap(Context context, int resId){  
        BitmapFactory.Options opt = new BitmapFactory.Options();  
        opt.inPreferredConfig = Bitmap.Config.RGB_565;   
       opt.inPurgeable = true;  
       opt.inInputShareable = true;  
          //获取资源图片 
       InputStream is = context.getResources().openRawResource(resId);  
           return BitmapFactory.decodeStream(is,null,opt);  
    }
 取得bitmap之后,再 detailView.setImageBitmap(pdfImage); 就ok了!   

那是为什么,会导致oom呢:

原来当使用像 imageView.setBackgroundResource,imageView.setImageResource, 或者
BitmapFactory.decodeResource 这样的方法来设置一张大图片的时候,

这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存。

因此,改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的
source,decodeStream最大的秘密在于其直接调用JNI>>nativeDecodeAsset()来完成decode,无需再使用java层的createBitmap,从而节省了java层的空间。如果在读取时加上图片的Config参数,可以跟有效减少加载的内存,从而跟有效阻止抛out
of Memory异常。

    另外,需要特别注意:

decodeStream是直接读取图片资料的字节码了, 不会根据机器的各种分辨率来自动适应,使用了decodeStream之后,需要在hdpi和mdpi,ldpi中配置相应的图片资源,否则在不同分辨率机器上都是同样大小(像素点数量),显示出来的大小就不对了。

下面 配合案例,修改了自己使用res资源创建裁剪bitmap 的函数


    /** * 传入path,根据view的宽高来获取图片缩略图 * @param path 注意是资源id哟,不是string 路径 * @param viewWidth * @param viewHeight * @return * * 11/18 修改为BitmapFactory.decodeStream * 内存 每次少占用了10M左右, */
    private Bitmap decodeThumbBitmapForFile(int path, int viewWidth, int viewHeight){
        BitmapFactory.Options options = new BitmapFactory.Options();
        //表示解析bitmap,但不占用内存
        options.inJustDecodeBounds = true;
        InputStream is = mContext.getResources().openRawResource(path);
        BitmapFactory.decodeStream(is, null, options);
// BitmapFactory.decodeResource(mContext.getResources(),path);
        //设置缩放比率,
        options.inSampleSize = computeScale(options, viewWidth, viewHeight);

        //设置false,加入到内存中
        options.inJustDecodeBounds = false;

        return BitmapFactory.decodeStream(is, null, options);
    }

调用了computeScale函数,那我也干脆贴上来吧

    /** * ¸根据view的宽高计算bitmap缩放比例 * @param options */

    private int computeScale(BitmapFactory.Options options, int viewWidth, int viewHeight){
        int inSampleSize = 1;
        if(viewWidth == 0 || viewHeight == 0){
            return inSampleSize;
        }
        int bitmapWidth = options.outWidth;
        int bitmapHeight = options.outHeight;

         //假如Bitmap的宽度或高度大于我们设定图片的View的宽高,则计算缩放比例
        if(bitmapWidth > viewWidth || bitmapHeight > viewWidth){
            int widthScale = Math.round((float) bitmapWidth / (float) viewWidth);
            int heightScale = Math.round((float) bitmapHeight / (float) viewWidth);

            //为了保证图片不缩放变形,我们取宽高比例最小的那个
            inSampleSize = widthScale < heightScale ? widthScale : heightScale;
        }
        return inSampleSize;
    }

测试高清大图的时候,adapter中 每个imageview 申请 的内存减弱了10M左右!提升效果很明显!

over!

你可能感兴趣的:(android,oom,bug,解决方案,缓存机制)