将图像加载到 Bitmap 对象时出现奇怪的 OutOfMemory 问题
我有一个 ListView,每行都有几个图像按钮。 当用户单击列表行时,它会启动一个新活动。 由于相机布局问题,我不得不构建自己的选项卡。 针对结果启动的活动是地图。 如果我单击按钮启动图像预览(从 SD 卡加载图像),应用程序将从活动返回到结果处理程序的 ListView
活动以重新启动我的新活动,这没什么不仅仅是一个图像小部件。
ListView
上的图像预览是通过光标和 ListAdapter
完成的。 这使得它非常简单,但我不确定如何放置调整大小的图像(即较小的位大小而不是像素作为动态图像按钮的 src 。所以我只是调整了图像的大小 有
问题是当它尝试返回并重新启动第二个活动时,我收到一个 OutOfMemoryError
,
- 没有一种方法可以轻松地逐行构建列表适配器,我可以在哪里动态调整大小(按位)?
这会更好,因为我还需要对每行中的小部件/元素的属性进行一些更改,因为我无法选择一行由于焦点问题,触摸屏(我可以使用滚球。)
- 我知道我可以进行带外调整大小并保存图像,但这并不是我真正想要做的,但是 这
一旦我禁用了 ListView 上的图像,它就会再次正常工作:
就是我的做法:
String[] from = new String[] { DBHelper.KEY_BUSINESSNAME, DBHelper.KEY_ADDRESS,
DBHelper.KEY_CITY, DBHelper.KEY_GPSLONG, DBHelper.KEY_GPSLAT,
DBHelper.KEY_IMAGEFILENAME + ""};
int[] to = new int[] { R.id.businessname, R.id.address, R.id.city, R.id.gpslong,
R.id.gpslat, R.id.imagefilename };
notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
setListAdapter(notes);
其中 R.id.imagefilename 。 是一个ButtonImage
。
这是我的 LogCat:
01-25 05:05:49.877: ERROR/dalvikvm-heap(3896): 6291456-byte external allocation too large for this process.
01-25 05:05:49.877: ERROR/(3896): VM wont let us allocate 6291456 bytes
01-25 05:05:49.877: ERROR/AndroidRuntime(3896): Uncaught handler: thread main exiting due to uncaught exception
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:304)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:149)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:174)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.drawable.Drawable.createFromPath(Drawable.java:729)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ImageView.resolveUri(ImageView.java:484)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ImageView.setImageURI(ImageView.java:281)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.SimpleCursorAdapter.setViewImage(SimpleCursorAdapter.java:183)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.SimpleCursorAdapter.bindView(SimpleCursorAdapter.java:129)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.CursorAdapter.getView(CursorAdapter.java:150)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.AbsListView.obtainView(AbsListView.java:1057)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ListView.makeAndAddView(ListView.java:1616)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ListView.fillSpecific(ListView.java:1177)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ListView.layoutChildren(ListView.java:1454)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.AbsListView.onLayout(AbsListView.java:937)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1108)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.onLayout(LinearLayout.java:922)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:999)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.onLayout(LinearLayout.java:920)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.ViewRoot.performTraversals(ViewRoot.java:771)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.ViewRoot.handleMessage(ViewRoot.java:1103)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.os.Handler.dispatchMessage(Handler.java:88)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.os.Looper.loop(Looper.java:123)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.app.ActivityThread.main(ActivityThread.java:3742)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at java.lang.reflect.Method.invokeNative(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at java.lang.reflect.Method.invoke(Method.java:515)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at dalvik.system.NativeStart.main(Native Method)
01-25 05:10:01.127: ERROR/AndroidRuntime(3943): ERROR: thread attach failed
显示图像时我也遇到了新错误:
22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d
22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri:
22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process.
22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes
22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed
I have a ListView
with a couple of image buttons on each row. When the user clicks the list row, it launches a new activity. I have had to build my own tabs because of an issue with the camera layout. The activity that gets launched for the result is a map. If I click on my button to launch the image preview (load an image off the SD card) the application returns from the activity back to the ListView
activity to the result handler to relaunch my new activity which is nothing more than an image widget.
The image preview on the ListView
is being done with the cursor and ListAdapter
. This makes it pretty simple, but I am not sure how I can put a resized image (I.e. Smaller bit size not pixel as the src
for the image button on the fly. So I just resized the image that came off the phone camera.
The issue is that I get an OutOfMemoryError
when it tries to go back and re-launch the 2nd activity.
- Is there a way I can build the list adapter easily row by row, where I can resize on the fly (bitwise)?
This would be preferable as I also need to make some changes to the properties of the widgets/elements in each row as I am unable to select a row with the touch screen because of the focus issue. (I can use rollerball.)
- I know I can do an out of band resize and save my image, but that is not really what I want to do, but some sample code for that would be nice.
As soon as I disabled the image on the ListView
it worked fine again.
FYI: This is how I was doing it:
String[] from = new String[] { DBHelper.KEY_BUSINESSNAME, DBHelper.KEY_ADDRESS,
DBHelper.KEY_CITY, DBHelper.KEY_GPSLONG, DBHelper.KEY_GPSLAT,
DBHelper.KEY_IMAGEFILENAME + ""};
int[] to = new int[] { R.id.businessname, R.id.address, R.id.city, R.id.gpslong,
R.id.gpslat, R.id.imagefilename };
notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
setListAdapter(notes);
Where R.id.imagefilename
is a ButtonImage
.
Here is my LogCat:
01-25 05:05:49.877: ERROR/dalvikvm-heap(3896): 6291456-byte external allocation too large for this process.
01-25 05:05:49.877: ERROR/(3896): VM wont let us allocate 6291456 bytes
01-25 05:05:49.877: ERROR/AndroidRuntime(3896): Uncaught handler: thread main exiting due to uncaught exception
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:304)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:149)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:174)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.drawable.Drawable.createFromPath(Drawable.java:729)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ImageView.resolveUri(ImageView.java:484)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ImageView.setImageURI(ImageView.java:281)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.SimpleCursorAdapter.setViewImage(SimpleCursorAdapter.java:183)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.SimpleCursorAdapter.bindView(SimpleCursorAdapter.java:129)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.CursorAdapter.getView(CursorAdapter.java:150)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.AbsListView.obtainView(AbsListView.java:1057)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ListView.makeAndAddView(ListView.java:1616)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ListView.fillSpecific(ListView.java:1177)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ListView.layoutChildren(ListView.java:1454)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.AbsListView.onLayout(AbsListView.java:937)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1108)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.onLayout(LinearLayout.java:922)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:999)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.onLayout(LinearLayout.java:920)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.ViewRoot.performTraversals(ViewRoot.java:771)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.ViewRoot.handleMessage(ViewRoot.java:1103)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.os.Handler.dispatchMessage(Handler.java:88)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.os.Looper.loop(Looper.java:123)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.app.ActivityThread.main(ActivityThread.java:3742)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at java.lang.reflect.Method.invokeNative(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at java.lang.reflect.Method.invoke(Method.java:515)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at dalvik.system.NativeStart.main(Native Method)
01-25 05:10:01.127: ERROR/AndroidRuntime(3943): ERROR: thread attach failed
I also have a new error when displaying an image:
22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d
22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri:
22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process.
22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes
22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(30)
我有 iOS 经验,当我发现像加载和显示图像这样基本的问题时,我感到很沮丧。 毕竟,遇到此问题的每个人都在尝试显示合理大小的图像。 无论如何,这里有两个更改解决了我的问题(并使我的应用程序响应非常灵敏)。
1) 每次执行
BitmapFactory.decodeXYZ()
时,请确保传入BitmapFactory.Options
并将inPurgeable
设置为true
(最好将inInputShareable
也设置为true
)。2) 切勿使用 Bitmap.createBitmap(width, height, Config.ARGB_8888)。 我的意思是永远不会! 我从来没有遇到过在几次通过后不会引发内存错误的情况。 再多的
recycle()
、System.gc()
,无论有什么帮助。 它总是引发异常。 另一种实际有效的方法是在可绘制对象中放置一个虚拟图像(或使用上面步骤 1 解码的另一个位图),将其重新缩放为您想要的任何大小,然后操作生成的位图(例如将其传递到 Canvas)为了更多乐趣)。 因此,您应该使用:Bitmap.createScaledBitmap(srcBitmap, width, height, false)
。 如果出于某种原因您必须使用暴力创建方法,那么至少要传递 Config.ARGB_4444。这几乎可以保证为您节省数小时甚至数天的时间。 所有关于缩放图像等的讨论都不起作用(除非您考虑使用错误的尺寸或降级图像作为解决方案)。
I come from iOS experience and I was frustrated to discover an issue with something so basic as loading and showing an image. After all, everyone that is having this issue is trying to display reasonably sized images. Anyway, here are the two changes that fixed my problem (and made my app very responsive).
1) Every time you do
BitmapFactory.decodeXYZ()
, make sure to pass in aBitmapFactory.Options
withinPurgeable
set totrue
(and preferably withinInputShareable
also set totrue
).2) NEVER use
Bitmap.createBitmap(width, height, Config.ARGB_8888)
. I mean NEVER! I've never had that thing not raise memory error after few passes. No amount ofrecycle()
,System.gc()
, whatever helped. It always raised exception. The one other way that actually works is to have a dummy image in your drawables (or another Bitmap that you decoded using step 1 above), rescale that to whatever you want, then manipulate the resulting Bitmap (such as passing it on to a Canvas for more fun). So, what you should use instead is:Bitmap.createScaledBitmap(srcBitmap, width, height, false)
. If for whatever reason you MUST use the brute force create method, then at least passConfig.ARGB_4444
.This is almost guaranteed to save you hours if not days. All that talk about scaling the image, etc. does not really work (unless you consider getting wrong size or degraded image a solution).
这是一个已知错误,并不是因为文件太大。 由于 Android 缓存了 Drawable,因此在使用少量图像后就会出现内存不足的情况。 但我找到了另一种方法,跳过 android 默认缓存系统。
解决方案:
将图像移动到“assets”文件夹并使用以下函数获取 BitmapDrawable:
It's a known bug, it's not because of large files. Since Android Caches the Drawables, it's going out of memory after using few images. But I've found an alternate way for it, by skipping the android default cache system.
Solution:
Move the images to "assets" folder and use the following function to get BitmapDrawable:
我遇到了同样的问题,并通过避免使用 BitmapFactory.decodeStream 或decodeFile 函数来解决它,而是使用 BitmapFactory.decodeFileDescriptor
decodeFileDescriptor
看起来它调用的本机方法与decodeStream/decodeFile 不同。无论如何,有效的是这个(请注意,我添加了一些选项,如上面的一些选项,但这并不是造成差异的原因。关键是调用 BitmapFactory.decodeFileDescriptor 而不是 decodeStream< /strong> 或 decodeFile):
我认为decodeStream/decodeFile 中使用的本机函数有问题。 我已经确认使用decodeFileDescriptor 时会调用不同的本机方法。 另外,我读到的是“图像(位图)不是以标准 Java 方式分配的,而是通过本机调用分配的;分配是在虚拟堆之外完成的,但
算作反对它!”
I had this same issue and solved it by avoiding the BitmapFactory.decodeStream or decodeFile functions and instead used
BitmapFactory.decodeFileDescriptor
decodeFileDescriptor
looks like it calls different native methods than the decodeStream/decodeFile.Anyways, what worked was this (note that I added some options as some had above, but that's not what made the difference. What is critical is the call to BitmapFactory.decodeFileDescriptor instead of decodeStream or decodeFile):
I think there is a problem with the native function used in decodeStream/decodeFile. I have confirmed that a different native method is called when using decodeFileDescriptor. Also what I've read is "that Images (Bitmaps) are not allocated in a standard Java way but via native calls; the allocations are done outside of the virtual heap, but are
counted against it!"
我认为避免 OutOfMemoryError 的最佳方法是面对它并理解它。
我制作了一个应用来故意引发
OutOfMemoryError
,并监视内存使用情况。在我对这个App做了很多实验之后,我得到了以下结论:
我先谈谈Honey Comb之前的SDK版本。
Bitmap 存储在本机堆中,但会自动进行垃圾回收,无需调用 recycle()。
如果 {VM 堆大小} + {分配的本机堆内存} >= {设备的 VM 堆大小限制},并且您尝试创建位图,则会抛出 OOM。
注意:计算的是 VM HEAP SIZE,而不是 VM ALLOCATED MEMORY。
即使分配的虚拟机内存缩小,虚拟机堆大小在增长后也永远不会缩小。
因此,您必须将峰值 VM 内存保持得尽可能低,以防止 VM 堆大小变得太大而无法节省位图的可用内存。
因此,
手动调用 System.gc() 是没有意义的,系统会在尝试增加堆大小之前先调用它。
Native Heap Size 永远不会缩小,但不计入 OOM,因此无需担心。
Native Heap Size 永远不会缩小
那么,我们来谈谈SDK是从Honey Comb开始的。
位图存储在VM堆中,本机内存不计入OOM。
OOM 的条件要简单得多:{VM 堆大小} >= {设备的 VM 堆大小限制}。
因此,您有更多的可用内存来创建具有相同堆大小限制的位图,抛出 OOM 的可能性较小。
因此,您有更多的可用内存来创建具有
以下是我对垃圾收集和内存泄漏的一些观察。
你可以自己在App中看到。 如果一个 Activity 执行了一个在该 Activity 被销毁后仍在运行的 AsyncTask,则该 Activity 在 AsyncTask 完成之前不会被垃圾回收。
这是因为AsyncTask是一个匿名内部类的实例,它保存了Activity的引用。
如果任务在后台线程的 IO 操作中被阻塞,调用 AsyncTask.cancel(true) 不会停止执行。
回调也是匿名内部类,因此如果项目中的静态实例保留它们并且不释放它们,则会泄漏内存。
如果您安排了重复或延迟的任务,例如计时器,并且您没有在 onPause() 中调用 cancel() 和 purge(),则内存将会泄漏。
I think best way to avoid the
OutOfMemoryError
is to face it and understand it.I made an app to intentionally cause
OutOfMemoryError
, and monitor memory usage.After I've done a lot of experiments with this App, I've got the following conclusions:
I'm gonna talk about SDK versions before Honey Comb first.
Bitmap is stored in native heap, but it will get garbage collected automatically, calling recycle() is needless.
If {VM heap size} + {allocated native heap memory} >= {VM heap size limit for the device}, and you are trying to create bitmap, OOM will be thrown.
NOTICE: VM HEAP SIZE is counted rather than VM ALLOCATED MEMORY.
VM Heap size will never shrink after grown, even if the allocated VM memory is shrinked.
So you have to keep the peak VM memory as low as possible to keep VM Heap Size from growing too big to save available memory for Bitmaps.
Manually call System.gc() is meaningless, the system will call it first before trying to grow the heap size.
Native Heap Size will never shrink too, but it's not counted for OOM, so no need to worry about it.
Then, let's talk about SDK Starts from Honey Comb.
Bitmap is stored in VM heap, Native memory is not counted for OOM.
The condition for OOM is much simpler: {VM heap size} >= {VM heap size limit for the device}.
So you have more available memory to create bitmap with the same heap size limit, OOM is less likely to be thrown.
Here is some of my observations about Garbage Collection and Memory Leak.
You can see it yourself in the App. If an Activity executed an AsyncTask that was still running after the Activity was destroyed, the Activity will not get garbage collected until the AsyncTask finish.
This is because AsyncTask is an instance of an anonymous inner class, it holds a reference of the Activity.
Calling AsyncTask.cancel(true) will not stop the execution if the task is blocked in an IO operation in background thread.
Callbacks are anonymous inner classes too, so if a static instance in your project holds them and do not release them, memory would be leaked.
If you scheduled a repeating or delayed task, for example a Timer, and you do not call cancel() and purge() in onPause(), memory would be leaked.
最近看到很多关于OOM异常和缓存的问题。 开发人员指南对此有一篇非常好的文章,但有些往往无法以适当的方式实施它。
因此,我编写了一个示例应用程序来演示 Android 环境中的缓存。 此实现尚未出现 OOM。
查看此答案的末尾以获取源代码的链接。
要求:
功能:
ListView
甩开,它根本不会下载之间的位图。这不包括:
示例代码:
正在下载的图像是来自 Flickr 的图像 (75x75)。 但是,输入您想要处理的任何图像网址,如果超过最大值,应用程序会将其缩小。 在此应用程序中,网址仅位于
String
数组中。LruCache
有一个很好的方法来处理与位图。 但是,在此应用程序中,我将 LruCache 的实例放入我创建的另一个缓存类中,以使应用程序更加可行。Cache.java 的关键内容(
loadBitmap()
方法是最重要的):您不需要编辑 Cache.java 文件中的任何内容,除非您想实现磁盘缓存。
MainActivity.java 的关键内容:
getView()
被频繁调用。 如果我们没有实施检查来确保我们不会在每行启动无限数量的线程,那么在那里下载图像通常不是一个好主意。 Cache.java 检查 rowObject.mBitmapUrl 是否已在任务中,如果是,则不会启动另一个任务。 因此,我们很可能不会超出 AsyncTask 池的工作队列限制。下载:
您可以从 https://www.dropbox.com/s/ 下载源代码pvr9zyl811tfeem/ListViewImageCache.zip。
最后的话:
我已经测试了几周了,还没有遇到任何 OOM 异常。 我已经在模拟器、Nexus One 和 Nexus S 上对此进行了测试。我已经测试了包含高清质量图像的图像 URL。 唯一的瓶颈是下载需要更多时间。
我可以想象只有一种可能的情况会出现 OOM,那就是如果我们下载许多非常大的图像,并且在它们被缩放并放入缓存之前,将同时占用更多内存并导致 OOM。 但无论如何,这都不是一个理想的情况,而且很可能无法以更可行的方式解决。
在评论中报告错误! :-)
I have seen a lot of questions about OOM exceptions and caching lately. The developer guide has a really good article on this, but some tends to fail on implementing it in a suitable way.
Because of this I wrote an example application that demonstrates caching in an Android environment. This implementation has not yet gotten an OOM.
Look at the end of this answer for a link to the source code.
Requirements:
Features:
ListView
away, it simply won't download the bitmaps betweenThis does not include:
Sample code:
The images that are being downloaded are images (75x75) from Flickr. However, put whatever image urls you want to be processed, and the application will scale it down if it exceeds the maximum. In this application the urls are simply in a
String
array.The
LruCache
has a good way to deal with bitmaps. However, in this application I put an instance of anLruCache
inside another cache class that I created in order to get the application more feasible.Cache.java's critical stuff (the
loadBitmap()
method is the most important):You shouldn't need to edit anything in the Cache.java file unless you want to implement disk caching.
MainActivity.java's critical stuff:
getView()
gets called very often. It's normally not a good idea to download images there if we haven't implemented a check that ensure us that we won't start an infinite amount of threads per row. Cache.java checks whether therowObject.mBitmapUrl
already is in a task and if it is, it won't start another. Therefore, we are most likely not exceeding the work queue restriction from theAsyncTask
pool.Download:
You can download the source code from https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip.
Last words:
I have tested this for a few weeks now, I haven't gotten a single OOM exception yet. I have tested this on the emulator, on my Nexus One and on my Nexus S. I have tested image urls that contain images that were in HD quality. The only bottleneck is that it takes more time to download.
There is only one possible scenario where I can imagine that the OOM will appear, and that is if we download many, really big images, and before they get scaled and put into cache, will simultaneously take up more memory and cause an OOM. But that isn't even an ideal situation anyway and it most likely won't be possible to solve in a more feasible way.
Report errors in the comments! :-)
我执行了以下操作来拍摄图像并动态调整其大小。 希望这可以帮助
I did the following to take the image and resize it on the fly. Hope this helps
不幸的是如果以上都不起作用,请将其添加到您的Manifest文件中。 在 application 标签内
unfortunately if None of the Above works, then Add this to your Manifest file. Inside application tag
看来这是一个长期存在的问题,有很多不同的解释。 我采纳了这里两个最常见的答案的建议,但是这些都没有解决我的问题,即虚拟机声称它无法承担执行过程的解码部分的字节数。 经过一番挖掘,我了解到这里真正的问题是解码过程从 NATIVE 堆中获取信息。
请参阅此处: BitmapFactory OOM 让我发疯
这让我进行了另一场讨论我找到了这个问题的更多解决方案。 一种是在图像显示后手动调用
System.gc();
。 但这实际上会使您的应用程序使用更多内存,以减少本机堆。 从 2.0 (Donut) 版本开始,更好的解决方案是使用 BitmapFactory 选项“inPurgeable”。 因此,我只是在o2.inSampleSize=scale;
之后添加了o2.inPurgeable=true;
。有关该主题的更多信息,请参见:内存堆的限制只有 6M 吗?
说了这么多,我对 Java 和 Android 也是个十足的傻瓜。 因此,如果您认为这是解决该问题的糟糕方法,那么您可能是对的。 ;-) 但这对我来说创造了奇迹,我发现现在不可能从堆缓存中运行虚拟机。 我发现的唯一缺点是您正在丢弃缓存的绘制图像。 这意味着如果您直接返回该图像,您每次都会重新绘制它。 就我的应用程序的工作方式而言,这并不是真正的问题。 你的旅费可能会改变。
It seems that this is a very long running problem, with a lot of differing explanations. I took the advice of the two most common presented answers here, but neither one of these solved my problems of the VM claiming it couldn't afford the bytes to perform the decoding part of the process. After some digging I learned that the real problem here is the decoding process taking away from the NATIVE heap.
See here: BitmapFactory OOM driving me nuts
That lead me to another discussion thread where I found a couple more solutions to this problem. One is to call
System.gc();
manually after your image is displayed. But that actually makes your app use MORE memory, in an effort to reduce the native heap. The better solution as of the release of 2.0 (Donut) is to use the BitmapFactory option "inPurgeable". So I simply addedo2.inPurgeable=true;
just aftero2.inSampleSize=scale;
.More on that topic here: Is the limit of memory heap only 6M?
Now, having said all of this, I am a complete dunce with Java and Android too. So if you think this is a terrible way to solve this problem, you are probably right. ;-) But this has worked wonders for me, and I have found it impossible to run the VM out of heap cache now. The only drawback I can find is that you are trashing your cached drawn image. Which means if you go RIGHT back to that image, you are redrawing it each and every time. In the case of how my application works, that is not really a problem. Your mileage may vary.
使用此
bitmap.recycle();
这有助于避免任何图像质量问题。Use this
bitmap.recycle();
This helps without any image quality issue.我已通过以下方式解决了同样的问题。
I have resolved the same issue in the following manner.
我有一个更有效的解决方案,不需要任何形式的扩展。 只需对位图解码一次,然后根据其名称将其缓存在地图中。 然后只需根据名称检索位图并将其设置在 ImageView 中即可。 没有什么需要做的了。
这是可行的,因为解码位图的实际二进制数据不存储在 dalvik VM 堆中。 它存储在外部。 因此,每次解码位图时,它都会在 VM 堆之外分配内存,而这些内存永远不会被 GC 回收。
为了帮助您更好地理解这一点,假设您已将图像保存在可绘制文件夹中。 您只需通过执行 getResources().getDrwable(R.drawable.) 即可获取图像。 这不会每次都解码您的图像,而是在每次调用它时重新使用已经解码的实例。 所以本质上它是被缓存的。
现在,由于您的图像位于某个文件中(或者甚至可能来自外部服务器),因此您有责任缓存已解码的位图实例,以便在任何需要的地方重用。
希望这可以帮助。
I have a much more effective solution which does not need scaling of any sort. Simply decode your bitmap only once and then cache it in a map against its name. Then simply retrieve the bitmap against the name and set it in the ImageView. There is nothing more that needs to be done.
This will work because the actual binary data of the decoded bitmap is not stored within the dalvik VM heap. It is stored externally. So every time you decode a bitmap, it allocates memory outside of VM heap which is never reclaimed by GC
To help you better appreciate this, imagine you have kept ur image in the drawable folder. You just get the image by doing a getResources().getDrwable(R.drawable.). This will NOT decode your image everytime but re-use an already decoded instance everytime you call it. So in essence it is cached.
Now since your image is in a file somewhere (or may even be coming from an external server), it is YOUR responsibility to cache the decoded bitmap instance to be reused any where it is needed.
Hope this helps.
这里有两个问题......
There are two issues here....
这对我有用!
This worked for me!
这里的答案很好,但我想要一个完全可用的类来解决这个问题..所以我做了一个。
这是我的 BitmapHelper 类,它是 OutOfMemoryError 证明:-)
Great answers here, but I wanted a fully usable class to address this problem.. so I did one.
Here is my BitmapHelper class that is OutOfMemoryError proof :-)
上面的答案都不适合我,但我确实想出了一个非常丑陋的解决方法来解决这个问题。 我将一个非常小的 1x1 像素图像作为资源添加到我的项目中,并在调用垃圾收集之前将其加载到我的 ImageView 中。 我认为可能是 ImageView 没有释放 Bitmap,所以 GC 从未拾取它。 它很丑陋,但现在似乎有效。
None of the answers above worked for me, but I did come up with a horribly ugly workaround that solved the problem. I added a very small, 1x1 pixel image to my project as a resource, and loaded it into my ImageView before calling into garbage collection. I think it might be that the ImageView was not releasing the Bitmap, so GC never picked it up. It's ugly, but it seems to be working for now.
这对我有用。
这是在 C# monodroid 上。
您可以轻松更改图像的路径。 这里重要的是要设置的选项。
This works for me.
and this is on C# monodroid.
you can easily change the path of the image. what important here is the options to be set.
这似乎是与社区分享我的用于加载和处理图像的实用程序类的合适地方,欢迎您使用它并自由修改它。
This seems like the appropriate place to share my utility class for loading and processing images with the community, you are welcome to use it and modify it freely.
在我的一个应用程序中,我需要从
相机/图库
拍照。 如果用户点击相机中的图像(可能是 2MP、5MP 或 8MP),图像大小会从kB
秒到MB
秒变化。 如果图像大小小于(或最多 1-2MB),上述代码工作正常,但如果我的图像大小超过 4MB 或 5MB,则OOM
出现在框架中:(那么我已经努力解决这个问题最后我对 Fedor 的代码做了以下改进(感谢 Fedor 提出了这么好的解决方案):)
我希望这能帮助面临同样问题的朋友!
有关更多信息,请参阅此
In one of my application i need to take picture either from
Camera/Gallery
. If user click image from Camera(may be 2MP, 5MP or 8MP), image size varies fromkB
s toMB
s. If image size is less(or up to 1-2MB) above code working fine but if i have image of size above 4MB or 5MB thenOOM
comes in frame :(then i have worked to solve this issue & finally i've made the below improvement to Fedor's(All Credit to Fedor for making such a nice solution) code :)
I hope this will help the buddies facing the same problem!
for more please refer this
几分钟前我刚刚遇到这个问题。 我通过更好地管理列表视图适配器解决了这个问题。 我认为这是我使用的数百个 50x50px 图像的问题,事实证明,每次显示行时我都试图夸大我的自定义视图。 只需测试该行是否已膨胀,我就消除了此错误,并且我使用了数百个位图。 这实际上是针对 Spinner 的,但基本适配器对于 ListView 的工作原理是一样的。 这个简单的修复也极大地提高了适配器的性能。
I just ran into this issue a couple minutes ago. I solved it by doing a better job at managing my listview adapter. I thought it was an issue with the hundreds of 50x50px images I was using, turns out I was trying to inflate my custom view each time the row was being shown. Simply by testing to see if the row had been inflated I eliminated this error, and I am using hundreds of bitmaps. This is actually for a Spinner, but the base adapter works all the same for a ListView. This simple fix also greatly improved the performance of the adapter.
此问题仅发生在 Android 模拟器中。 我在模拟器中也遇到了这个问题,但是当我签入设备时,它工作正常。
所以请检查设备。 它可以在设备中运行。
This issue only happens in Android emulators. I also faced this issue in an emulator but when I checked in a device then it worked fine.
So please check in a device. It may be run in device.
我花了一整天的时间测试这些解决方案,唯一对我有用的是上述获取图像和手动调用 GC 的方法,我知道这不是必需的,但这是唯一有效的方法当我将我的应用程序置于重负载测试活动之间切换时。 我的应用程序在列表视图中有一个缩略图列表(假设是活动 A),当您单击其中一个图像时,它会将您带到另一个活动(假设是活动 B),该活动显示该项目的主图像。 当我在两个活动之间来回切换时,我最终会收到 OOM 错误,并且应用程序将强制关闭。
当我到达列表视图的一半时,它就会崩溃。
现在,当我在活动 B 中实现以下操作时,我可以毫无问题地浏览整个列表视图,并继续前进……而且速度非常快。
I've spent the entire day testing these solutions and the only thing that worked for me is the above approaches for getting the image and manually calling the GC, which I know is not supposed to be necessary, but it is the only thing that worked when I put my app under heavy load testing switching between activities. My app has a list of thumbnail images in a listview in (lets say activity A) and when you click on one of those images it takes you to another activity (lets say activity B) that shows a main image for that item. When I would switch back and forth between the two activities, I would eventually get the OOM error and the app would force close.
When I would get half way down the listview it would crash.
Now when I implement the following in activity B, I can go through the entire listview with no issue and keep going and going and going...and its plenty fast.
这里的所有解决方案都需要设置 IMAGE_MAX_SIZE。 这限制了具有更强大硬件的设备,如果图像尺寸太小,它在高清屏幕上看起来很难看。
我提出了一个适用于我的三星 Galaxy S3 和其他几种设备(包括功能较弱的设备)的解决方案,当使用功能更强大的设备时,图像质量会更好。
其要点是计算特定设备上为应用程序分配的最大内存,然后将比例设置为尽可能低而不超过此内存。 代码如下:
我将此位图使用的最大内存设置为最大分配内存的 25%,您可能需要根据需要进行调整,并确保此位图已清理,并且在您使用后不会保留在内存中。使用完毕。 通常,我使用此代码来执行图像旋转(源位图和目标位图),因此我的应用程序需要同时在内存中加载 2 个位图,25% 为我提供了良好的缓冲区,在执行图像旋转时不会耗尽内存。
希望这可以帮助那里的人..
All the solutions here require setting a IMAGE_MAX_SIZE. This limits devices with more powerful hardware and if the image size is too low it looks ugly on the HD screen.
I came out with a solution that works with my Samsung Galaxy S3 and several other devices including less powerful ones, with better image quality when a more powerful device is used.
The gist of it is to calculate the maximum memory allocated for the app on a particular device, then set the scale to be lowest possible without exceeding this memory. Here's the code:
I set the maximum memory used by this bitmap to be 25% of maximum allocated memory, you may need to adjust this to your needs and make sure this bitmap is cleaned up and don't stay in memory when you've finished using it. Typically I use this code to perform image rotation (source and destination bitmap) so my app needs to load 2 bitmaps in memory at the same time, and 25% gives me a good buffer without running out of memory when performing image rotation.
Hope this helps someone out there..
对从 SdCard 或可绘制对象中选择的每个图像使用这些代码来转换位图对象。
使用您的图像路径代替 ImageData_Path.get(img_pos).getPath() 。
use these code for every image in select from SdCard or drewable to convert bitmap object.
use your image path instend of ImageData_Path.get(img_pos).getPath() .
一般来说,如果您正在加载图像,Android 设备堆大小仅为 16MB(因设备/操作系统而异,请参阅帖子 堆大小)并且它的大小超过了16MB,它会抛出内存不足的异常,而不是使用Bitmap,从SD卡或从资源甚至从网络加载图像尝试使用 getImageUri ,加载位图需要更多内存,或者如果您使用该位图完成工作,则可以将位图设置为 null。
Generally android device heap size is only 16MB (varies from device/OS see post Heap Sizes), if you are loading the images and it crosses the size of 16MB , it will throw out of memory exception, instead of using the Bitmap for , loading images from SD card or from resources or even from network try to using getImageUri , loading bitmap require more memory , or you can set bitmap to null if your work done with that bitmap.
我的 2 美分:我通过以下方式解决了位图 OOM 错误:
a)将图像缩放 2 倍
b)使用 Picasso我的 ListView 的自定义适配器中的库,在 getView 中进行一次调用,如下所示:Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);
My 2 cents: i solved my OOM errors with bitmaps by:
a) scaling my images by a factor of 2
b) using Picasso library in my custom Adapter for a ListView, with a one-call in getView like this:
Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);
此类OutofMemoryException无法通过调用System.gc()等方法完全解决。
参考活动生命周期< /strike>活动状态由操作系统本身决定,具体取决于每个进程的内存使用情况和每个进程的优先级。
您可以考虑所使用的每个位图图片的大小和分辨率。 我建议缩小尺寸,重新采样到较低的分辨率,参考图库的设计(一张小图PNG,一张原图。)
Such
OutofMemoryException
cannot be totally resolved by calling theSystem.gc()
and so on .By referring to the Activity Life CycleThe Activity States are determined by the OS itself subject to the memory usage for each process and the priority of each process.
You may consider the size and the resolution for each of the bitmap pictures used. I recommend to reduce the size ,resample to lower resolution , refer to the design of galleries (one small picture PNG , and one original picture.)
此代码将有助于从可绘制对象加载大位图
This code will help to load large bitmap from drawable
要修复 OutOfMemory 错误,您应该执行以下操作:
此
inSampleSize
选项可减少内存消耗。这是一个完整的方法。 首先,它读取图像大小而不解码内容本身。 然后它找到最佳的
inSampleSize
值,它应该是2的幂,最后对图像进行解码。To fix the OutOfMemory error, you should do something like this:
This
inSampleSize
option reduces memory consumption.Here's a complete method. First it reads image size without decoding the content itself. Then it finds the best
inSampleSize
value, it should be a power of 2, and finally the image is decoded.Android 培训 课程,“高效显示位图",提供了一些用于理解和处理异常“java.lang.OutOfMemoryError:位图大小超出 VM 预算”的重要信息加载位图时。
读取位图尺寸和类型
BitmapFactory
类提供了多种解码方法(decodeByteArray()
、decodeFile()
、decodeResource() 等)用于从各种来源创建
位图
。 根据您的图像数据源选择最合适的解码方法。 这些方法尝试为构造的位图分配内存,因此很容易导致OutOfMemory
异常。 每种类型的解码方法都有附加签名,可让您通过 BitmapFactory.Options 类指定解码选项。 解码时将inJustDecodeBounds
属性设置为true
可以避免内存分配,为位图对象返回null
但设置outWidth
,outHeight
和outMimeType
。 该技术允许您在构建位图(和内存分配)之前读取图像数据的尺寸和类型。为了避免 java.lang.OutOfMemory 异常,请在解码之前检查位图的尺寸,除非您绝对相信源会为您提供可预测大小的图像数据,并且适合可用内存。
将缩小版本加载到内存中
既然图像尺寸已知,它们可用于决定是否应将完整图像加载到内存中或是否应加载子采样版本。 以下是需要考虑的一些因素:
例如,如果最终将在
ImageView
中以 128x96 像素缩略图显示,则不值得将 1024x768 像素图像加载到内存中。要告诉解码器对图像进行二次采样,将较小的版本加载到内存中,请在
BitmapFactory.Options
对象中将inSampleSize
设置为true
。 例如,分辨率为 2048x1536 的图像使用inSampleSize
4 进行解码会生成大约 512x384 的位图。 将其加载到内存中需要使用 0.75MB,而不是完整图像的 12MB(假设位图配置为ARGB_8888
)。 以下是根据目标宽度和高度计算样本大小值的方法,该值是 2 的幂:要使用此方法,首先将
inJustDecodeBounds
设置为true 进行解码,传递选项,然后使用新的
inSampleSize值和
inJustDecodeBounds< 再次解码code>set tofalse`:此方法可以轻松地将任意大尺寸的位图加载到显示 100x100 像素缩略图的
ImageView
中,如以下示例代码所示:通过根据需要替换适当的
BitmapFactory.decode*
方法,可以遵循类似的过程来解码其他来源的位图。The Android Training class, "Displaying Bitmaps Efficiently", offers some great information for understanding and dealing with the exception `java.lang.OutOfMemoryError: bitmap size exceeds VM budget when loading Bitmaps.
Read Bitmap Dimensions and Type
The
BitmapFactory
class provides several decoding methods (decodeByteArray()
,decodeFile()
,decodeResource()
, etc.) for creating aBitmap
from various sources. Choose the most appropriate decode method based on your image data source. These methods attempt to allocate memory for the constructed bitmap and therefore can easily result in anOutOfMemory
exception. Each type of decode method has additional signatures that let you specify decoding options via theBitmapFactory.Options
class. Setting theinJustDecodeBounds
property totrue
while decoding avoids memory allocation, returningnull
for the bitmap object but settingoutWidth
,outHeight
andoutMimeType
. This technique allows you to read the dimensions and type of the image data prior to the construction (and memory allocation) of the bitmap.To avoid
java.lang.OutOfMemory
exceptions, check the dimensions of a bitmap before decoding it unless you absolutely trust the source to provide you with predictably sized image data that comfortably fits within the available memory.Load a scaled-down version into Memory
Now that the image dimensions are known, they can be used to decide if the full image should be loaded into memory or if a subsampled version should be loaded instead. Here are some factors to consider:
For example, it’s not worth loading a 1024x768 pixel image into memory if it will eventually be displayed in a 128x96 pixel thumbnail in an
ImageView
.To tell the decoder to subsample the image, loading a smaller version into memory, set
inSampleSize
totrue
in yourBitmapFactory.Options
object. For example, an image with resolution 2048x1536 that is decoded with aninSampleSize
of 4 produces a bitmap of approximately 512x384. Loading this into memory uses 0.75MB rather than 12MB for the full image (assuming a bitmap configuration ofARGB_8888
). Here’s a method to calculate a sample size value that is a power of two based on a target width and height:To use this method, first decode with
inJustDecodeBounds
set totrue, pass the options through and then decode again using the new
inSampleSizevalue and
inJustDecodeBoundsset to
false`:This method makes it easy to load a bitmap of arbitrarily large size into an
ImageView
that displays a 100x100 pixel thumbnail, as shown in the following example code:You can follow a similar process to decode bitmaps from other sources, by substituting the appropriate
BitmapFactory.decode*
method as needed.我对 Fedor 的代码做了一个小小的改进。 它基本上做同样的事情,但是没有(在我看来)丑陋的 while 循环,它总是会产生 2 的幂。 感谢 Fedor 提出了最初的解决方案,我一直被困住,直到找到他的解决方案,然后我才能做出这个:)
I've made a small improvement to Fedor's code. It basically does the same, but without the (in my opinion) ugly while loop and it always results in a power of two. Kudos to Fedor for making the original solution, I was stuck until I found his, and then I was able to make this one :)