转载--android bitmap oom 分析

刘炳昭 2012-12-07

原文出处:http://labs.ywlx.net/?p=3351

android由解析bitmap引起的内存溢出问题

发表于2012/10/28由peibingqing

最近在做一款塔防游戏,用的事surfaceview框架,由于图片过多,而且游戏过程中都需要这些图片,所以加载成bitmap后造成OOM(outofmemory)异常。下面是我一步一步找解决此问题的纪录,再此分享,希望对以后出现此问题的开发者有所帮助。

第一:出现问题,我的测试手机是2。2android操作系统,不会出现oom问题,但是在老板的android4.2上却出现了问题,因为是oom,所以我首先想到的是手动改变手机的内存大小限制。网上有些帖子说可以通过函数设置应用的HEAPSIZE来解决这个问题,其实是不对的。VMRuntime.getRuntime().setMinimumHeapSize(NewSize);堆(HEAP)是VM中占用内存最多的部分,通常是动态分配的。堆的大小不是一成不变的,通常有一个分配机制来控制它的大小。比如初始的HEAP是4M大,当4M的空间被占用超过75%的时候,重新分配堆为8M大;当8M被占用超过75%,分配堆为16M大。倒过来,当16M的堆利用不足30%的时候,缩减它的大小为8M大。重新设置堆的大小,尤其是压缩,一般会涉及到内存的拷贝,所以变更堆的大小对效率有不良影响。MaxHeapSize,是堆内存的上限值,Android的缺省值是16M(某些机型是24M),对于普通应用这是不能改的。函数setMinimumHeapSize其实只是改变了堆的下限值,它可以防止过于频繁的堆内存分配,当设置最小堆内存大小超过上限值时仍然采用堆的上限值,对于内存不足没什么作用。setTargetHeapUtilization(floatnewTarget)可以设定内存利用率的百分比,当实际的利用率偏离这个百分比的时候,虚拟机会在GC的时候调整堆内存大小,让实际占用率向个百分比靠拢。在手机上进行了多次测试,确实不好使,在此,我断了改变内存限制的方法。

第二:查找出现问题的原因。1,在网上搜索bitmap内存溢出,找到很多说是因为图片大小引起的此问题。观察我的资源文件,没有太大的图片,只是图片数量过多,有将近900张,分别找出一张最大的图片和几张比较大的图片,单独测试,没有发现问题。方法1排除。

2,既然图片数量过多,突破点可能就是图片数量问题。于是分别找了200,400,600图片进行测试,在500左右的时候遇到错误,通过宝哥知道了将小图片整合存放到一张大图的方法,以此来减少图片的数量,但是仔细想想,加载成bitmap的时候还是要切割成小图生成bitmap,所以对此方法表示怀疑。由于以前没用过此方法,试试也无妨。所用到的工具是gdx—texturepackger,它只是一个工具,这里就不多说了。测试的最终结果是还是oom。方法2排除。

3,现在看来,既然不是图片数量的问题,而且会在500张左右的时候报错,那就可能是占用内存大小的问题了,Android手机有内存限制,但是我的图片大小又大于这个限制,这让我头疼了很长时间,研究国外的一些文章,从中发现了一些有用的信息,这些信息能够加深你对Android的解析bitmap机制的理解,在此分享:

AsofHoneycombBitmapdataisallocatedintheVMheap.

作为蜂窝位图数据是在VM分配堆。)

ThereisareferencetoitintheVMheap(whichissmall),buttheactualdataisallocatedintheNativeheapbytheunderlyingSkiagraphicslibrary.有一个引用在VM堆(小),但实际的数据是在本机堆分配由底层Skia图形库。

Unfortunately,whilethedefinitionofBitmapFactory.decode…()saysthatitreturnsnulliftheimagedatacouldnotbedecoded,theSkiaimplementation(orrathertheJNIgluebetweentheJavacodeandSkia)logsthemessageyou’reseeing(“VMwon’tletusallocatexxxxbytes”)andthenthrowsanOutOfMemoryexceptionwiththemisleadingmessage“bitmapsizeexceedsVMbudget”.不幸的是,虽然BitmapFactory.decode的定义…()表示,它返回null如果图像数据不能解码,Skia实现(或者说JNI胶之间的Java代码和Skia)日志消息你看到(“VM不会让我们分配xxxx字节”),然后抛出一个OutOfMemory异常与误导信息”位图的大小超过VM预算”。

TheissueisnotintheVMheapbutisratherintheNativeheap.这个问题不是在VM堆而是在本机堆。

TheNatïveheapissharedbetweenrunningapplications,sotheamountoffreespacedependsonwhatotherapplicationsarerunningandtheirbitmapusage.本机堆是正在运行的应用程序之间共享,因此空闲空间的大小取决于其他运行程序,他们使用的位图。

However,IhavefoundthatgetNativeHeapFreeSize()andgetNativeHeapSize()arenotreliable.然而,我发现getNativeHeapFreeSize()和getNativeHeapSize()是不可靠的。

TheNativeheapsizevariesbyplatform.本机堆大小不同的平台。

Soatstartup,wecheckthemaximumallowedVMheapsizetodeterminethemaximumallowedNativeheapsize.所以在启动时,我们检查最大允许VM堆大小来确定最大允许本机堆大小。

“BitmapdataisnotallocatedintheVMheap”—itisallocatedontheVMheapasofHoneycomb“位图数据不是在VM分配堆”——这是VM分配的堆在蜂窝Yes.是的。AsofHoneycomb(v3.0),bitmapdataisallocatedontheVMheap.作为蜂窝(v3.0),位图数据堆上分配VM。SoalloftheaboveonlyappliestoGingerbread(v2.3.x)andbefore所以所有上述只适用于姜饼(v23x)和之前

这些信息零零散散,但是不难发现,问题的原因就在于根据Android版本的不同,bitmapdata存放的位置是不同的,3.0以前是分配在nativeheap上,3.0以后是分配在VMheap上。

为了验证这个问题,我们需要抓去heap快照,众所周知,eclipse中的ddms可以查看heap信息,但是不够全面,这里我用到了adbshelldumpsysmeminfo+包名这条命令来查看heap信息,对比两个机子的不同如下:

2.2的

4.0

从中不难发现,bitmap的存放位置根据Android版本的不同真的有所不同。好了,下面就是找出怎么把图片存放到nativeheap里就行了,BitmapFactory里就那么几个decode方法,很容易找到BitmapFactory.decodeStream就可以解决。下面贴一下代码:

BitmapFactory.Optionsoptions=newBitmapFactory.Options();

options.inPreferredConfig=Config.ARGB_8888;

options.inPurgeable=true;//允许可清除

options.inInputShareable=true;//以上options的两个属性必须联合使用才会有效果

Stringsname=String.format(“xxx.png”,sTowerStyle,j,sDirction,i);

InputStreamis=am.open(sname);

arrBmp[iBmpIndex]=BitmapFactory.decodeStream(is,null,options);

ok搞定收工。

小问题大发现:1.遇到问题,不要急躁。最初遇到这个问题的时候以为很好解决,试了几种方法后还是解决不了,内心难免会有挫败感,这个时候,最需要的是耐心。

2.网上有很多资源,但是能不能查得到就是自己的问题了,我发现那些编程老手们查找问题总是能够准确定位,快速的找到解决方法。以后要加强这方面的锻炼。

3.国内的资源大多偏向解决问题,国外的资源大多偏向分析问题,所以有的时候还是需要多看看外文的一些资料。当然这需要不错的英文功底。当初看外文的资料,头都大了。这是需要加强的一个方面。

DanceInWind28/10/12

相关推荐