如何解决 Android 中的 java.lang.OutOfMemoryError 问题

Posted

技术标签:

【中文标题】如何解决 Android 中的 java.lang.OutOfMemoryError 问题【英文标题】:How to solve java.lang.OutOfMemoryError trouble in Android 【发布时间】:2014-11-01 09:34:30 【问题描述】:

虽然我在可绘制文件夹中有非常小的图像,但我从用户那里收到了这个错误。而且我没有在代码中使用任何位图函数。至少是故意的:)

java.lang.OutOfMemoryError
    at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
    at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:683)
    at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:513)
    at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:889)
    at android.content.res.Resources.loadDrawable(Resources.java:3436)
    at android.content.res.Resources.getDrawable(Resources.java:1909)
    at android.view.View.setBackgroundResource(View.java:16251)
    at com.autkusoytas.bilbakalim.SoruEkrani.cevapSecimi(SoruEkrani.java:666)
    at com.autkusoytas.bilbakalim.SoruEkrani$9$1.run(SoruEkrani.java:862)
    at android.os.Handler.handleCallback(Handler.java:733)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:146)
    at android.app.ActivityThread.main(ActivityThread.java:5602)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1283)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1099)
    at dalvik.system.NativeStart.main(Native Method)

根据这个stackTrace,我在这一行遇到了这个错误('tv' is a textView):

tv.setBackgroundResource(R.drawable.yanlis);

有什么问题?如果您需要有关代码的其他信息,我可以添加。 谢谢!

【问题讨论】:

你读过这个吗? developer.android.com/training/displaying-bitmaps/… 试试这个:***.com/questions/19558713/… 不,但正如我所说,我的图像尺寸非常小(最大 600kb)。我想这是为了更大的图像。 @2Dee 【参考方案1】:

您不能动态增加堆大小,但您可以通过 using 请求使用更多。

android:largeHeap="true"

manifest.xml 中,您可以在清单中添加在某些情况下有效的这些行。

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:largeHeap="true"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">

您的应用程序的进程是否应该使用大型 Dalvik 堆创建。这适用于为应用程序创建的所有进程。它仅适用于加载到进程中的第一个应用程序;如果您使用共享用户 ID 来允许多个应用程序使用一个进程,则它们都必须一致地使用此选项,否则它们将产生不可预知的结果。 大多数应用程序不应该需要这个,而是应该专注于减少它们的整体内存使用以提高性能。启用此功能也不能保证可用内存的固定增加,因为某些设备受到其总可用内存的限制。


要在运行时查询可用内存大小,请使用方法getMemoryClass()getLargeMemoryClass()

如果仍然遇到问题,那么这也应该有效

 BitmapFactory.Options options = new BitmapFactory.Options();
 options.inSampleSize = 8;
 mBitmapInsurance = BitmapFactory.decodeFile(mCurrentPhotoPath,options);

If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory.

就显示图像的速度而言,这是对 BitmapFactory.Options.inSampleSize 的最佳使用。 文档提到使用 2 的幂的值,所以我使用 2、4、8、16 等。

让我们更深入地了解图像采样:

例如,如果 1024x768 像素的图像最终会显示在 ImageView 中的 128x128 像素缩略图中,则不值得将其加载到内存中。

要告诉解码器对图像进行二次采样,将较小的版本加载到内存中,请在 BitmapFactory.Options 对象中将 inSampleSize 设置为 true。例如,分辨率为 2100 x 1500 像素的图像使用 4 的inSampleSize 进行解码,会生成大约 512x384 的位图。将其加载到内存中使用 0.75MB 而不是 12MB 的完整图像(假设位图配置为ARGB_8888)。下面是一种基于目标宽度和高度计算样本大小值的方法,该值是 2 的幂:

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) 

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) 
            inSampleSize *= 2;
        
    

    return inSampleSize;

注意:由于解码器使用了 通过四舍五入到最接近的 2 次幂,根据 inSampleSize 文档。

要使用此方法,首先将inJustDecodeBounds 设置为true 进行解码,传递选项,然后使用新的inSampleSize 值和inJustDecodeBounds 设置为false 再次解码:

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
    int reqWidth, int reqHeight) 

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);

此方法可以轻松地将任意大尺寸的位图加载到显示 100x100 像素缩略图的ImageView 中,如以下示例代码所示:

mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

您可以按照类似的过程来解码来自其他来源的位图,方法是根据需要替换适当的BitmapFactory.decode* 方法。


我发现这段代码也很有趣:

private Bitmap getBitmap(String path) 

Uri uri = getImageUri(path);
InputStream in = null;
try 
    final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
    in = mContentResolver.openInputStream(uri);

    // Decode image size
    BitmapFactory.Options o = new BitmapFactory.Options();
    o.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(in, null, o);
    in.close();

    int scale = 1;
    while ((o.outWidth * o.outHeight) * (1 / Math.pow(scale, 2)) > 
          IMAGE_MAX_SIZE) 
       scale++;
    
    Log.d(TAG, "scale = " + scale + ", orig-width: " + o.outWidth + ", 
       orig-height: " + o.outHeight);

    Bitmap bitmap = null;
    in = mContentResolver.openInputStream(uri);
    if (scale > 1) 
        scale--;
        // scale to max possible inSampleSize that still yields an image
        // larger than target
        o = new BitmapFactory.Options();
        o.inSampleSize = scale;
        bitmap = BitmapFactory.decodeStream(in, null, o);

        // resize to desired dimensions
        int height = bitmap.getHeight();
        int width = bitmap.getWidth();
        Log.d(TAG, "1th scale operation dimenions - width: " + width + ",
           height: " + height);

        double y = Math.sqrt(IMAGE_MAX_SIZE
                / (((double) width) / height));
        double x = (y / height) * width;

        Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, (int) x, 
           (int) y, true);
        bitmap.recycle();
        bitmap = scaledBitmap;

        System.gc();
     else 
        bitmap = BitmapFactory.decodeStream(in);
    
    in.close();

    Log.d(TAG, "bitmap size - width: " +bitmap.getWidth() + ", height: " + 
       bitmap.getHeight());
    return bitmap;
 catch (IOException e) 
    Log.e(TAG, e.getMessage(),e);
    return null;


如何管理应用的内存:link


使用它不是一个好主意android:largeHeap="true"这里是谷歌的摘录解释它,

但是,请求大堆的能力仅适用于 可以证明需要消耗更多 RAM 的一小部分应用程序(例如 作为大型照片编辑应用程序)。永远不要简单地请求大堆 因为你的内存已经用完了,你需要一个快速修复——你应该 仅当您确切知道所有内存在哪里时才使用它 分配以及为什么必须保留它。然而,即使你很自信 您的应用程序可以证明大堆是合理的,您应该避免要求它 任何可能的程度。使用额外的内存将越来越多 不利于整体用户体验,因为垃圾 收集将花费更长的时间并且系统性能可能会变慢 任务切换或执行其他常见操作。

在与out of memory errors 合作后,我会说将其添加到清单中以避免 oom 问题并不是一种罪过


在 Android 运行时 (ART) 上验证应用行为

Android 运行时 (ART) 是运行 Android 5.0(API 级别 21)及更高版本的设备的默认运行时。此运行时提供了许多可提高 Android 平台和应用程序的性能和流畅性的功能。您可以在Introducing ART找到更多关于ART新功能的信息。

但是,一些适用于 Dalvik 的技术不适用于 ART。本文档让您了解在迁移现有应用程序以与 ART 兼容时需要注意的事项。大多数应用程序应该只在使用 ART 运行时才能工作。


解决垃圾收集 (GC) 问题

在 Dalvik 下,应用程序经常发现显式调用 System.gc() 以提示垃圾回收 (GC) 很有用。这对于 ART 来说应该没那么必要了,特别是当您调用垃圾收集来防止 GC_FOR_ALLOC 类型的出现或减少碎片时。您可以通过调用 System.getProperty("java.vm.version") 来验证正在使用的运行时。如果使用 ART,则属性值为“2.0.0”或更高。

此外,Android 开源项目 (AOSP) 正在开发压缩垃圾收集器,以改进内存管理。因此,您应该避免使用与压缩 GC 不兼容的技术(例如保存指向对象实例数据的指针)。这对于使用 Java 本机接口 (JNI) 的应用程序尤其重要。有关详细信息,请参阅防止 JNI 问题。


防止 JNI 问题

ART 的 JNI 比 Dalvik 的要严格一些。使用 CheckJNI 模式来捕获常见问题是一个特别好的主意。如果您的应用使用 C/C++ 代码,您应该查看以下文章:


此外,您可以使用本机内存 (NDK & JNI),因此您实际上绕过了堆大小限制。

这里有一些关于它的帖子:

How to cache bitmaps into native memory

https://***.com/a/9428660/1761003

JNI bitmap operations , for helping to avoid OOM when using large images

这是为它制作的库:

https://github.com/AndroidDeveloperLB/AndroidJniBitmapOperations

【讨论】:

你的 android:largeHeap="true" 技巧对我帮助很大。非常感谢先生 @Fakher 阅读了其他链接的先生...这里有一些关于它的帖子:-***.com/questions/17900732/… -***.com/questions/18250951/…,这是为它制作的库:-github.com/AndroidDeveloperLB/AndroidJniBitmapOperations 说得好Gattsu:"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." 这是我读过的最完整和最好的答案之一【参考方案2】:

我只看到两个选项:

    您的应用程序存在内存泄漏。 在运行您的应用程序时,设备没有足够的内存。

【讨论】:

那么如何修复我的应用程序中的内存泄漏? 通过检测产生和存储大量数据的地方(通常是由于没有发布图像的问题)。你可能会觉得这很有帮助***.com/questions/2298208/…【参考方案3】:

如果您收到此错误 java.lang.OutOfMemoryError,这是 Android 中最常见的问题。当由于内存空间不足而无法分配对象时,Java 虚拟机 (JVM) 会抛出此错误。

试试这个android:hardwareAccelerated="false" , android:largeHeap="true"在你的 应用程序下的 manifest.xml 文件如下:

<application
  android:name=".MyApplication"
  android:allowBackup="true"
  android:icon="@mipmap/ic_launcher"
  android:label="@string/app_name"
  android:theme="@style/AppTheme"
  android:hardwareAccelerated="false"
  android:largeHeap="true" />

【讨论】:

我为什么要创建android:hardwareAccelerated="false"?如果我做到了,那会发生什么? 如果你写 hardwareAccelerated="false" ,那么在你的项目中你不会得到 Cardview 的提升,考虑一下?【参考方案4】:

在处理位图时应该实现一个 LRU 缓存管理器

http://developer.android.com/reference/android/util/LruCache.html http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html When should I recycle a bitmap using LRUCache?

使用通用图像加载器之类的层库:

https://github.com/nostra13/Android-Universal-Image-Loader

编辑:

现在在处理图像和大多数时候使用位图时,我使用 Glide,它可以让您配置 Glide Module 和 LRUCache

https://github.com/bumptech/glide

【讨论】:

【参考方案5】:

处理 Android 应用此类错误/异常的提示很少:

    Activity & Application 有如下方法:

    onLowMemory onTrimMemory 处理这些方法以监视内存使用情况。

    清单中的标签可以将属性“largeHeap”设置为 TRUE,这会为 App 沙箱请求更多堆。

    管理内存缓存和磁盘缓存:

    图像和其他数据可能在应用程序运行时缓存在内存中(本地在活动/片段中和全局中);应该管理或删除。

    使用 Wea​​kReference,Java 实例创建的 SoftReference,特别是文件。

    如果图像太多,请使用适当的库/数据结构来管理内存、使用加载图像的采样、处理磁盘缓存。

    处理 OutOfMemory 异常

    遵循编码的最佳实践

    内存泄漏(不要用强引用保存所有内容)

    最小化活动堆栈,例如堆栈中的活动数量(不要将所有内容都保存在上下文/活动中)

    上下文是有意义的,那些不需要超出范围(活动和片段)的数据/实例,将它们保存到适当的上下文中,而不是全局引用保存。

    尽量减少静态变量的使用,更多的单例。

    照顾操作系统的基本内存基础

    内存碎片问题

    当您确定不再需要内存缓存时,有时手动调用 GC.Collect()。

【讨论】:

能否分享一下处理OutOfMemory异常的代码【参考方案6】:

android:largeHeap="true" 没有修复错误

在我的情况下,我通过将 SVG 转换为矢量将图标/图像添加到 Drawable 文件夹后出现此错误。很简单,去icon xml文件,设置宽度和高度的小数字

android:
android:
android:viewportWidth="3033"
android:viewportHeight="3033"

【讨论】:

【参考方案7】:

检查图片大小

我直接通过 XML (app:srcCompat) 在 imageview 中加载了大约 350kB 的图像,这导致了 OOM 错误并且应用程序崩溃了。

为了解决这个问题,我使用 Glide 将完全相同的图像加载到同一个图像视图中,并且它起作用了!

课程:减小图片大小/延迟加载图片

【讨论】:

以上是关于如何解决 Android 中的 java.lang.OutOfMemoryError 问题的主要内容,如果未能解决你的问题,请参考以下文章

如何修复 Android Studio 中的 Lottie 错误 - 'Error:Execution failed for task' java.lang.RuntimeException [...

android api < 21(KitKat) 中的 java.lang.VerifyError

如何解决线程“main”中的异常,java.lang.ArithmeticException:/为零? [关闭]

android 应用程序中的 Geckoview 因错误“java.lang.Exception:加载 sqlite 库时出错”而崩溃

如何通过 Anylogic 中的基于代理的建模解决“java.lang.IndexOutOfBoundsException”问题?

如何在android中解决StringIndexOutOfBoundsException