android 加载图片oom异常

来源:互联网 发布:nginx 缓存mp4文件 编辑:程序博客网 时间:2024/04/27 13:38

一、OOM异常是什么?产生OOM异常的原因

        OOM(Out Of Memory--内存不够用了)

        a、计算图片占用的内存大小:

                  1、 占用内存 = 图片的长度 * 图片的宽度 * 单位像素占用的字节数;

                  2、 单位像素占用的字节数是由BitmapFactory.Options的inPreferredConfig变量的值决定的:

                        A(alpha)--透明度,R(red)--红色,G(green)--绿色,B(blue)--蓝色,即三原色;如一张图片是720*1080,ARGB是一

                        种彩模式:  

                        ① Bitmap.Config.ALPHA_8: 此时图片就只有alpha值,一个像素就只占用一个字节(8代表的是这个透明度占用8

                             位,即一个字节),图片占用的内存大小:720*1080*1(字节)

                        ② Bitmap.Config.ARGB_4444: 即A、R、G、B 各占四个bit,总共就是4*4=16个bit= 2(字节),这种格式的图片看

                             起来质量很差,已经不再使用, 图片占用的内存大小:720*1080*2(字节)

                        ③ Bitmap.Config.ALPHA_8888: 即A、R、G、B各占8个bit,总共就是4*8=32bit = 4(字节),这是一种高质量的图

                            片,android手机上Bitmap默认的格式,图片占用的内存:720*1080*4(字节)

                        ④ Bitmap.Config.RGB_565 : 即 R、G、B 分别占用5、6、5个bit,总共:5+6+5=16bit=2(字节),这种格式的图片,

                             不支持透明和半透明,图片质量也达到了效果,且占用内存相对来说较小,图片占用内存:720*1080*2(字节)

      

          b、  一个应用程序,android系统一般会为它分配16MB的内存大小,,例如:三星s4后置摄像头像素1300万,则拍照后,照片

                 的大小就是1300万*4(字节)(假设单位像素的字节数按4个字节),即49.6MB,远远超过了android系统分配给这个应

                 用的内存,且这16MB不仅仅是用来存储图片的,所以如果不处理图片,直接加载图片的话,就会出现OOM异常。这是

                 android开发中最常见的OOM异常。

二、解决OOM异常的方法:

           a、改变图片的大小,即缩小图片,缩小图片的方法:

                 方法一:利用Matrix来缩小图片

 public Bitmap zoomBitmap(Bitmap bitmap) {        //获得Bitmap的高和宽        int bmpWidth = bitmap.getWidth();        int bmpHeight = bitmap.getHeight();        //这边假设imageView的宽和高是一样的        int imageViewHeight = imageViewWidth;        //设置缩小比例,imageViewWidth 就是我们需要的宽度        double scale = (double) imageViewWidth / bmpWidth;        double scaleH = (double) imageViewHeight/bmpHeight;                //为了保证图片可以显示完全        if (scale>scaleH){            scale = scaleH;        }        Log.e("缩放比例:", "scale = " + scale);        //计算出长宽要缩放的比例        float scaleWidth = (float) (1 * scale);        float scaleHeight = (float) (1 * scale);        //产生resize后的Bitmap对象        Matrix matrix = new Matrix();        matrix.postScale(scaleWidth, scaleHeight);        Bitmap resizeBmp = null;        resizeBmp = Bitmap.createBitmap(bitmap, 0, 0, bmpWidth, bmpHeight, matrix, true);        Log.e("缩放后的图片的宽度:bitmapWidth=", "" + resizeBmp.getWidth());        //修改色彩模式        Bitmap zoomBitmap = resizeBmp.copy                (Bitmap.Config.RGB_565,false);        return zoomBitmap;    }
              优点:可以按任何比例来缩放,即缩放比例可以是小数;而Options.inSampleSize 只能是整数。

              缺点:会耗费很大的内存,因为缩放后的图片的色彩模式是ARGB_8888,每个像素单位就是4个字节了;

                        解决办法:修改色彩模式,如代码所示,这样虽然会生成一个Bitmap对象,但是总的消耗的内存还是减小的。


             方法二:利用BitmapFactory.Options进行缩放:

 private Bitmap getimage(String srcPath) {        BitmapFactory.Options newOpts = new BitmapFactory.Options();        //开始读入图片,此时把options.inJustDecodeBounds 设回true了        newOpts.inJustDecodeBounds = true;        BitmapFactory.decodeFile(srcPath, newOpts);//此时返回bitmap为空,只有一些图片大小等信息;
      
        //这时候设置false ,返回的bitmap就不是空的了
        newOpts.inJustDecodeBounds = false;        int w = newOpts.outWidth;        int h = newOpts.outHeight;
        //imageViewWidth和imageViewHeight是我们希望的宽和高        int hh = imageViewWidth;        int ww = imageViewWidth;
        //缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可        int be = 1;//be=1表示不缩放        if (w > h && w > ww) {//如果宽度大的话根据宽度固定大小缩放            be = (int) (newOpts.outWidth / ww);        } else if (w < h && h > hh) {//如果高度高的话根据宽度固定大小缩放            be = (int) (newOpts.outHeight / hh);        }
        //be不能是小数,必须是整型的        if (be <= 0)            be = 1;        newOpts.inSampleSize = be;//设置缩放比例        //重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了        bitmap = BitmapFactory.decodeFile(srcPath, newOpts);             //直接返回,不用质量压缩也可以展示        return bitmap;    }
             优点:可以通过options.inJustDecodeBounds=true,来节省内存的开销

             缺点:options.inSampleSize只能是2的整数次幂,如果不是的话,就向下取最大的2的整数次幂

       b、改变图片的质量,但是这样会造成图片看起来不清晰:   

private Bitmap compressQualityImage(Bitmap image) {        ByteArrayOutputStream baos = new ByteArrayOutputStream();        //100代表不压缩图片,把图片保存到baos中        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);        int options = 100;    
        //500KB 就是我们能用的最大内存空间了        while (baos.toByteArray().length / 1024 > 500) {            //清空baos            baos.reset();            options -= 10;            if (options < 0) {                break;            }            image.compress(Bitmap.CompressFormat.JPEG, options, baos);        }        //把压缩后的图片放在inBm中        ByteArrayInputStream inBm = new ByteArrayInputStream(baos.toByteArray());        //生成图片        Bitmap bitmap = BitmapFactory.decodeStream(inBm, null, null);        return bitmap;    }
        c、目前我知道的最优方案,实际应用中也高高的

              分析:当我们使用BitmapFactory.decodeResource()、decodeFile()等方法时,这些方法最终都是通过java层的

                          createBitmap()方法来生成Bitmap的,这样就会消耗很多的内存;因此我们如果用BitmapFactory.decodeStream()来生成一个Bitmap对象时,无需调用java层的

                          createBitmap()方法,而是直接调用JNI的nativeDecodeAsset()方法来完成,节省很多的内存空间,如果再加上图片的Config(色彩模式)参数(最好用

                          Bitmap.Config.RGB_565)这样就更加有效地减少的内存的开销。

               代码:

/**     *相对来说最优生成Bitmap的方法     * @param context     * @param resId   图片的id,一般在drawable下的图片,代码中也有如何根据图片路径来构造流     * @return     */    public static Bitmap readBitmap(Context context, int resId) {        BitmapFactory.Options opt = new BitmapFactory.Options();        //最低位的配置        opt.inPreferredConfig = Bitmap.Config.RGB_565;        //inPurgeable设为true的话表示使用BitmapFactory创建的Bitmap用于存储Pixel的内存空间在系统内存不足时可以被回收        opt.inPurgeable = true;        //是否深拷贝???        opt.inInputShareable = true;        //1、drawable下的图片构造流的方法        InputStream is =                context.getResources().openRawResource(resId);        //2、根据路径构造流的方法        try {            String picturePath = "..........";            File file = new File(picturePath);            InputStream inputStream = new FileInputStream(file);        }catch (Exception e){            e.printStackTrace();        }        return BitmapFactory.decodeStream(is, null, opt);    }
          d、注意:只用一次的Bitmap的记着回收,代码如下:

       if(!bitmap.isRecycled()){            bitmap.recycle();//回收图片所占的内存            bitmap = null;            System.gc();//提醒系统及时回收        }


三、上面知识都是我的理解,肯定有很多不足,我们可以去学习下Fresco: https://github.com/facebook/fresco
 

参考资料:http://www.cnblogs.com/plokmju/p/android_loadbigimage.html         

0 0
原创粉丝点击