Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 340|回复: 0

Android加载图片导致内存溢出(Out of Memory异常)

[复制链接]
  • TA的每日心情
    奋斗
    2024-4-6 11:05
  • 签到天数: 748 天

    [LV.9]以坛为家II

    2034

    主题

    2092

    帖子

    70万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    705612
    发表于 2021-5-22 16:04:34 | 显示全部楼层 |阅读模式

    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为false     

     opts.inJustDecodeBounds = false;    

     //  使用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进程的内存占用量为16M,因为Bitmap他除了java中持有数据外,底层C++的  skia图形库还会持有一个SKBitmap对象,因此一般图片占用内存推荐大小应该不超过8M。这个可以调整,编译源代码时可以设置参数。

    参考资料:http://www.tuicool.com/articles/yemM7zf

    方案六:在Manifest.xml文件里面的<application  里面添加Android:largeHeap="true"

    简单粗暴。这种方法允许应用需要耗费手机很多的内存空间,但却是最快捷的解决办法

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-19 15:50 , Processed in 0.064393 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表