高效加载图片防止OOM--总结

为了防止图片加载的时候出现OOM,笔者在这里总结了几种方式,均来自与wuli互联网并加上了自己的见解。

方法1:

读取图片时注意方法的调用,适当压缩 。尽量不要使用setImageBitmapsetImageResourceBitmapFactory.decode

Resource来设置一张大图,因为这些函数在完成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;   // width,hight设为原来的十分一
         Bitmap btp =  BitmapFactory.decodeStream(is, null,  options);

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

下面是我从网络上找到的一段神奇的代码,据说是以最少的内存去读取本地资源图片

public  static  Bitmap readBitMap(Context  context, int resId){ 
         BitmapFactory.Options opt = new  BitmapFactory.Options();
         opt.inPreferredConfig =  Bitmap.Config.RGB_565;//设置图片的Config参数
         opt.inPurgeable = true;
         opt.inInputShareable = true;
         //  获取资源图片
         InputStream is =  context.getResources().openRawResource(resId);
         return  BitmapFactory.decodeStream(is, null, opt);
         }

有些人可能对以上的参数不理解,我来解释一番:

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


Options.inPreferredConfig值来降低内存消耗: 
    默认为ARGB_8888: 每个像素4字节. 共32位。 
    Alpha_8: 只保存透明度,共8位,1字节。 
    ARGB_4444: 共16位,2字节。 
    RGB_565:共16位,2字节。 
    如果不需要透明度,可把默认值ARGB_8888改为RGB_565,节约一半内存。 

inInputShareable:

   设置是否深拷贝,与inPurgeable结合使用


      那为什么decodeStream方法可以代替decodeResource,setImageResource,setImageBitmap等方法来加载图片呢?
  decodeStream直接读取图片字节码,调用nativeDecodeAsset/nativeDecodeStream来完成decode。无需使用Java空间的一些额外处理过程,节省dalvik内存。但是由于直接读取字节码,没有处理过程,因此不会根据机器的各种分辨率来自动适应,需要在hdpi,mdpi和ldpi中分别配置相应的图片资源,否则在不同分辨率机器上都是同样的大小(像素点数量),显示的实际大小不对。
  decodeResource会在读取完图片数据后,根据机器的分辨率,进行图片的适配处理,导致增大了很多dalvik内存消耗。

方法2:

在适当的时候及时回收图片占用的内存  通常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;   
            }    
        }   
       System.gc();
在释放资源时,需要注意释放的Bitmap或者相关的Drawable是否有被其它类引用。如果正常的调用,可以通过
Bitmap.isRecycled()方法来判断是否有被标记回收;而如果是被UI线程的界面相关代码使用,就需要特别小心避免回收有可能被使用的资源,不然有可能抛出系统异常: E/AndroidRuntime: java.lang.IllegalArgumentException:

 Cannot draw recycled bitmaps 并且该异常无法有效捕捉并处理。


方法3:

不必要的时候避免图片的完整加载,只需要知道图片大小的情形下,可以不完整加载图片到内存。 在使用

BitmapFactory压缩图片的时候,BitmapFactory.Options设置inJustDecodeBounds为true后,再使用decodeFile()等方

法,可以在不分配空间状态下计算出图片的大小。示例如下:

 //加载图片
                    //图片压缩
                    //1.获得图片需要显示的大小
                    ImageSize imageSize = getImageViewSize(imageView);
                    //2.压缩图片
                    Bitmap bm = decodeSampleBitmapFromPath(path, imageSize.width, imageSize.height);
		
 		   //根据图片需要显示的宽和高进行压缩
       private Bitmap decodeSampleBitmapFromPath(String path, int width, int height) {
                  //获得图片的宽和高,并不把图片加载到内存中
                  BitmapFactory.Options options = new BitmapFactory.Options();
                  options.inJustDecodeBounds = true;//不分配内存
                  BitmapFactory.decodeFile(path, options);
                  options.inSampleSize = caculateInSampleSize(options, width, height);
                  //使用获得的InSampleSize再次解析图片
                  options.inJustDecodeBounds = false;//可以分配内存了
                  Bitmap bitmap = BitmapFactory.decodeFile(path, options);
                  return bitmap;
       }

      /**
        * 根据需求的宽和高以及图片的宽和高计算SampleSize
        */
    private int caculateInSampleSize(BitmapFactory.Options options, int reqwidth, int reqheight) {
        int width = options.outWidth;
        int height = options.outHeight;
        int inSampleSize = 1;
        if (width > reqwidth || height > reqheight) {
            int wdithRadio = Math.round(width * 1.0f / reqwidth);
            int heightRadio = Math.round(height * 1.0f / reqheight);
            inSampleSize = Math.max(heightRadio, wdithRadio);
        }
        return inSampleSize;
    }

    /**
     * 根据ImageView获取宽和高
     */
    private ImageSize getImageViewSize(ImageView imageView) {
        ImageSize imageSize = new ImageSize();
        displayMetrics = imageView.getContext().getResources().getDisplayMetrics();
        ViewGroup.LayoutParams lp = imageView.getLayoutParams();
        //获取ImageView的实际宽度
        int width = imageView.getWidth();
        if (width <= 0) {
            width = lp.width;//获取imageview在layout中声明的宽度
        }
        if (width <= 0) {
            width = getImageViewFieldValue(imageView, "mMaxWidth");//检查最大值
        }
        if (width <= 0) {
            width = displayMetrics.widthPixels;
        }
        int height = imageView.getHeight();
        //获取imageview的实际高度
        if (height <= 0) {
            height = lp.height;//获取imageview在layout中声明的高度
        }
        if (height <= 0) {
            height = getImageViewFieldValue(imageView, "mMaxHeight");//检查最大值
        }
        if (height <= 0) {
            height = displayMetrics.heightPixels;
        }
        imageSize.width = width;
        imageSize.height = height;
        return imageSize;
    }

    //通过反射获取Imageview的某个属性值
    private static int getImageViewFieldValue(Object object, String fieldName) {
        int value = 0;
        try {
            Field field = ImageView.class.getDeclaredField(fieldName);
            field.setAccessible(true);
            int fieldValue = field.getInt(object);
            if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {
                value = fieldValue;
            }
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return value;
    }
     private class ImageSize {
        int width;
        int height;
    }

方法4 

    优化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);


方法5:

 自定义堆(Heap)内存大小,我们可以强制定义自己软件的对内存大小,我们使用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)时仍然采用堆的上限值,对于内存不足没什么作用。


方法6:
在Manifest.xml文件里面的<application  里面添加Android:largeHeap="true"
简单粗暴。这种方法允许应用需要耗费手机很多的内存空间,但却是最快捷的解决办法


最后介绍一下图片占用进程的内存算法。android中处理图片的基础类是Bitmap,顾名思义,就是位图。占用内存的算法如:图片的width*height*Config.如果Config设置为ARGB_8888,那么上面的Config就是4。一张480*320的图片占用的内存就是480*320*4byte。 在默认情况下android进程的内存占用量为16M,因为Bitmap他除了java中持有数据外,底层C++的  skia图形库还会持有一个SKBitmap对象,因此一般图片占用内存推荐大小应该不超过8M。这个可以调整,编译源代码时可以设置参数。


总结完毕:不喜勿喷,谢谢!!!


  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Docker提供了一个选项来设置容器不被操作系统杀死,该选项称为"oom-kill-disable"。oom-kill-disable是一个用于禁用内存超限时杀死容器的选项。 当容器内存使用量超出其可用内存的限制时,操作系统会触发一个内存超限(OOM)事件。通常情况下,为了从OOM事件中恢复,操作系统会选择杀死占用内存较大的进程(包括容器)来释放内存资源。 如果我们想禁止Docker容器在OOM事件中被杀死,我们可以使用"oom-kill-disable"选项。这个选项可以在运行容器时通过命令行或Docker Compose文件进行设置,具体的使用方法如下: 1. 在使用命令行运行容器时,添加"--oom-kill-disable"选项,例如: ``` docker run --oom-kill-disable <容器名称或ID> ``` 2. 在Docker Compose文件中,为容器配置"oom_kill_disable"字段并设置为true,例如: ```yaml services: myservice: oom_kill_disable: true ``` 注意,禁用容器的OOM事件可能导致主机操作系统的整体性能下降,甚至会影响到其他容器的正常运行。因此,在使用"oom-kill-disable"选项时需要仔细评估容器内存使用及主机资源的情况,确保系统的稳定性和可用性。 最后,需要注意的是,对于大多数情况来说,允许操作系统根据OOM事件决定杀死容器是合理的做法,因为这有助于保持系统的可用性和可靠性,并防止整个系统由于单个容器的内存消耗过大而崩溃。所以,在设置"oom-kill-disable"选项时,需要慎重考虑。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值