Android技术积累:图片缓存管理
缓存: 本文链接地址:http://keegan-lee.diandian.com/post/2012-12-06/40047548955
android 由解析bitmap引起的内存溢出问题
最近在做一款塔防游戏,用的事surfaceview框架,由于图片过多,而且游戏过程中都需要这些图片,所以加载成bitmap后造成OOM(out of memory)异常。下面是我一步一步找解决此问题的纪录,再此分享,希望对以后出现此问题的开发者有所帮助。
第一:出现问题,我的测试手机是2。2android操作系统,不会出现oom问题,但是在老板的android4.2上却出现了问题,因为是 oom,所以我首先想到的是手动改变手机的内存大小限制。网上有些帖子说可以通过函数设置应用的HEAP SIZE来解决这个问题,其实是不对的。 VMRuntime.getRuntime().setMinimumHeapSize(NewSize); 堆(HEAP)是VM中占用内存最多的部分,通常是动态分配的。堆的大小不是一成不变的,通常有一个分配机制来控制它的大小。比如初始的HEAP是4M 大,当4M的空间被占用超过75%的时候,重新分配堆为8M大;当8M被占用超过75%,分配堆为16M大。倒过来,当16M的堆利用不足30%的时候, 缩减它的大小为8M大。重新设置堆的大小,尤其是压缩,一般会涉及到内存的拷贝,所以变更堆的大小对效率有不良影响。Max Heap Size,是堆内存的上限值,Android的缺省值是16M(某些机型是24M),对于普通应用这是不能改的。函数 setMinimumHeapSize其实只是改变了堆的下限值,它可以防止过于频繁的堆内存分配,当设置最小堆内存大小超过上限值时仍然采用堆的上限 值,对于内存不足没什么作用。 setTargetHeapUtilization(float newTarget) 可以设定内存利用率的百分比,当实际的利用率偏离这个百分比的时候,虚拟机会在GC的时候调整堆内存大小,让实际占用率向个百分比靠拢。在手机上进行了多 次测试,确实不好使,在此,我断了改变内存限制的方法。
第二:查找出现问题的原因。1,在网上搜索bitmap内存溢出,找到很多说是因为图片大小引起的此问题。观察我的资源文件,没有太大的图片,只是图片数量过多,有将近900张,分别找出一张最大的图片和几张比较大的图片,单独测试,没有发现问题。方法1排除。
2,既然图片数量过多,突破点可能就是图片数量问题。于是分别找了200,400,600图片进行测试,在500左右的时候遇到错误,通过宝哥知道 了将小图片整合存放到一张大图的方法,以此来减少图片的数量,但是仔细想想,加载成bitmap的时候还是要切割成小图生成bitmap,所以对此方法表 示怀疑。由于以前没用过此方法,试试也无妨。所用到的工具是gdx—texturepackger,它只是一个工具,这里就不多说了。测试的最终结果是还 是oom。方法2排除。
3,现在看来,既然不是图片数量的问题,而且会在500张左右的时候报错,那就可能是占用内存大小的问题了,Android手机有内存限制,但是我 的图片大小又大于这个限制,这让我头疼了很长时间,研究国外的一些文章,从中发现了一些有用的信息,这些信息能够加深你对Android的解析 bitmap机制的理解,在此分享:
As of Honeycomb Bitmap data is allocated in the VM heap.
作为蜂窝位图数据是在VM分配堆。)
There is a reference to it in the VM heap (which is small), but the actual data is allocated in the Native heap by the underlying Skia graphics library. 有一个引用在VM堆(小),但实际的数据是在本机堆分配由底层Skia图形库。
Unfortunately, while the definition of BitmapFactory.decode…() says that it returns null if the image data could not be decoded, the Skia implementation (or rather the JNI glue between the Java code and Skia) logs the message you’re seeing (“VM won’t let us allocate xxxx bytes”) and then throws an OutOfMemory exception with the misleading message “bitmap size exceeds VM budget”. 不幸的是,虽然BitmapFactory.decode的定义…()表示,它返回null如果图像数据不能解码,Skia实现(或者说JNI胶之间的 Java代码和Skia)日志消息你看到(“VM不会让我们分配xxxx字节”),然后抛出一个OutOfMemory异常与误导信息”位图的大小超过 VM预算”。
The issue is not in the VM heap but is rather in the Native heap. 这个问题不是在VM堆而是在本机堆。
The Natïve heap is shared between running applications, so the amount of free space depends on what other applications are running and their bitmap usage. 本机堆是正在运行的应用程序之间共享,因此空闲空间的大小取决于其他运行程序,他们使用的位图。
However, I have found that getNativeHeapFreeSize() and getNativeHeapSize() are not reliable. 然而,我发现getNativeHeapFreeSize()和getNativeHeapSize()是不可靠的。
The Native heap size varies by platform. 本机堆大小不同的平台。
So at startup, we check the maximum allowed VM heap size to determine the maximum allowed Native heap size. 所以在启动时,我们检查最大允许VM堆大小来确定最大允许本机堆大小。
“Bitmap data is not allocated in the VM heap” — it is allocated on the VM heap as of Honeycomb “位图数据不是在VM分配堆”——这是VM分配的堆在蜂窝Yes. 是的。 As of Honeycomb (v3.0), bitmap data is allocated on the VM heap. 作为蜂窝(v3.0),位图数据堆上分配VM。 So all of the above only applies to Gingerbread (v2.3.x) and before 所以所有上述只适用于姜饼(v2 3 x)和之前
这些信息零零散散,但是不难发现,问题的原因就在于根据Android版本的不同,bitmap data存放的位置是不同的,3.0以前是分配在native heap上,3.0以后是分配在VM heap上。
为了验证这个问题,我们需要抓去heap快照,众所周知,eclipse中的ddms可以查看heap信息,但是不够全面,这里我用到了adb shell dumpsys meminfo+包名 这条命令来查看heap信息,对比两个机子的不同如下:
2.2的
4.0
从中不难发现,bitmap的存放位置根据Android版本的不同真的有所不同。 好了,下面就是找出怎么把图片存放到native heap里就行了,BitmapFactory里就那么几个decode方法,很容易找到BitmapFactory .decodeStream就可以解决。下面贴一下代码:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Config.ARGB_8888;
options.inPurgeable = true;// 允许可清除
options.inInputShareable = true;// 以上options的两个属性必须联合使用才会有效果
String sname = String.format( “xxx.png”, sTowerStyle, j, sDirction, i);
InputStream is = am.open(sname);
arrBmp[ iBmpIndex] = BitmapFactory .decodeStream(is, null, options);
ok搞定收工。
小问题大发现:1.遇到问题,不要急躁。最初遇到这个问题的时候以为很好解决,试了几种方法后还是解决不了,内心难免会有挫败感,这个时候,最需要的是耐心。
2.网上有很多资源,但是能不能查得到就是自己的问题了,我发现那些编程老手们查找问题总是能够准确定位,快速的找到解决方法。以后要加强这方面的锻炼。
3.国内的资源大多偏向解决问题,国外的资源大多偏向分析问题,所以有的时候还是需要多看看外文的一些资料。当然这需要不错的英文功底。当初看外文的资料,头都大了。这是需要加强的一个方面。
Dance In Wind 28/10/12
Bitmap.Config下面有4个参数:
Java代码
Bitmap.Config ALPHA_8
Bitmap.Config ARGB_4444
Bitmap.Config ARGB_8888
Bitmap.Config RGB_565
首先
Java代码
A:Alpha透明度
R:Red红色
G:Green绿色
B:Blue蓝色
然后
Java代码
Bitmap.Config ALPHA_8 图形参数应该由一个字节来表示,应该是一种8位的位图
Bitmap.Config ARGB_4444 图形的参数应该由两个字节来表示 分别用4个bit来记录每个像素的A、R、G、B数据,16色位图
Bitmap.Config ARGB_8888 图形的参数应该由四个字节来表示 分别用8个bit来记录每个像素的A、R、G、B数据,就是常说的32bit位图、256色位图(这个也可能是RGB888这种24bit位图)
Bitmap.Config RGB_565 图形的参数应该由两个字节来表示 分别用5个、6个和5个bit记录像素的R、G、B数据,其中G的6个bit中一个是无效保留的,32色位图
一般情况下我们都是用ARGB_8888 但是它也相对的很占内存
因为一个像素8+8+8+8=32位 8位一个字节 也就是一个像素4个字节 如果是800*480的图片的话 也就是 800*480*4/1024/124 估计也有1M多了
所以作为手机应用开发人员你使用的内存是有限的
作者“TryLoveCatch”