Android位图限制-阻止java.lang.OutOfMemory
我目前正在与Android平台的一个奇怪行为作斗争——位图/Java堆内存限制。根据设备的不同,Android将应用程序开发人员的Java堆空间限制为16、24或32MIB(或者你可以在根手机上找到任意值)。这可以说是相当小,但相对简单,因为我可以使用以下API衡量使用情况:Android位图限制-阻止java.lang.OutOfMemory,android,out-of-memory,Android,Out Of Memory,我目前正在与Android平台的一个奇怪行为作斗争——位图/Java堆内存限制。根据设备的不同,Android将应用程序开发人员的Java堆空间限制为16、24或32MIB(或者你可以在根手机上找到任意值)。这可以说是相当小,但相对简单,因为我可以使用以下API衡量使用情况: Runtime rt = Runtime.getRuntime(); long javaBytes = rt.totalMemory() - rt.freeMemory(); long javaLimit = rt.max
Runtime rt = Runtime.getRuntime();
long javaBytes = rt.totalMemory() - rt.freeMemory();
long javaLimit = rt.maxMemory();
足够容易;现在开始扭转。在Android中,位图除了少数例外都存储在本机堆中,不计入Java堆。谷歌的一些目光敏锐、纯粹主义的开发人员认为这是“不好的”,并允许开发人员获得“超出其公平份额”。因此,有一段很好的代码,可以计算位图和其他资源产生的本机内存使用量,并将其与Java堆相加,如果你看一下。。。。。java.lang.OutOfMemory。哎哟
但没什么大不了的。我有很多位图,并不总是需要所有的位图。我可以“翻出”一些目前没有使用的:
因此,对于try#1,我重构了代码,这样我就可以用try/catch包装每个位图加载:
while(true) {
try {
return BitmapFactory.decodeResource(context.getResources(), android_id, bitmapFactoryOptions);
} catch (java.lang.OutOfMemory e) {
// Do some logging
// Now free some space (the code below is a simplified version of the real thing)
Bitmap victim = selectVictim();
victim.recycle();
System.gc(); // REQUIRED; else, weird behavior ensues
}
}
请看,下面是一个很好的小日志片段,显示了我捕获异常的代码,并循环使用了一些位图:
E/Epic (23221): OUT_OF_MEMORY (caught java.lang.OutOfMemory)
I/Epic (23221): ArchPlatform[android].logStats() -
I/Epic (23221): LoadedClassCount=0.00M
I/Epic (23221): GlobalAllocSize=0.00M
I/Epic (23221): GlobalFreedSize=0.02M
I/Epic (23221): GlobalExternalAllocSize=0.00M
I/Epic (23221): GlobalExternalFreedSize=0.00M
I/Epic (23221): EpicPixels=26.6M (this is 4 * #pixels in all loaded bitmaps)
I/Epic (23221): NativeHeapSize=29.4M
I/Epic (23221): NativeHeapAllocSize=25.2M
I/Epic (23221): ThreadAllocSize=0.00M
I/Epic (23221): totalMemory()=9.1M
I/Epic (23221): maxMemory()=32.0M
I/Epic (23221): freeMemory()=4.4M
W/Epic (23221): Recycling bitmap 'game_word_puzzle_11_aniframe_005'
I/Epic (23221): BITMAP_RECYCLING: recycled 1 bitmaps worth 1.1M). age=294
E/Epic(23221):内存不足(捕获java.lang.OutOfMemory)
I/Epic(23221):ArchPlatform[android].logStats()-
I/Epic(23221):加载类计数=0.00M
I/Epic(23221):全球定位=0.00M
I/Epic(23221):全球自由度=0.02米
I/Epic(23221):全球卫星定位=0.00M
I/Epic(23221):全球互联网规模=0.00M
I/Epic(23221):EpicPixels=26.6M(在所有加载的位图中为4*像素)
I/Epic(23221):NativeHeapSize=2940万
I/Epic(23221):NativeHeapAllocSize=25.2M
I/Epic(23221):螺纹AllocSize=0.00M
I/Epic(23221):totalMemory()=9.1M
I/Epic(23221):maxMemory()=32.0M
I/Epic(23221):freeMemory()=4.4M
W/Epic(23221):回收位图“游戏、文字、拼图、11、aniframe、005”
I/Epic(23221):位图(回收:回收1张位图,价值110万美元)。年龄=294
请注意totalMemory-freeMemory如何仅为4.7MIB,但有~26?位图占用的本机内存的MiB,我们在31/32 MiB范围内,达到了极限。我仍然有点困惑,因为我的所有加载位图的运行计数是26.6mib,而本地alloc大小只有25.2mib。所以我算错了什么。但这一切都是大概的,并明确表明了跨池“总和”与mem限制的发生
我以为我把它修好了。但不,安卓不会轻易放弃
以下是我从四台测试设备中的两台获得的信息:
I/dalvikvm-heap(17641): Clamp target GC heap from 32.687MB to 32.000MB
D/dalvikvm(17641): GC_FOR_MALLOC freed <1K, 41% free 4684K/7815K, external 24443K/24443K, paused 24ms
D/dalvikvm(17641): GC_EXTERNAL_ALLOC freed <1K, 41% free 4684K/7815K, external 24443K/24443K, paused 29ms
E/dalvikvm-heap(17641): 1111200-byte external allocation too large for this process.
E/dalvikvm(17641): Out of memory: Heap Size=7815KB, Allocated=4684KB, Bitmap Size=24443KB, Limit=32768KB
E/dalvikvm(17641): Trim info: Footprint=7815KB, Allowed Footprint=7815KB, Trimmed=880KB
E/GraphicsJNI(17641): VM won't let us allocate 1111200 bytes
I/dalvikvm-heap(17641): Clamp target GC heap from 32.686MB to 32.000MB
D/dalvikvm(17641): GC_FOR_MALLOC freed <1K, 41% free 4684K/7815K, external 24443K/24443K, paused 17ms
I/DEBUG ( 1505): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
I/DEBUG ( 1505): Build fingerprint: 'verizon_wwe/htc_mecha/mecha:2.3.4/GRJ22/98797:user/release-keys'
I/DEBUG ( 1505): pid: 17641, tid: 17641
I/DEBUG ( 1505): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000
I/DEBUG ( 1505): r0 0055dab8 r1 00000000 r2 00000000 r3 0055dadc
I/DEBUG ( 1505): r4 0055dab8 r5 00000000 r6 00000000 r7 00000000
I/DEBUG ( 1505): r8 000002b7 r9 00000000 10 00000000 fp 00000384
I/DEBUG ( 1505): ip 0055dab8 sp befdb0c0 lr 00000000 pc ab14f11c cpsr 60000010
I/DEBUG ( 1505): d0 414000003f800000 d1 2073646565637834
I/DEBUG ( 1505): d2 4de4b8bc426fb934 d3 42c80000007a1f34
I/DEBUG ( 1505): d4 00000008004930e0 d5 0000000000000000
I/DEBUG ( 1505): d6 0000000000000000 d7 4080000080000000
I/DEBUG ( 1505): d8 0000025843e7c000 d9 c0c0000040c00000
I/DEBUG ( 1505): d10 40c0000040c00000 d11 0000000000000000
I/DEBUG ( 1505): d12 0000000000000000 d13 0000000000000000
I/DEBUG ( 1505): d14 0000000000000000 d15 0000000000000000
I/DEBUG ( 1505): d16 afd4242840704ab8 d17 0000000000000000
I/DEBUG ( 1505): d18 0000000000000000 d19 0000000000000000
I/DEBUG ( 1505): d20 0000000000000000 d21 0000000000000000
I/DEBUG ( 1505): d22 0000000000000000 d23 0000000000000000
I/DEBUG ( 1505): d24 0000000000000000 d25 0000000000000000
I/DEBUG ( 1505): d26 0000000000000000 d27 0000000000000000
I/DEBUG ( 1505): d28 00ff00ff00ff00ff d29 00ff00ff00ff00ff
I/DEBUG ( 1505): d30 0000000000000000 d31 3fe55167807de022
I/DEBUG ( 1505): scr 68000012
I/dalvikvm堆(17641):将目标GC堆从32.687MB钳制到32.000MB
D/dalvikvm(17641):GC_FOR_MALLOC freed每个设备的限制不同(如果要按原样加载位图,请使用第三个链接),或者这里有一些技巧来避免该问题,如:
- 使用应用程序类的onLowMemory()释放一些内存以避免崩溃
- 在解码位图之前,请指示所需的位图大小。查看链接以了解更多信息:
- 当然,还要释放旧位图的内存
用这个小飞贼,对我有用
/**
* Checks if a bitmap with the specified size fits in memory
* @param bmpwidth Bitmap width
* @param bmpheight Bitmap height
* @param bmpdensity Bitmap bpp (use 2 as default)
* @return true if the bitmap fits in memory false otherwise
*/
public static boolean checkBitmapFitsInMemory(long bmpwidth,long bmpheight, int bmpdensity ){
long reqsize=bmpwidth*bmpheight*bmpdensity;
long allocNativeHeap = Debug.getNativeHeapAllocatedSize();
final long heapPad=(long) Math.max(4*1024*1024,Runtime.getRuntime().maxMemory()*0.1);
if ((reqsize + allocNativeHeap + heapPad) >= Runtime.getRuntime().maxMemory())
{
return false;
}
return true;
}
下面是一个使用示例
BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
bmpFactoryOptions.inJustDecodeBounds=true;
BitmapFactory.decodeFile(path,bmpFactoryOptions);
if ( (runInSafeMemoryMode()) && (!Manager.checkBitmapFitsInMemory(bmpFactoryOptions.outWidth, bmpFactoryOptions.outHeight, 2)) ){
Log.w(TAG,"Aborting bitmap load for avoiding memory crash");
return null;
}
我尝试了Application.onLowMemory()。不幸的是,在我的segfault崩溃的复制中,它从未被调用。这与我最终使用的代码非常相似。我使用bitmap.recycle()来释放空间,而不是返回true/false…我尝试了。。。但是如果我使用checkBitmapFitsInMemory(),它将返回false;如果我不使用checkBitmapFitsInMemory(),应用程序将加载位图而不会发生任何崩溃。我的记录值:bmpwidth=93,bmpheight=131,bmpdensity=2,reqsize=24366,heapPad=4194304,allocNativeHeap=33150304,maxMemory=33554432。你知道如何编写一个类似的方法,但效果更好吗?这段代码在Honeycom和更高版本上不起作用,因为位图不再存储在本机堆上了。@vomitcuddle发布Honeycom之后,你要做什么才能更新这段代码,使其正常工作。这只是对Debug.getNativeHeapLocatedSize()的不同调用,并用其他内容替换它吗?另外,您在上面定义的常量是什么。1024个组件表示纹理大小,4个组件表示每像素字节的配置数,0.1只是一个任意数,这样我们就不会将其加载到超过十分之一的可用内存中?@dpk-toMib是一个将大字节计数格式化为更友好的“12.3mib”的助手。。。MiB是更迂腐的MB形式,它意味着base-1024 SI单位而不是base-1000。我可能没有意识到我使用了它,也没有粘贴它。您可以删除它或使其成为您自己喜爱的格式,而不影响代码的意图。
BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
bmpFactoryOptions.inJustDecodeBounds=true;
BitmapFactory.decodeFile(path,bmpFactoryOptions);
if ( (runInSafeMemoryMode()) && (!Manager.checkBitmapFitsInMemory(bmpFactoryOptions.outWidth, bmpFactoryOptions.outHeight, 2)) ){
Log.w(TAG,"Aborting bitmap load for avoiding memory crash");
return null;
}