• (四十二)、加载大分辨率图片到内存


    有些图片的分辨率比较高,把它直接加载到手机内存中之后,会导致堆内存溢出的问题,下面就讲解一下Android的堆内存以及如何在Android应用中加载一个高分辨率的图片的方法。

    1  还原堆内存溢出的错误
    首先来还原一下堆内存溢出的错误。首先在SD卡上放一张照片,分辨率为(3776 X 2520),大小为3.88MB,是我自己用相机拍的一张照片。应用的布局很简单,一个Button一个ImageView,然后按照常规的方式,使用BitmapFactory加载一张照片并使用一个ImageView展示。
    代码如下:

    btn_loadimage.setOnClickListener(new View.OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    Bitmap bitmap=BitmapFactory.decodeFile("/sdcard/a.jpg");
                    iv_bigimage.setImageBitmap(bitmap);
                }
    }

    当点击按钮后,程序会报错,查看日志为:


    先来分析一下这个错误,首先dalvikvm(Android虚拟机)发现需要的内存38MB大于应用的堆内存24MB,这个时候尝试使用软加载的方式加载数据,我们知道当内存不足的时候dalvikvm会自动进行GC(Garbage Collection),大概清理了55k的空间出来,耗时203毫秒,但是内存还是不够,所以最后发生堆内存溢出的错误。

    2、分析堆内存溢出

    Android系统主要用于低能耗的移动设备,所以对内存的管理有很多限制,一个应用程序,Android系统缺省会为其分配最大16MB(某些机型是24MB)的空间作为堆内存空间,我这里使用的模拟器调试的,这个模拟器被设定为24MB,可以在Android Virtual Device Manager中查看到。

    而这里的图片明明只有3.88MB,远远小于Android为应用分配的堆内存,而加载到内存中,为什么需要消耗大约38MB的内存呢?
    我们都知道,图片是由一个一个点分布组成的(分辨率),通常加载这类数据都会在内存中创建一个二维数组,数组中的每一项代表一个点,而这个图片的分辨率是3776 * 2520,每一点又是由ARGB色组成,每个色素占4个Byte,所以这张图片加载到内存中需要消耗的内存为:
    3776 * 2520 * 4byte = 38062080byte
    大约需要38MB的内存才能正确加载这张图片,这就是上面错误描述需要38MB的内存空间,大小略有出入,因为图片还有一些Exif信息需要存储,会比仅靠分辨率计算要大一些。

    3、如何加载大分辨率图片
    有时候我们确实会需要加载一些大分辨率的图片,但是对于移动设备而言,哪怕加载能成功那么大的内存也是一种浪费(屏幕分辨率限制),所以就需要想办法把图片按照一定比率压缩,使分辨率降低,以至于又不需要耗费很大的堆内存空间,又可以最大的利用设备屏幕的分辨率来显示图片。这里就用到一个BitmapFactory.Options对象,下面来介绍它。
    BitmapFactory.Options为BitmapFactory的一个内部类,它主要用于设定与存储BitmapFactory加载图片的一些信息。下面是Options中需要用到的属性:
    inJustDecodeBounds:如果设置为true,将不把图片的像素数组加载到内存中,仅加载一些额外的数据到Options中。
    outHeight:图片的高度。
    outWidth:图片的宽度。
    inSampleSize:如果设置,图片将依据此采样率进行加载,不能设置为小于1的数。例如设置为4,分辨率宽和高将为原来的1/4,这个时候整体所占内存将是原来的1/16。

    示例Demo

    下面通过一个简单的Demo来演示上面提到的内容,代码中注释比较清晰,这里就不再累述了。

    package cn.bgxt.loadbigimg;
    
    import android.os.Bundle;
    import android.os.Environment;
    import android.app.Activity;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.BitmapFactory.Options;
    import android.view.Menu;
    import android.view.View;
    import android.view.WindowManager;
    import android.widget.Button;
    import android.widget.ImageView;
    
    public class MainActivity extends Activity {
        private Button btn_loadimage;
        private ImageView iv_bigimage;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            btn_loadimage = (Button) findViewById(R.id.btn_loadimage);
            iv_bigimage = (ImageView) findViewById(R.id.iv_bigimage);
    
            btn_loadimage.setOnClickListener(new View.OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    // Bitmap bitmap=BitmapFactory.decodeFile("/sdcard/a.jpg");
                    // iv_bigimage.setImageBitmap(bitmap);
    
                    BitmapFactory.Options opts = new Options();
                    // 不读取像素数组到内存中,仅读取图片的信息
                    opts.inJustDecodeBounds = true;
                    BitmapFactory.decodeFile("/sdcard/a.jpg", opts);
                    // 从Options中获取图片的分辨率
                    int imageHeight = opts.outHeight;
                    int imageWidth = opts.outWidth;
    
                    // 获取Android屏幕的服务
                    WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
                    // 获取屏幕的分辨率,getHeight()、getWidth已经被废弃掉了
                    // 应该使用getSize(),但是这里为了向下兼容所以依然使用它们
                    int windowHeight = wm.getDefaultDisplay().getHeight();
                    int windowWidth = wm.getDefaultDisplay().getWidth();
    
                    // 计算采样率
                    int scaleX = imageWidth / windowWidth;
                    int scaleY = imageHeight / windowHeight;
                    int scale = 1;
                    // 采样率依照最大的方向为准
                    if (scaleX > scaleY && scaleY >= 1) {
                        scale = scaleX;
                    }
                    if (scaleX < scaleY && scaleX >= 1) {
                        scale = scaleY;
                    }
    
                    // false表示读取图片像素数组到内存中,依照设定的采样率
                    opts.inJustDecodeBounds = false;
                    // 采样率
                    opts.inSampleSize = scale;
                    Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/a.jpg", opts);
                    iv_bigimage.setImageBitmap(bitmap);
    
                }
            });
        }
    }

    效果展示:

    4、总结
    这里讲解了如何加载一个大分辨率的图片到内存中并使用它。不过一般好一点的图片处理软件,都会有图片放大功能,如果仅做此处理,单纯的把处理后的图片放大,会影响显示效果,图片还原度不高。一般会重新获取放大区域的图片的分辨率像素数组,然后重新处理加载到内存中进行显示。

    5、转载自http://www.jb51.net/article/43462.htm

     
  • 相关阅读:
    java8 查找字符串中首次出现2次的字母
    java8 stream编程
    详解--从地址栏输入url到页面展现中间都发生了什么?
    前端aes解密实战小结
    使用高德地图开发需要注意的一些点
    vue-cli项目打包优化(webpack3.0)
    3种web会话管理方式
    web缓存之--http缓存机制
    javascript创建对象的几种方式
    对原型链、闭包的理解
  • 原文地址:https://www.cnblogs.com/fuyanan/p/4267480.html
Copyright © 2020-2023  润新知