简体   繁体   English

如何避免java.lang.OutOfMemoryError:在使用大位图时,位图大小超过了android中的VM预算?

[英]How can I avoid java.lang.OutOfMemoryError: bitmap size exceeds VM budget in android while using a large bitmap?

I realize that there are many similar questions, but most involve scaling down the bitmap, or explicit calls to bitmap.recycle() and System.gc() which doesn't guarantee anything (and in my case failed to prevent the error). 我意识到有许多类似的问题,但大多数涉及缩小位图,或显式调用bitmap.recycle()和System.gc(),它们不保证任何东西(在我的情况下未能防止错误)。

Edit: I have also tried using isPurgable = true when creating my bitmap. 编辑:我在创建位图时也尝试使用isPurgable = true。

Edit: Also, I have only tested this with Froyo (2.2.2) on Motorola Droid. 编辑:另外,我只在摩托罗拉Droid上用Froyo(2.2.2)进行了测试。

Here is the scenario: I am loading one bitmap (width: 1500, height: 2400). 这是场景:我正在加载一个位图(宽度:1500,高度:2400)。 This takes up roughly 14 MB. 这大约需要14 MB。 The rest of the app is minuscule with regard to memory consumption (easily less than 2 MB). 应用程序的其余部分在内存消耗方面微不足道(容易小于2 MB)。

I am using the bitmap with a transformation matrix to pan and zoom around on a surface view. 我正在使用带有变换矩阵的位图来平移和缩放表面视图。 On first load, this works perfectly. 在第一次加载时,这非常有效。 However, when I exit the app and relaunch it, I get the dreaded OutOfMemoryError. 但是,当我退出应用程序并重新启动它时,我得到了可怕的OutOfMemoryError。 On third launch it works, on fourth it crashes ... and so on. 在第三次发射它是有效的,第四次它崩溃......等等。

I don't need to save state, and so I tried calling finish() in onPause() (as well as the recycle() and gc() methods mentioned above). 我不需要保存状态,所以我尝试在onPause()(以及上面提到的recycle()和gc()方法中调用finish())。 Finish() seems to stop all threads, but does not clear the memory. Finish()似乎会停止所有线程,但不会清除内存。

I should mention that I am also using a technique which I found in a comment from this question . 我应该提一下,我也在使用一种技术,我在这个问题的评论中找到了这种技术 Also Please check this 还请检查一下

So, my image is loaded from the web, as an immutable bitmap. 因此,我的图像是从Web加载的,作为一个不可变的位图。 Its bytes are then saved to sdcard (very slow) just to be reloaded back to a mutable bitmap. 然后将其字节保存到sdcard(非常慢),以便重新加载回可变位图。 If jumping through all these hoops is laughable, please educate me... 如果跳过所有这些篮球是可笑的,请教育我...

For my case, clearing all memory allocated for the app would be acceptable (if it doesn't generate crash messages). 对于我的情况,清除为应用程序分配的所有内存是可以接受的(如果它不生成崩溃消息)。 Is there anyway to just totally clear the memory allocated to my app so that each restart is as clean as the first launch? 无论如何只是完全清除分配给我的应用程序的内存,以便每次重启都像第一次启动一样干净吗?

Is there any solution involving tiling? 有没有涉及平铺的解决方案? Surely I am missing something.. since the image file itself (a png) is only a few kilobytes, and I have viewed larger images in the stock gallery app without this problem. 当然我错过了一些东西..因为图像文件本身(一个png)只有几千字节,我在库存图库应用程序中查看了较大的图像,没有这个问题。

Edit: I have determined the cause of the problem based on insight gleaned from @Jason Lebrun's answer. 编辑:我根据从@Jason Lebrun的回答中收集的见解确定了问题的原因。 It turns out that the canvas I had used to draw on this bitmap held a reference too it, so that canvas needed to be set to null for it to be properly garbage collected. 事实证明,我曾经在这个位图上绘制的画布也有一个引用,所以画布需要设置为null才能正确地进行垃圾回收。 Hope this helps someone with a similar issue. 希望这可以帮助有类似问题的人。

Are you experiencing this problem on Gingberbread, or a different version? 您是否在Gingberbread或其他版本上遇到此问题? Gingerbread has a lot of problems with apps that use a lot of Bitmap memory, so knowing the OS version can help with determining the nature of the problem. Gingerbread在使用大量Bitmap内存的应用程序中存在很多问题,因此了解操作系统版本可以帮助确定问题的本质。

In your case, it's hard to say exactly what might be the cause. 在你的情况下,很难确切地说出可能是什么原因。 However, with a 14MB bitmap, even a 2nd instance is likely to use up your available heap and cause a crash. 但是,使用14MB位图时,即使是第二个实例也可能会耗尽可用堆并导致崩溃。 On Gingerbread, it's pretty easy to end up with Bitmap memory sticking around for longer than it should, due to the concurrent GC + the fact that Bitmap memory is allocated in a native array. 在Gingerbread上,由于并发GC + Bitmap内存在本机数组中分配的事实,因此很容易最终导致Bitmap内存停留的时间超过应有的时间。

When you exit the app, it's probably not being unloaded from memory. 当您退出应用程序时,它可能没有从内存中卸载。 That would explain your crash pattern: the first time you run the app, the large bitmap is loaded. 这可以解释您的崩溃模式:第一次运行应用程序时,会加载大位图。 The 2nd time you launch it, it's actually just restarting an Activity for a process already in memory, and for some reason, the memory for the Bitmap is still hanging around taking up room. 第二次启动它时,它实际上只是为已经在内存中的进程重新启动一个Activity,并且由于某种原因,Bitmap的内存仍然占用空间。 So the app crashes, and a new process is started, with a fresh heap. 因此,应用程序崩溃,并启动一个新进程,并使用新堆。 If recycling is not helping, you might still have a reference to the previous Bitmap sticking around. 如果回收没有帮助,您可能仍然可以参考之前的Bitmap。 If you're always using the same Bitmap, reusing a static reference to it might help, although I'm not sure. 如果你总是使用相同的Bitmap,重复使用静态引用可能有帮助,虽然我不确定。

Are you sure that the Bitmap is not leaked via a leaked context, long running background code, or something similar? 您确定Bitmap不会通过泄露的上下文,长时间运行的后台代码或类似的东西泄露吗?

Other answers here have good advice, which is to try tiling the Bitmap after you fetch it, and only loading tiles as necessary. 这里的其他答案有很好的建议,即在获取后尝试平铺Bitmap,并且只在必要时加载tile。 If you don't need to support older versions, you can use BitmapRegionDecoder (http://developer.android.com/reference/android/graphics/BitmapRegionDecoder.html) to do this, but only on devices that support API level 10 or higher. 如果您不需要支持旧版本,可以使用BitmapRegionDecoder (http://developer.android.com/reference/android/graphics/BitmapRegionDecoder.html)执行此操作,但仅限支持API级别10的设备或更高。

To expand on @jtietema's answer, have you tried loading/rendering only the part of your bitmap that would be visible after applying your transformation matrix? 要扩展@ jtietema的答案,您是否尝试仅加载/渲染应用转换矩阵后可见的位图部分? You could do this by using a bounds-only bitmap for the whole image and transforming that, and using the resulting rectangle as an input to your acquiring-bitmap-from-sd-card. 您可以通过对整个图像使用仅限边界的位图并对其进行转换,并使用生成的矩形作为获取位图从sd卡的输入来完成此操作。

You can use something like the following to decrease the sample size. 您可以使用以下内容来减小样本量。 I use this method in one of my apps to display images from the assets directory. 我在我的一个应用程序中使用此方法来显示assets目录中的图像。 But you can play around with the sample size, I've used values of 1 for images that aren't to big (94kb) and 4 for larger images (1.9mb) 但你可以使用样本大小,我已经使用值1表示不大(94kb)的图像和4表示较大的图像(1.9mb)

protected void loadImageIntoBitmap( String imageAssetFile, Bitmap bitmap, int sampleSize, ImageView imgvw ) throws IOException {
    InputStream is = getContext().getAssets().open( imageAssetFile );
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inSampleSize = sampleSize;
    bitmap = BitmapFactory.decodeStream( is,null,options );
    imgvw.setImageBitmap( bitmap );

    is.close();
}

What are you doing with the bitmap? 你在用位图做什么? The resolution is way higher than that of your android device, so if you are viewing the whole bitmap than scaling it down would do. 分辨率高于你的Android设备的分辨率,所以如果你正在查看整个位图而不是缩小它就可以了。

If you are zooming in you could create just a subset of the bitmap and just load the part that is visible. 如果您正在放大,您可以只创建位图的一个子集,只加载可见的部分。

well whenever you are saving the image to SD card you can use a lower quality of the image like: 无论何时将图像保存到SD卡,您都可以使用较低质量的图像,如:

myBitmap.compress(Bitmap.CompressFormat.PNG, 85, fileOutputStream);

and/or also use the bitmapFactory options to get a smaller image to save memory: 和/或还使用bitmapFactory选项获取较小的图像以节省内存:

BitmapFactory.Options factoryOptions= new BitmapFactory.Options();
factoryOptions.inSampleSize = samplesize;

note that calling recicle() and gc() doesn't mean that the resources will be freed immediatly. 请注意,调用recicle()和gc()并不意味着资源将立即释放。 As the docs say: 正如文档所说:

from myBitmap.recycle() : 来自myBitmap.recycle()

Free the native object associated with this bitmap, and clear the reference to the pixel data. 释放与此位图关联的本机对象,并清除对像素数据的引用。 This will not free the pixel data synchronously; 这不会同步释放像素数据; it simply allows it to be garbage collected if there are no other references . 如果没有其他引用,它只是允许它被垃圾收集 The bitmap is marked as "dead", meaning it will throw an exception if getPixels() or setPixels() is called, and will draw nothing. 位图标记为“死”,这意味着如果调用getPixels()或setPixels(),它将抛出异常,并且不会绘制任何内容。 This operation cannot be reversed, so it should only be called if you are sure there are no further uses for the bitmap. 此操作无法撤消,因此只有在您确定位图没有进一步用途时才应调用此操作。 This is an advanced call, and normally need not be called, since the normal GC process will free up this memory when there are no more references to this bitmap. 这是一个高级调用,通常不需要调用,因为正常的GC进程将在没有更多对此位图的引用时释放此内存。

and from System.gc() : System.gc()

Indicates to the VM that it would be a good time to run the garbage collector. 向VM表明运行垃圾收集器是一个好时机。 Note that this is a hint only. 请注意,这只是一个提示。 There is no guarantee that the garbage collector will actually be run . 无法保证垃圾收集器实际运行

therefore your app might be still using the resources and then whenever you re open the app the system is still using the resources plus the new ones that you are generating, and that might be the reason of why you are getting the out of memory error. 因此,您的应用程序可能仍在使用资源,然后每当您重新打开应用程序时,系统仍在使用资源加上您正在生成的新资源,这可能是您遇到内存不足错误的原因。 So in short handling a large image in memory is not a good idea. 因此,在简短的处理中,内存中的大图像并不是一个好主意。

This method takes in the image path and gives you a drawable without crashing 此方法接收图像路径并为您提供可绘制而不会崩溃

For best possible quality of image,always call this method with argument imageSizeDivide's value =1 为了获得最佳图像质量,请始终使用参数imageSizeDivide的value = 1调用此方法

public Drawable recurseCompressAndGetImage(String image_path,
        int imageSizeDivide) {
    try {

        Log.w("", "imageSizeDivide = " + imageSizeDivide);
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = imageSizeDivide;// controls the quality of image

        // Bitmap           
        Bitmap srcBmp = BitmapFactory
                .decodeFile(image_path.trim(), options);

       //next if-else block converts the image into a squire image.Remove this block if u wish
        if (srcBmp.getWidth() >= srcBmp.getHeight()) {

            dstBmp = Bitmap.createBitmap(srcBmp, srcBmp.getWidth() / 2
                    - srcBmp.getHeight() / 2, 0, srcBmp.getHeight(),
                    srcBmp.getHeight());

        } else {

            dstBmp = Bitmap.createBitmap(srcBmp, 0, srcBmp.getHeight() / 2
                    - srcBmp.getWidth() / 2, srcBmp.getWidth(),
                    srcBmp.getWidth());
        }

        dstBmp = Bitmap.createScaledBitmap(dstBmp, 400, 400, true);

        return new BitmapDrawable(mContext.getResources(), dstBmp);

    } catch (OutOfMemoryError e) {

        //reduce quality and try again
        return recurseCompressAndGetImage(image_path, imageSizeDivide * 2);

    }

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 java.lang.OutOfMemoryError:位图大小超过VM预算 - java.lang.OutOfMemoryError: bitmap size exceeds VM budget java.lang.OutOfMemoryError:位图大小超出VM预算&java.io.FileNotFoundException - java.lang.OutOfMemoryError: bitmap size exceeds VM budget & java.io.FileNotFoundException java.lang.OutOfMemoryError:位图大小超出了Swipey-Viewpager中的VM预算 - java.lang.OutOfMemoryError: bitmap size exceeds VM budget in Swipey-Viewpager java.lang.OutOfMemoryError:位图大小超过第二次运行应用程序时VM预算崩溃 - java.lang.OutOfMemoryError: bitmap size exceeds VM budget crashes on second time running app java OutOfMemoryError:位图大小超出VM预算 - java OutOfMemoryError: bitmap size exceeds VM budget 方向改变时,位图大小超出VM预算 - android - OutofMemoryError - bitmap size exceeds VM budget - on orientation change Android:加载图片时出错:OutOfMemoryError:位图大小超过VM预算 - Android: Error loading images: OutOfMemoryError: bitmap size exceeds VM budget 位图大小超过Vm预算错误android - bitmap size exceeds Vm budget error android 位图大小超过VM预算与使用drawables android - bitmap size exceeds VM budget vs using drawables android 如何修复:java.lang.OutOfMemoryError:请求的数组大小超过 VM 限制,同时为项目运行 Junit - How to fix : java.lang.OutOfMemoryError: Requested array size exceeds VM limit, while running Junit for a project
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM