安卓中位图的OutOfMemoryError

3

我正在开发一个演示应用程序,其中我正在播放许多图像(位图)。 我正在使用全局位图对象将相同的图像显示到多个Activity中。 但是,当我尝试使用Bitmap.createBitmap(...)创建位图时,我会遇到OutOfMemoryError错误。 我尝试使用这段代码,但仍然出现相同的错误,并且它会导致我的应用程序崩溃并抛出OutOfMemoryError。

我被困在这里。 有人有任何解决方案可以避免这种情况吗??

提前感谢。

在裁剪图像后,我获得位图,因此我正在使用以下代码解码位图大小。

public static Bitmap loadFromBitmap( Context context, Bitmap b, int maxW, int maxH ) throws IOException {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        Bitmap bitmap = null;
        options.inScaled = false;
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        options.inJustDecodeBounds = true;
        BufferedInputStream stream = null;
        ByteArrayOutputStream outstream = new ByteArrayOutputStream();
        b.compress(CompressFormat.JPEG, 100, outstream);
        InputStream is = new java.io.ByteArrayInputStream(outstream.toByteArray());

        stream = new BufferedInputStream(is, 16384 );
        if ( stream != null ) {
            options.inSampleSize = calculateInSampleSize( options, maxW, maxH );
            stream = null;
            stream = new BufferedInputStream(is, 16384 );
        } else {
            return null;
        }
        options.inDither = false;
        options.inJustDecodeBounds = false;
        options.inPurgeable = true;
        bitmap = BitmapFactory.decodeStream( stream, null, options );
        if ( bitmap != null ) bitmap = BitmapUtils.resizeBitmap( bitmap, maxW, maxH );
        return bitmap;
    }

    public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            // Calculate ratios of height and width to requested height and width
            final int heightRatio = Math.round((float) height / (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);

            // Choose the smallest ratio as inSampleSize value, this will guarantee
            // a final image with both dimensions larger than or equal to the
            // requested height and width.
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }

        return inSampleSize;
    }

错误:

02-21 15:32:31.160: E/AndroidRuntime(2951): FATAL EXCEPTION: main
02-21 15:32:31.160: E/AndroidRuntime(2951): java.lang.OutOfMemoryError
02-21 15:32:31.160: E/AndroidRuntime(2951):     at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
02-21 15:32:31.160: E/AndroidRuntime(2951):     at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:483)
02-21 15:32:31.160: E/AndroidRuntime(2951):     at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:351)
02-21 15:32:31.160: E/AndroidRuntime(2951):     at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:374)
02-21 15:32:31.160: E/AndroidRuntime(2951):     at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:404)
02-21 15:32:31.160: E/AndroidRuntime(2951):     at android.app.Activity.performCreate(Activity.java:4465)
02-21 15:32:31.160: E/AndroidRuntime(2951):     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1092)
02-21 15:32:31.160: E/AndroidRuntime(2951):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1924)
02-21 15:32:31.160: E/AndroidRuntime(2951):     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1985)
02-21 15:32:31.160: E/AndroidRuntime(2951):     at android.app.ActivityThread.access$600(ActivityThread.java:127)
02-21 15:32:31.160: E/AndroidRuntime(2951):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1151)
02-21 15:32:31.160: E/AndroidRuntime(2951):     at android.os.Handler.dispatchMessage(Handler.java:99)
02-21 15:32:31.160: E/AndroidRuntime(2951):     at android.os.Looper.loop(Looper.java:137)
02-21 15:32:31.160: E/AndroidRuntime(2951):     at android.app.ActivityThread.main(ActivityThread.java:4447)
02-21 15:32:31.160: E/AndroidRuntime(2951):     at java.lang.reflect.Method.invokeNative(Native Method)
02-21 15:32:31.160: E/AndroidRuntime(2951):     at java.lang.reflect.Method.invoke(Method.java:511)
02-21 15:32:31.160: E/AndroidRuntime(2951):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
02-21 15:32:31.160: E/AndroidRuntime(2951):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
02-21 15:32:31.160: E/AndroidRuntime(2951):     at dalvik.system.NativeStart.main(Native Method)

这个链接会帮助你。 - Ram kiran Pachigolla
谢谢Ram,但是我在我的问题中提到了同样的链接。:( - anddev
发帖代码和日志...使用system.gc();清除垃圾值,当加载图像时。 - appukrb
请查看我编辑过的问题。 - anddev
请问您能否提供更多关于您所尝试做的事情的信息?您是想复制一个位图吗?还是想在多个ImageView上共享同一个Drawable?或者是想重新调整图片的大小? - Ben Max Rubinstein
显示剩余4条评论
5个回答

6
啊,谷歌在安卓中位图处理方面的实现带来了许多美妙的挑战。
每当我遇到内存问题时,我倾向于进行一些内存分析 - 很多时候(虽然不总是),这是因为我们做错了什么(即意外地占用了大量内存)。浏览代码时,我有点困惑需要将代码强制通过三个流,最终又得到一个位图。我感觉自己缺少一些信息才能理解这个过程,但如果我是你,我一定会查看每个步骤分配了多少内存(堆转储 -> hprof-conv -> 内存分析器是你的好朋友)。
然而,我怀疑你可能在寻找的是BitmapRegionDecoder。我最近才发现这个功能,但我认为它几乎完全符合你所需——允许你解码一个位图区域(输入流),而不必将整个位图加载到内存中(你还可以获得宽度/高度)。唯一的缺点是它只适用于Android 2.3.3+,但如今市场份额超过90%,所以这不应该是一个巨大的问题。如果正确使用,我已经使用这个做了一些非常棒的工作(平滑的6000x4000图像的平移和缩放)- 它绝对是内存有效的。

[编辑]

我最近开源了自己的愚蠢实现,使用BitmapRegionDecoder处理非常大的位图的平移、快速滑动和捏合缩放。你可以在https://github.com/micabyte/android_game找到这段代码(ASL2.0)。

您想查看的类是BitmapSurfaceRenderer。到目前为止,我已经使用8000x4000的图像进行了测试,并且它似乎运行得非常好。等我有时间时,我会发布一个简单的示例应用程序,演示如何使用它。

看起来很不错。你有时间放一个简单的示例应用程序吗? - Niels
1
我还没有时间真正做出任何能够展示如何使一切工作的适当事情(特别是使用片段,在我的项目中使用),但是在https://github.com/micabyte/android_game_example上有一个非常简单的示例应用程序,使用BitmapSurfaceRenderer。 - Michael A.

4
尝试在解码之前摆脱outstream,例如将指针置空(关闭这个特定的流不会有任何效果,因为流的类型不同)。
由于您要执行相当广泛的操作,因此我们需要尽可能轻量级...
您的代码还缺少关闭输入流(这不是错误,但在解码后仍应该使用close()),即使在解码流后也要关闭它。
尝试查看Nostras的ImageLoader,因为它能够高效地将图像加载到指定大小的容器中,即调整大小和压缩它们,在您需要处理它们之前。它还支持内容URI,这将帮助您一次完成所有任务。

2

使用options.inPurgeable=true和op.inInputShareable=true编辑您的代码,并在代码中进行一些内存节省的方法。


内存节省的方法是什么?我的意思是哪种类型的方法?你能举个例子吗? - anddev
我已经在我的代码中使用了“options.inPurgeable=true”。还有其他地方需要吗? - anddev
请尝试使用options.inInputShareable=true这两个选项。 - WSS

1
尝试在每次调用loadFromBitmap()之前添加System.gc()

1

在将 Bitmap 对象设置为 null 之前,请使用方法 Bitmap.recycle() 进行回收。

以下是示例:

 public final void setBitmap(Bitmap bitmap) {
   if (this.myBitmapImage != null) {
      this.myBitmapImage.recycle();
   }
    this.myBitmapImage = bitmap;
 }

接下来,您可以按照以下方式更改myBitmapImage,而无需调用System.gc()

 setMyBitmap(anotherBitmap);
   setMyBitmap(null);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接