zoukankan      html  css  js  c++  java
  • Android加载图片导致内存溢出(Out of Memory异常)

    Android在加载大背景图或者大量图片时,经常导致内存溢出(Out of Memory Error),本文根据我处理这些问题的经历及其它开发者的经验,整理解决方案如下(部分代码及文字出处无法考证):


    方案一、读取图片时注意方法的调用,适当压缩
    尽量不要使用setImageBitmapsetImageResourceBitmapFactory.decodeResource来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存。
    因此,改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的 source,decodeStream最大的秘密在于其直接调用JNI>>nativeDecodeAsset()来完成decode,无需再使用java层的createBitmap,从而节省了java层的空间。

            InputStream is = this.getResources().openRawResource(R.drawable.pic1);

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

            options.inJustDecodeBounds = false;

            options.inSampleSize = 10;   // widthhight设为原来的十分一

            Bitmap btp = BitmapFactory.decodeStream(is, null, options);

     
    如果在读取时加上图片的Config参数,可以跟有效减少加载的内存,从而跟有效阻止抛out of Memory异常。

      /**

         * 以最省内存的方式读取本地资源的图片

         * @param context

         * @param resId

         * @return

         */

        public static Bitmap readBitMap(Context context, int resId){ 

            BitmapFactory.Options opt = new BitmapFactory.Options();

            opt.inPreferredConfig = Bitmap.Config.RGB_565;

            opt.inPurgeable = true;

            opt.inInputShareable = true;

            // 获取资源图片

            InputStream is = context.getResources().openRawResource(resId);

            return BitmapFactory.decodeStream(is, null, opt);

            }

     
    另外,decodeStream直接拿图片来读取字节码, 不会根据机器的各种分辨率来自动适应,使用了decodeStream之后,需要在hdpi和mdpi,ldpi中配置相应的图片资源, 否则在不同分辨率机器上都是同样大小(像素点数量),显示出来的大小就不对了。
     
    方案二、在适当的时候及时回收图片占用的内存
    通常Activity或者Fragment在onStop/onDestroy时候就可以释放图片资源:

     if(imageView != null && imageView.getDrawable() != null){     

          Bitmap oldBitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();    

          imageView.setImageDrawable(null);    

          if(oldBitmap != null){    

                oldBitmap.recycle();    

                oldBitmap = null;   

          }    

     }   

     // Other code.

     System.gc();

     
    在释放资源时,需要注意释放的Bitmap或者相关的Drawable是否有被其它类引用。如果正常的调用,可以通过Bitmap.isRecycled()方法来判断是否有被标记回收;而如果是被UI线程的界面相关代码使用,就需要特别小心避免回收有可能被使用的资源,不然有可能抛出系统异常:
    E/AndroidRuntime: java.lang.IllegalArgumentException: Cannot draw recycled bitmaps
    并且该异常无法有效捕捉并处理。
     
    方案三、不必要的时候避免图片的完整加载
    只需要知道图片大小的情形下,可以不完整加载图片到内存。
    在使用BitmapFactory压缩图片的时候,BitmapFactory.Options设置inJustDecodeBounds为true后,再使用decodeFile()等方法,可以在不分配空间状态下计算出图片的大小。示例: 

     BitmapFactory.Options opts = new BitmapFactory.Options();    

     // 设置inJustDecodeBounds为true    

     opts.inJustDecodeBounds = true;    

     // 使用decodeFile方法得到图片的宽和高    

     BitmapFactory.decodeFile(path, opts);    

     // 打印出图片的宽和高

     Log.d("example", opts.outWidth + "," + opts.outHeight);

    (ps:原理其实就是通过图片的头部信息读取图片的基本信息)
     
    方案四、优化Dalvik虚拟机的堆内存分配
    堆(HEAP)是VM中占用内存最多的部分,通常是动态分配的。堆的大小不是一成不变的,通常有一个分配机制来控制它的大小。比如初始的HEAP是4M大,当4M的空间被占用超过75%的时候,重新分配堆为8M大;当8M被占用超过75%,分配堆为16M大。倒过来,当16M的堆利用不足30%的时候,缩减它的大小为8M大。重新设置堆的大小,尤其是压缩,一般会涉及到内存的拷贝,所以变更堆的大小对效率有不良影响。
    Heap Utilization是堆的利用率。当实际的利用率偏离这个百分比的时候,虚拟机会在GC的时候调整堆内存大小,让实际占用率向个百分比靠拢。使用 dalvik.system.VMRuntime类提供的setTargetHeapUtilization方法可以增强程序堆内存的处理效率。

     private final static float TARGET_HEAP_UTILIZATION = 0.75f;    

     // 在程序onCreate时就可以调用

     VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION);

     
    方案五、自定义堆(Heap)内存大小
    对于一些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大小。

     
    但是上面方法还是存在问题,函数setMinimumHeapSize其实只是改变了堆的下限值,它可以防止过于频繁的堆内存分配,当设置最小堆内存大小超过上限值(Max Heap Size)时仍然采用堆的上限值,对于内存不足没什么作用。
     
    最后介绍一下图片占用进程的内存算法。android中处理图片的基础类是Bitmap,顾名思义,就是位图。占用内存的算法如:图片的width*height*Config。
    如果Config设置为ARGB_8888,那么上面的Config就是4。一张480*320的图片占用的内存就是480*320*4 byte。
    在默认情况下android进程的内存占用量为16M,因为Bitmap他除了java中持有数据外,底层C++的 skia图形库还会持有一个SKBitmap对象,因此一般图片占用内存推荐大小应该不超过8M。这个可以调整,编译源代码时可以设置参数。
     
  • 相关阅读:
    SharePoint 2013 安装.NET Framework 3.5 报错
    SharePoint 2016 配置工作流环境
    SharePoint 2016 站点注册工作流服务报错
    Work Management Service application in SharePoint 2016
    SharePoint 2016 安装 Cumulative Update for Service Bus 1.0 (KB2799752)报错
    SharePoint 2016 工作流报错“没有适用于此应用程序的地址”
    SharePoint 2016 工作流报错“未安装应用程序管理共享服务代理”
    SharePoint JavaScript API in application pages
    SharePoint 2016 每天预热脚本介绍
    SharePoint 无法删除搜索服务应用程序
  • 原文地址:https://www.cnblogs.com/wangle1001986/p/4232000.html
Copyright © 2011-2022 走看看