加载图像到Bitmap对象时出现奇怪的OutOfMemory问题

1386

我有一个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

9
我通过避免使用Bitmap.decodeStream或decodeFile方法,改用BitmapFactory.decodeFileDescriptor方法来解决这个问题。 - Fraggle
2
我几周前也遇到了类似的问题,通过将图像缩小到最佳点来解决它。我在我的博客http://codingjunkiesforum.wordpress.com/2014/06/12/outofmemory-due-to-large-bitmap-handling-in-android/中写了完整的方法,并上传了完整的示例项目,其中包含OOM易感代码与OOM证明代码https://github.com/shailendra123/BitmapHandlingDemo。 - Shailendra Singh Rajawat
6
这个问题的被接受答案正在元社区上讨论。 - rene
2
阅读这篇博客文章:http://codingaffairs.blogspot.com/2016/07/processing-bitmap-and-memory-management.html - Developine
5
这是由于糟糕的Android架构造成的。它应该像iOS和UWP一样自动调整图像大小。我不需要亲自做这些事情。Android开发人员习惯了那种困难,认为它可以正常工作。 - Access Denied
显示剩余3条评论
44个回答

0

避免位图内存泄漏或OOM的最佳实践

  1. 不要将位图引用长时间保留到Context / Activity。
  2. 如果您在应用程序中使用大型位图作为背景或其他内容,则不要将完整图像拉入主内存。您可以使用位图的insample size属性将大小调整为屏幕所需大小。
  3. 不再使用时清除位图引用。

0
我需要将一张大尺寸的图片加载到Bitmap中,我使用了Glide来解决这个问题。首先使用inJustDecodeBounds设置为true的BitmapFactory.Options检查图像大小,然后使用Glide获取Bitmap对象。我使用分析器检查内存使用情况,但是当我使用BitmapFactory.decodeFile()时,我没有看到任何内存峰值。 由于我使用Xamarin编写c#代码,因此需要对Java进行一些微调。 Glide库文档
private Bitmap DecodeFile(File file) {
        // Decode image size
        BitmapFactory.Options options = new BitmapFactory.Options();
        
        // setting inJustDecodeBounds to true won't load the file into memory, 
        // but gives you the actual file size.
        options.InJustDecodeBounds = true;
        BitmapFactory.decodeStream(new FileInputStream(file), null, options);
        int actualWidth = options.OutWidth;
        int actualHeight = options.OutHeight;
                
        var ratio = (double)actualHeight / actualWidth;

        // Default to 800 x 600. changed the size whatever you need.
        var desiredWidth = 800;
        var desiredHeight = 600;

        if(actualHeight > actualWidth)
        {
            var ratio = (double)actualWidth / actualHeight;
            var futureTarget = Glide.With(Application.Context)
                .AsBitmap()
                .Load(file)
                .SetDiskCacheStrategy(DiskCacheStrategy.None)
                .SkipMemoryCache(true)
                .Submit((int)(desiredWidth * ratio), desiredWidth);
            bitmap = futureTarget.Get() as Bitmap;
        }
        else
        {
            var ratio = (double)actualHeight / actualWidth;
            var futureTarget = Glide.With(Application.Context)
                .AsBitmap()
                .Load(file)
                .SetDiskCacheStrategy(DiskCacheStrategy.None)
                .SkipMemoryCache(true)
                .Submit(desiredWidth, (int)(desiredWidth * ratio));
            bitmap = futureTarget.Get() as Bitmap;
        }return bitmap;}

-1
请将以下行添加到您的manifest.xml文件中:
<application

    android:hardwareAccelerated="false"
    android:largeHeap="true">

    <activity>
    </activity>

</application>

请正确格式化您的代码,此代码似乎不是有效的XML。 - Vaibhav Vishal

-10

在将位图设置到ImageView后,可以像这样回收它:

bitmap.recycle();
bitmap=null;

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