Android应用程序开发之图片操作(二)——工程图片资源的加载及OOM的处理

时间:2021-02-22 17:39:16

(一)工程图片资源的加载方法  

  在Android应用程序开发之图片操作(一)中,详细说明了如何操作各种资源图片,只是有的没有附上示例代码,在此,我将针对项目工程中的图片资源的显示加载进行说明。官方说明,请参考http://developer.android.com/training/displaying-bitmaps/display-bitmap.html。

1 public static Bitmap readBitMap(Context context, int resId){  
2 BitmapFactory.Options opt = new BitmapFactory.Options();
3 opt.inPreferredConfig = Bitmap.Config.RGB_565; //表示16位位图 565代表对应三原色占的位数
4    pt.inSampleSize = X;
5 opt.inPurgeable = true; //设置图片可以被回收
6 opt.inInputShareable = true; //获取图片资源
7 InputStream is =context.getResources().openRawResource(resId);
8 return BitmapFactory.decodeStream(is,null,opt);
9 }

说明:

1,opt.inPreferredConfig

  通过设置Options.inPreferredConfig值来降低内存消耗:

  默认为ARGB_8888: 每个像素4字节. 共32位。

     Alpha_8: 只保存透明度,共8位,1字节
     ARGB_4444: 共16位,2字节。
     RGB_565:共16位,2字节。
     如果不需要透明度,可把默认值ARGB_8888改为RGB_565,节约一半内存。

2,opt.inSampleSize

  通过设置Options.inSampleSize 对大图片进行压缩,可先设置otp.inJustDecodeBounds,获取Bitmap的外围数据,宽和高等。然后计算压缩比例,进行压缩。参考:http://blog.csdn.net/xu_fu/article/details/8262153。

3,opt.inPurgeable

  如果 inPurgeable 设为True的话表示使用BitmapFactory创建的Bitmap用于存储Pixel的内存空间在系统内存不足时可以被回收,在应用需要再次访问Bitmap的Pixel时(如绘制Bitmap或是调用getPixel),系统会再次调用BitmapFactory decoder重新生成Bitmap的Pixel数组.为了能够重新解码图像,bitmap要能够访问存储Bitmap的原始数据。在inPurgeable为false时,表示创建的Bitmap的Pixel内存空间不能被回收,这样每次要使用Bitmap时,BitmapFactory就会不停地使用decodeByteArray创建新的Bitmap对象,不同设备的内存不同,因此能够同时创建的Bitmap个数可能有所不同,200个bitmap足以使大部分的设备重新OutOfMemory错误.当isPurgable设为true时,系统中内存不足时,可以回收部分Bitmap占据的内存空间,这时一般不会出现OutOfMemory 错误。因此该项一般设置为true。

4,opt.inInputShareable

  inInputShareable与inPurgeable一起使用,如果inPurgeable为false那该设置将被忽略,如果为true,那么它可以决定位图是否能够共享一个指向数据源的引用,或者是进行一份拷贝;

二,内存溢出(此部分转自http://blog.csdn.net/sweetsnow24/article/details/7968462

Android内存溢出的解决办法
  昨天在模拟器上给gallery放入图片的时候,出现java.lang.OutOfMemoryError: bitmap size exceeds VM budget 异常,图像大小超过了RAM内存。
      模拟器RAM比较小,只有8M内存,当我放入的大量的图片(每个100多K左右),就出现上面的原因。
由于每张图片先前是压缩的情况,放入到Bitmap的时候,大小会变大,导致超出RAM内存,具体解决办法如下:

                 //解决加载图片 内存溢出的问题
                 //Options 只保存图片尺寸大小,不保存图片到内存
                BitmapFactory.Options opts = new BitmapFactory.Options();
                //缩放的比例,缩放是很难按准备的比例进行缩放的,其值表明缩放的倍数,SDK中建议其值是2的指数值,值越大会导致图片不清晰
                opts.inSampleSize = 4;
                Bitmap bmp = null;
                bmp = BitmapFactory.decodeResource(getResources(), mImageIds[position],opts);                             
                ...              
               //回收
                bmp.recycle();

通过上面的方式解决了,但是这并不是最完美的解决方式。
通过一些了解,得知如下:
优化Dalvik虚拟机的堆内存分配

对于Android平台来说,其托管层使用的Dalvik Java VM从目前的表现来看还有很多地方可以优化处理,比如我们在开发一些大型游戏或耗资源的应用中可能考虑手动干涉GC处理,使用 dalvik.system.VMRuntime类提供的setTargetHeapUtilization方法可以增强程序堆内存的处理效率。当然具体原理我们可以参考开源工程,这里我们仅说下使用方法:   private final static float TARGET_HEAP_UTILIZATION = 0.75f; 在程序onCreate时就可以调用 VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION); 即可。

Android堆内存也可自己定义大小
    对于一些Android项目,影响性能瓶颈的主要是Android自己内存管理机制问题,目前手机厂商对RAM都比较吝啬,对于软件的流畅性来说RAM对性能的影响十分敏感,除了 优化Dalvik虚拟机的堆内存分配外,我们还可以强制定义自己软件的对内存大小,我们使用Dalvik提供的 dalvik.system.VMRuntime类来设置最小堆内存为例:
private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;
VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE); //设置最小heap内存为6MB大小。当然对于内存吃紧来说还可以通过手动干涉GC去处理

bitmap 设置图片尺寸,避免 内存溢出 OutOfMemoryError的优化方法
★android 中用bitmap 时很容易内存溢出,报如下错误:Java.lang.OutOfMemoryError : bitmap size exceeds VM budget

● 主要是加上这段:
BitmapFactory.Options options = new BitmapFactory.Options();
                options.inSampleSize = 2;

● eg1:(通过Uri取图片)
private ImageView preview;
BitmapFactory.Options options = new BitmapFactory.Options();
                    options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一
                    Bitmap bitmap = BitmapFactory.decodeStream(cr
                            .openInputStream(uri), null, options);
                    preview.setImageBitmap(bitmap);
以上代码可以优化内存溢出,但它只是改变图片大小,并不能彻底解决内存溢出。
● eg2:(通过路径去图片)
private ImageView preview;
private String fileName= "/sdcard/DCIM/Camera/2010-05-14 16.01.44.jpg";
BitmapFactory.Options options = new BitmapFactory.Options();
                options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一
                        Bitmap b = BitmapFactory.decodeFile(fileName, options);
                        preview.setImageBitmap(b);
                        filePath.setText(fileName);

★Android 还有一些性能优化的方法:
●  首先内存方面,可以参考 Android堆内存也可自己定义大小 和 优化Dalvik虚拟机的堆内存分配

●  基础类型上,因为Java没有实际的指针,在敏感运算方面还是要借助NDK来完成。Android123提示游戏开发者,这点比较有意思的是Google 推出NDK可能是帮助游戏开发人员,比如OpenGL ES的支持有明显的改观,本地代码操作图形界面是很必要的。

●  图形对象优化,这里要说的是Android上的Bitmap对象销毁,可以借助recycle()方法显示让GC回收一个Bitmap对象,通常对一个不用的Bitmap可以使用下面的方式,如

if(bitmapObject.isRecycled()==false) //如果没有回收  
         bitmapObject.recycle();   

●  目前系统对动画支持比较弱智对于常规应用的补间过渡效果可以,但是对于游戏而言一般的美工可能习惯了GIF方式的统一处理,目前Android系统仅能预览GIF的第一帧,可以借助J2ME中通过线程和自己写解析器的方式来读取GIF89格式的资源。

● 对于大多数Android手机没有过多的物理按键可能我们需要想象下了做好手势识别 GestureDetector 和重力感应来实现操控。通常我们还要考虑误操作问题的降噪处理。

Android堆内存也可自己定义大小

   对于一些大型Android项目或游戏来说在算法处理上没有问题外,影响性能瓶颈的主要是Android自己内存管理机制问题,目前手机厂商对RAM都比较吝啬,对于软件的流畅性来说RAM对性能的影响十分敏感,除了上次Android开发网提到的优化Dalvik虚拟机的堆内存分配外,我们还可以强制定义自己软件的对内存大小,我们使用Dalvik提供的 dalvik.system.VMRuntime类来设置最小堆内存为例:

private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;

VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE); //设置最小heap内存为6MB大小。当然对于内存吃紧来说还可以通过手动干涉GC去处理,我们将在下次提到具体应用。

优化Dalvik虚拟机的堆内存分配

对于Android平台来说,其托管层使用的Dalvik JavaVM从目前的表现来看还有很多地方可以优化处理,比如我们在开发一些大型游戏或耗资源的应用中可能考虑手动干涉GC处理,使用 dalvik.system.VMRuntime类提供的setTargetHeapUtilization方法可以增强程序堆内存的处理效率。当然具体原理我们可以参考开源工程,这里我们仅说下使用方法:   private final static floatTARGET_HEAP_UTILIZATION = 0.75f; 在程序onCreate时就可以调用 VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION); 即可

http://www.cnblogs.com/santry/archive/2011/08/29/2158438.html

Android虽然会自动管理内存,JAVA也有garbage collection (GC )内存回收机制。

但是如果程序在一次操作中打开几个M的文件,那么通常会出现下面的错误信息。

02-04 21:46:08.703: ERROR/dalvikvm-heap(2429): 1920000-byte external allocation too large for this process.

02-04 21:52:28.463: ERROR/AndroidRuntime(2429): java.lang.OutOfMemoryError: bitmap size exceeds VM budget

移动终端因为内存有限,往往图片处理经常出现上述的错误。

解决方法:

1.明确调用System.gc();

   这种内存回收会有一定的作用,但是请不要太期待。

2.图片处理完成后回收内存。

  请在调用BitMap进行图片处理后进行内存回收。

  bitmap.recycle();

  这样会把刚刚用过的图片占用的内存释放。

3.图片处理时指定大小。

  下面这个方法处理几个M的图片时是必须的

1.   BitMap getBitpMap(){  

2.   ParcelFileDescriptor pfd;  

3.   try{  

4.       pfd = mCon.getContentResolver().openFileDescriptor(uri, "r");  

5.   }catch (IOException ex){  

6.       return null;  

7.   }  

8.   java.io.FileDescriptor fd = pfd.getFileDescriptor();  

9.   BitmapFactory.Options options = new BitmapFactory.Options();  

10.     //先指定原始大小  

11. options.inSampleSize = 1;  

12.     //只进行大小判断  

13.     options.inJustDecodeBounds = true;  

14.     //调用此方法得到options得到图片的大小  

15.     BitmapFactory.decodeFileDescriptor(fd, null, options);  

16.     //我们的目标是在800pixel的画面上显示。  

17.     //所以需要调用computeSampleSize得到图片缩放的比例  

18.     options.inSampleSize = computeSampleSize(options, 800);  

19.     //OK,我们得到了缩放的比例,现在开始正式读入BitMap数据  

20.     options.inJustDecodeBounds = false;  

21.     options.inDither = false;  

22.     options.inPreferredConfig = Bitmap.Config.ARGB_8888;  

23.       

24.     //根据options参数,减少所需要的内存  

25.     Bitmap sourceBitmap = BitmapFactory.decodeFileDescriptor(fd, null, options);  

26.     return sourceBitmap;  

27. }  

28. //这个函数会对图片的大小进行判断,并得到合适的缩放比例,比如2即1/2,3即1/3  

29. static int computeSampleSize(BitmapFactory.Options options, int target) {  

30.     int w = options.outWidth;  

31.     int h = options.outHeight;  

32.     int candidateW = w / target;  

33.     int candidateH = h / target;  

34.     int candidate = Math.max(candidateW, candidateH);  

35.     if (candidate == 0)  

36.         return 1;  

37.     if (candidate > 1) {  

38.         if ((w > target) && (w / candidate) < target)  

39.             candidate -= 1;  

40.     }  

41.     if (candidate > 1) {  

42.         if ((h > target) && (h / candidate) < target)  

43.             candidate -= 1;  

44.     }  

45.     if (VERBOSE)  

46.         Log.v(TAG, "for w/h " + w + "/" + h + " returning " + candidate + "(" + (w/candidate) + " / " + (h/candidate));  

47.     return candidate;  

48. } 

http://wenwen.soso.com/z/q259026573.htm?sp=2000

///////////////////////////////////////////////////////

///////////////////////////////////////////////////////

 android系统的手机在系统底层指定了堆内存的上限值,大部分手机的缺省值是16MB,不过也有些高配置的机型是24MB的,所以我们的程序在申请内存空间时,为了确保能够成功申请到内存空间,应该保证当前已分配的内存加上当前需要分配的内存值的总大小不能超过当前堆的最大内存值,而且内存管理上将外部内存完全当成了当前堆的一部分,也就是说Bitmap对象通过栈上的引用来指向堆上的Bitmap对象,而堆上的Bitmap对象又对应了一个使用了外部存储的native图像,也就是实际上使用的字节数组byte[]来存储的位图信息,因此解码之后的Bitmap的总大小就不能超过8M了。

解决这类问题的最根本的,最有效的办法就是,使用完bitmap之后,调用bitmap对象的recycle()方法释放所占用的内存,以便于下一次使用。

下面是网上找到的一些常用的优化办法,但是基本上都不能从本质上解决问题。

1.设置系统的最小堆大小:

int newSize = 4 * 1024 * 1024 ; //设置最小堆内存大小为4MB

VMRuntime.getRuntime().setMinimumHeapSize(newSize);
VMRuntime.getRuntime().setTargetHeapUtilization(0.75); // 设置堆内存的利用率为75%

补充说明:堆(HEAP)是VM中占用内存最多的部分,通常是动态分配的。堆的大小不是一成不变的,当堆内存实际的利用率偏离设定的值的时候,虚拟机会在GC的时候调整堆内存大小,让实际占用率向个百分比靠拢。比如初始的HEAP是4M大小,当4M的空间被占用超过75%的时候,重新分配堆为8M大;当8M被占用超过75%,分配堆为16M大。倒过来,当16M的堆利用不足30%的时候,缩减它的大小为8M大。重新设置堆的大小,尤其是压缩,一般会涉及到内存的拷贝,所以变更堆的大小对效率有不良影响。

2.对图片的大小进行控制

BitmapFactory? .Options options = new BitmapFactory? .Options();

options.inSampleSize = 2; //图片宽高都为原来的二分之一,即图片为原来的四分之一

Bitmap bitmap = BitmapFactory? .decodeFile("/mnt/sdcard/a.jpg",options);

补充说明:这种方法只是对图片做了一个缩放处理,降低了图片的分辨率,在需要保证图片质量的应用中不可取。

3.

BitmapFactory? .Options options = new BitmapFactory? .Options();
options.inTempStorage = new byte[1024*1024*5]; //5MB的临时存储空间

Bitmap bm = BitmapFactory? .decodeFile("/mnt/sdcard/a.jpg",options);

补充说明:从创建Bitmap的C++底层代码BitmapFactory.cpp中的处理逻辑来看,如果option不为null的话,那么会优先处理option中设置的各个参数,假设当前你设置option的inTempStorage为1024*1024*4(4M)大小的话,而且每次解码图像时均使用该option对象作为参数,那么你的程序极有可能会提前失败,经过测试,如果使用一张大小为1.03M的图片来进行解码,如果不使用option参数来解码,可以正常解码四次,也就是分配了四次内存,而如果使用option的话,就会出现内存溢出错误,只能正常解码两次。Options类有一个预处理参数,当你传入options时,并且指定临时使用内存大小的话,Android将默认先申请你所指定的内存大小,如果申请失败,就会先抛出内存溢出错误。而如果不指定内存大小,系统将会自动计算,如果当前还剩3M空间大小,而解码只需要2M大小,那么在缺省情况下将能解码成功,而在设置inTempStorage大小为4M的情况下就将出现内存溢出错误。所以,通过设置Options的inTempStorage大小也不能从根本上解决大图像解码的内存溢出问题。

总之再做android开发时,出现内存溢出是属于系统底层限制,只要解码需要的内存超过系统可分配的最大内存值,那么内存溢出错误必然会出现.