在项目里面处理图片也有很长一段时间了,但今天却遇到一个非常棘手的问题,那就是在处理Bitmap的时候出现了OutOfMemery的错误,而且是非常频繁地出现,导致程序根本无法运行。以前处理Bitmap也不少了,但似乎没出过什么问题,也没想到会出什么问题,不就是API么,调用就行了,然而在今天终于领教到了Java类语言中也需要节约使用内存的教训。
原本是做一个列表,每一项包含一张图片,在以往的经验中,大概列表数目并不特别多,而且图片质量也并非很高,所以怎么加载都没有出现问题,但今天的列表中图片似乎过于高清,况且不但高清,体积也是十分之大,一张图片就是数百KB乃至于一兆的容量,除此之外,列表项目点击进去依然需要加载很多高清图片。在进行了几次滑动列表和点击项目的操作后,程序很快且必然会出现outofmemery错误,然后崩溃退出了。
众所周知,outofmemery就是“内存溢出”的意思,以下简称“OOM”。简单要的说,就是应用程序运行需要的内存容量超过了此时操作系统(此时为Android虚拟机)能分配的最大容量,程序的运行得不到足够的内存支撑,所以崩溃了。Android虚拟机会为每个应用提供大约16M的内存,由于应用程序本身的运行还要占用内存,如果涉及到图片加载,加载图片使用的内存大概达到8M左右(最多应该不过10M)时候,就会出现OOM。归根结底还是图片的质量过高,而且加载高质量图片时候又并未对其进行优化,所以出现错误。
我们一般在处理Bitmap图片时使用的是Android提供的BitmapFactory进行的解码,这个类提供了很多decode方法用于从各种途径解析出图片。我以前调用decode方法解码图片的时候,关心的是方式,也就是图片来源是什么,如果是来源于文件,用的是decodeFile()方法,若是来源于输入流,则用decodeStream(),如果是字节数组,使用decodeByteArray()。其实上面这些方法除了传入途径或是来源外,还有一个可选的参数BitmapFactory.Options,该类的实例包含了很多在解码图片时的附加参数,其中最重要的,和解决OOM错误有关的属性有如下几个:
1.options.inJustDecodeBounds,这个属性决定了在加载图片时是否只加载边界信息。什么是边界信息,就是图片的一系列参数,却并不包含图片文件本身的字节,加载边界信息就是为了在加载解码图片之前对图片进行适当的压缩处理。因为加载边界信息时并不会读入图片文件字节,在加载边界信息之后如果选择对图片进行压缩处理就能从一定程度上节省内存。
2.options.inPreferredConfig,这个属性默认为Bitmap.Config.ARGB_8888:,表示每个像素4字节. 共32位,还有一些其他属性,Alpha_8表示只保存透明度,共8位,1字节,ARGB_4444: 共16位,2字节,RGB_565:共16位,2字节。如果对图片的透明度没有特别的要求,最好选用RGB_565参数,除了损失透明度信息之外,对图片基本上是原样保存的,加上占用的字节少了一半,所以也能在很在程度上节约内存。
3.options.inPurgeable,这个属性表示使用BitmapFactory创建的Bitmap用于存储Pixel的内存空间,在系统内存不足时可以被回收,我认为这个属性对于程序的稳定性是一个非常重要的因素,作为用户,大多数时候并不能看到程序运行效率有多高,最直观的感受是程序的稳定性,稳定性做好了才有可能考虑其他的,这个属性可以防止程序在解码图片内存不足时崩溃退出。
4.options.inSampleSize,这个属性表示图形的缩放比例。实际上,手机屏幕大小毕竟有限,在这有限的区域内肉眼能识别的像素点也是有限的,所以很多时候手机上显示的图片可以适当压缩,却并不会影响人眼的观看。控制适当的缩放比例,是对内存优化最有效的措施。在第1项中,加载边界信息后,就能通过边界信息,结合图片本身的尺寸大小计算出合适的缩放比例了。这个属性的动态算法可参考下面的,网上也很多,大体都一样:
- public static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
- int initialSize = computeInitialSampleSize(options, minSideLength,maxNumOfPixels);
- int roundedSize;
- if (initialSize <= 8) {
- roundedSize = 1;
- while (roundedSize < initialSize) {
- roundedSize <<= 1;
- }
- } else {
- roundedSize = (initialSize + 7) / 8 * 8;
- }
- return roundedSize;
- }
-
- private static int computeInitialSampleSize(BitmapFactory.Options options,
- int minSideLength, int maxNumOfPixels) {
- double w = options.outWidth;
- double h = options.outHeight;
- // 上下限范围
- int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math
- .sqrt(w * h / maxNumOfPixels));
- int upperBound = (minSideLength == -1) ? 128 : (int) Math.min(
- Math.floor(w / minSideLength), Math.floor(h / minSideLength));
- if (upperBound < lowerBound) {
- // return the larger one when there is no overlapping zone.
- return lowerBound;
- }
- if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
- return 1;
- } else if (minSideLength == -1) {
- return lowerBound;
- } else {
- return upperBound;
- }
- }
通过对以上属性的调整和设置,想必就能够处理大多数情况下的加载Bitmap出现的OOM问题了。关键是无论从何种途径加载图片,必然是一个读取到内存的过程,然而边界信息却不会将整张图片的字节读取到内存,而且可以获得图片的属性信息,并对此作出判断和调整,这才是解决Bitmap OOM问题的方法。