Android中大图片的处理(一)之图片存在形式

来源:互联网 发布:威锋认证的淘宝店 编辑:程序博客网 时间:2024/04/25 20:45

大家应该知道,我们编写的应用程序都是有一定内存限制的,程序占用了过高的内存就容易出现OOM(OutOfMemory)异常。图片来说更是OOM (OutOfMemoryError)的常见引发者,例如说系统图片库里展示的图片大都是用手机摄像头拍出来的,这些图片的分辨率会比我们手机屏幕的分辨率高得多。

Android中图片的存在形式

  • 1:文件形式:二进制形式存在与硬盘中。
  • 2:流的形式:二进制形式存在与内存中。
  • 3:Bitmap的形式

图片占用内存大小计算

前面两种形式对图片的体积影响并不大,重点是BitMap形式存在的图片,受单位像素字节数影响,往往容易使图片占用内存瞬间变大。以下我们讨论的图片就针对BitMap形式下存在的。

Android中一张图片(BitMap)占用的内存主要和以下三个因数有关:

图片长度图片宽度单位像素占用的字节数

一张图片(BitMap)占用的内存 = 图片长度 x 图片宽度 x 单位像素占用的字节数

注:图片长度和图片宽度的单位是像素。

创建一个BitMap时,其单位像素占用的字节数由其参数BitmapFactory.Options中的类型是Bitmap.Config的变量决定。

Bitmap.Config类是个枚举类型:

类型 描述 ALPHA_8 此时图片只有alpha值,没有RGB值,一个像素占用一个字节 ARGB_4444 一个像素占用2个字节,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各占4个bites,共16bites,即2个字节。这种格式的图片,看起来质量太差,已经不推荐使用。 ARGB_8888 一个像素占用4个字节,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各占8个bites,共32bites,即4个字节。这是一种高质量的图片格式,电脑上普通采用的格式。它也是Android手机上一个BitMap的默认格式。 RGB_565 一个像素占用2个字节,没有alpha(A)值,即不支持透明和半透明,Red(R)值占5个bites ,Green(G)值占6个bites ,Blue(B)值占5个bites,共16bites,即2个字节.对于没有透明和半透明颜色的图片来说,该格式的图片能够达到比较的呈现效果,相对于ARGB_8888来说也能减少一半的内存开销。因此它是一个不错的选择。

注:ARGB指的是一种色彩模式,里面A代表Alpha,R表示red,G表示green,B表示blue

图片格式(Bitmap.Config) 占用内存的计算方向 以一张100*100的图片占用内存的大小为例 ALPHA_8 图片长度*图片宽度 100*100=10000字节 ARGB_4444 图片长度*图片宽度*2 100*100*2=20000字节 ARGB_8888 图片长度*图片宽度*4 100*100*4=40000字节 RGB_565 图片长度*图片宽度*2 100*100*2=20000字节

举个例子:
3000*2000 像素ARGB_8888类型的大图片
存储在sdcard的大小是3M
加载到内存的大小是3000*2000*4*8(bit) /8/1024/1024 =24(MB)
这也是为什么大图片加载时容易造成OOM的原因

对图片处理的方式

根据使用的场景不同对图片进行处理。
(1)减少表示一个像素所需要的字节数:系统默认采用ARGB_8888,如果对透明度和色值范围要求不高的情况下,可以采用RGB_565,或者Bitmap.Config提供的其它选项,但是这种修改带来的效果有限;
(2)降低图片分辨率(边界压缩):BitmapFactory加载图片时,BitmapFactory.Options设置inSampleSize可以显著减少图片占用的内存大小,当然也是降低了图片的分辨率。inSampleSize为2时,其实内存占用是原来的四分之一

直接上代码
布局就不贴了,一个button作为显示图片的开关

 @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mIvIcon = (ImageView) findViewById(R.id.iv_icon);    }    public void showImage(View view) {        String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/big.JPG";        Bitmap scaleBitmap = BitmapUtil.getScaleBitmap(path, this);        mIvIcon.setImageBitmap(scaleBitmap);//设置缩放后的大图片    }
/** * 大图片处理工具类 */public class BitmapUtil  {    private static final String TAG = BitmapUtil.class.getSimpleName();    private static Bitmap bitmap;    //取得缩放的图片    public static Bitmap getScaleBitmap(String filePath, Context context) {        //生成bitmap的配置对象        BitmapFactory.Options  options = new BitmapFactory.Options();        options.inJustDecodeBounds=true;//仅仅取得边界属性,度量图片的实际宽度和高度        bitmap = BitmapFactory.decodeFile(filePath, options);        //取得要转bitmap的图片的属性(图片的宽和高)        int outHeight = options.outHeight;        int outWidth = options.outWidth;        //取得屏幕的宽高        int heightPixels = context.getResources().getDisplayMetrics().heightPixels;        int widthPixels = context.getResources().getDisplayMetrics().widthPixels;        if (outWidth > widthPixels || outHeight > heightPixels) {            //缩放比例, 会根据机器的各种分辨率来自动适应            int sizeX = outWidth / widthPixels;            int sizeY = outHeight / heightPixels;            //设置采用的比例            options.inSampleSize = sizeX > sizeY ? sizeX : sizeY;        }else {            options.inSampleSize = 1;        }        Log.d("TAG:"+TAG,"图片的宽和高"+outWidth+"---"+outHeight+"\n" +                "屏幕的宽和高"+widthPixels+"---"+heightPixels+"\n"+                " 缩放size"+options.inSampleSize);        options.inJustDecodeBounds=false;//最后把标志复原        //设置加载图片的颜色数为16bit,默认是RGB_8888,表示24bit颜色和透明通道,但一般用不上        options.inPreferredConfig = Bitmap.Config.RGB_565;        bitmap = BitmapFactory.decodeFile(filePath, options);        return bitmap;    }}

合理的方式创建Bitmap对象

BitmapFactory这个类提供了多个解析方法(decodeByteArray, decodeFile, decodeResource等)用于创建Bitmap对象,我们应该根据图片的来源选择合适的方法。

比如SD卡中的图片可以使用decodeFile方法
网络上的图片可以使用decodeStream方法
资源文件中的图片可以使用decodeResource方法

这些方法会尝试为已经构建的bitmap自动按系统的方式分配内存,这时就会很容易导致OOM出现。为此每一种解析方法都提供了一个可选的BitmapFactory.Options参数,将这个参数的inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存,返回值也不再是一个Bitmap对象,而是null。虽然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType(图片类型)属性都会被赋值。
这个技巧让我们可以在加载图片之前就获取到图片的长宽值和MIME类型,从而根据情况对图片进行压缩。如下代码所示:

BitmapFactory.Options options = new BitmapFactory.Options();   options.inJustDecodeBounds = true;   BitmapFactory.decodeResource(getResources(), R.id.image, options);   //取得图片相应的参数值int imageHeight = options.outHeight;   int imageWidth = options.outWidth;   String imageType = options.outMimeType; 

确切的知道一张图片的宽高和图片类型,我们就可以决定是把整张图片加载到内存中还是加载一个压缩版的图片到内存中。

举个例子,你的ImageView只有128*96像素的大小,只是为了显示一张缩略图,这时候把一张1024*768像素的图片完全加载到内存中显然是不值得的。这个时候就可以通过上述对图片处理的第二种方式调整inSampleSize值来实现图片的压缩。具体代码如下

 /**     * 根据所需展示图片的宽高来获取相应的缩放比例     */    public static int getInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {        // 源图片的高度和宽度        final int height = options.outHeight;        final int width = options.outWidth;        int inSampleSize = 1;        if (height > reqHeight || width > reqWidth) {            // 计算出实际宽高和目标宽高的比率            final int heightRatio = Math.round((float) height / (float) reqHeight);            final int widthRatio = Math.round((float) width / (float) reqWidth);            // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;        }        Log.d("TAG:" + TAG, "-----inSampleSize=" + inSampleSize);        return inSampleSize;    }
 /**     * 获取压缩后的Bitmap     */    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,                                                         int reqWidth, int reqHeight) {        // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小        final BitmapFactory.Options options = new BitmapFactory.Options();        options.inJustDecodeBounds = true;        BitmapFactory.decodeResource(res, resId, options);        // 调用上面定义的方法计算inSampleSize值        options.inSampleSize = getInSampleSize(options, reqWidth, reqHeight);        // 使用获取到的inSampleSize值再次解析图片        options.inJustDecodeBounds = false;        return BitmapFactory.decodeResource(res, resId, options);    }

下面的代码非常简单地将任意一张图片压缩成128*96的缩略图,并在ImageView上展示。

    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mIvIcon = (ImageView) findViewById(R.id.iv_icon);        mIvIcon.setImageBitmap(decodeSampledBitmapFromResource(getResources(), R.drawable.big, 128, 96));    }

使用图片缓存技术

详细见 Android中大图片的处理(二)之缓存策略

总结

针对上面的对图片处理总结如下步骤

1.得到实际的宽度和高度2.得到理想的宽度和高度: (图片的宽或者高/ 屏幕的宽或者高)*图片的宽或者高3.根据理想的宽度和高度和实际的宽度和高度计算出最好的采样值(inSampleSize)    (图片的宽或者高/ 屏幕的宽或者高)

追及

另外,需要注意这里的图片占用内存是指在Navtive中占用的内存,当然BitMap使用的绝大多数内存就是该内存。因为我们暂时可以简单的认为它就是BitMap所占用的内存。

在Android4.0之前,Bitmap的内存是分配在Native堆中,调用recycle()可以立即释放Native内存。
从Android4.0开始,Bitmap的内存就是分配在dalvik堆中,即JAVA堆中的,调用recycle()并不能立即释放Native内存。但是调用recycle()也是一个良好的习惯。

1 0
原创粉丝点击