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


70

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

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”是textView):

tv.setBackgroundResource(R.drawable.yanlis);

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




不,但是正如我所说,我的图片非常小(最大600kb)。我想这是更大的图像。@ 2Dee
UtkuSoytaş2014年

Answers:


146

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

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);

如果设置为大于1的值,则请求解码器对原始图像进行二次采样,返回较小的图像以节省内存。

就显示图像的速度而言,这是BitmapFactory.Options.inSampleSize的最佳用法。文档中提到使用的是2的幂的值,因此我正在使用2、4、8、16等。

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

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

为了告诉解码器对图像进行二次采样,将较小的版本加载到内存中,inSampleSizetrue在您的BitmapFactory.Options对象中将其设置为。例如,以2解码的分辨率为2100 x 1500像素的图像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;
}

注意:根据inSampleSize文档,解码器使用四舍五入到最接近的2的幂的最终值来计算2的幂 。

要使用此方法,请先将inJustDecodeBounds设置为true,然后将选项传递给解码,然后再使用新inSampleSizeinJustDecodeBounds设置为再次解码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);
}

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

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;
}

如何管理应用程序的内存:链接


使用 android:largeHeap="true"Google的摘录对此进行解释不是一个好主意

但是,请求大堆的能力仅适用于少数可以证明需要消耗更多RAM的应用程序(例如大型照片编辑应用程序)。切勿仅仅因为内存用完并且需要快速修复而请求大堆-仅在确切知道所有内存的分配位置以及为什么必须保留它时,才应使用它。但是,即使您确信自己的应用程序可以证明大堆的合理性,也应尽可能避免请求它。使用额外的内存将越来越不利于整体用户体验,因为在任务切换或执行其他常见操作时,垃圾回收将花费更长的时间并且系统性能可能会降低。

经过艰苦的工作,out of memory errors我会说将其添加到清单中以避免oom问题不是罪恶


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

对于运行Android 5.0(API级别21)及更高版本的设备,Android运行时(ART)是默认运行时。该运行时提供了许多功能,可改善Android平台和应用的性能和流畅性。您可以在“简介”中找到有关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的JNI更为严格。使用CheckJNI模式来捕获常见问题是一个特别好的主意。如果您的应用程序使用C / C ++代码,则应阅读以下文章:


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

这是一些有关它的文章:

这是为此而建的图书馆:


3
您的android:largeHeap =“ true”技巧对我有很大帮助。非常感谢先生
Sajedul Karim博士

@Fakher阅读其他链接的先生......这里有一些关于它做了一些职位: - stackoverflow.com/questions/17900732/... - stackoverflow.com/questions/18250951/... ,这里是为它做了一个库: - github.com / AndroidDeveloperLB / AndroidJniBitmapOperations
8:54

2
说好的话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."
sam

1
这是我读过的最完整,最好的答案之一
UserID0908 '18


4

处理位图时,应实现LRU缓存管理器

http://developer.android.com/reference/android/util/LruCache.html http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html 何时应使用LRUCache回收位图?

要么

使用像Universal Image Loader这样的层库:

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

编辑:

现在,当处理图像以及大部分时间使用位图时,我使用Glide,它可以让您配置Glide模块和LRUCache

https://github.com/bumptech/glide


4

很少有提示可以处理此类针对Android Apps的错误/异常:

  1. 活动和应用程序具有以下方法:

    • onLowMemory
    • onTrimMemory处理这些方法以监视内存使用情况。
  2. 清单中的标记可以将属性'largeHeap'设置为TRUE,这将为App沙箱请求更多的堆。

  3. 管理内存中缓存和磁盘缓存:

    • 图像和其他数据可能已在应用程序运行时缓存在内存中(本地存储在活动/片段中以及全局);应该进行管理或删除。
  4. Java实例创建(特别是文件)的WeakReference和SoftReference的使用。

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

  6. 处理内存不足异常

  7. 遵循编码的最佳做法

    • 内存泄漏(不要强力引用所有内容)
  8. 最小化活动堆栈,例如堆栈中的活动数量(不要在上下文/活动中包含所有内容)

    • 上下文是有意义的,不需要超出范围(活动和片段)的那些数据/实例,则将它们保存在适当的上下文中,而不是全局引用持有。
  9. 减少使用静态,增加更多单例。

  10. 照顾好OS基本内存基础

    • 内存碎片问题
  11. 当您确定不再需要内存缓存时,有时会手动使用Involk GC.Collect()。


您能否分享代码如何处理OutOfMemory异常
Chetan Chaudhari

4

如果您收到此错误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"?如果我做到了,那会发生什么?
Dholakiya王子19年

4
如果您编写hardwareAccelerated =“ false”,那么在您的项目中您将不会获得Cardview的海拔,请考虑一下吗?
dileep krishnan

0

android:largeHeap="true" 没有解决错误

就我而言,通过将SVG转换为矢量将图标/图像添加到Drawable文件夹后,出现了此错误。只需简单地转到图标xml文件并为宽度和高度设置较小的数字

android:width="24dp"
android:height="24dp"
android:viewportWidth="3033"
android:viewportHeight="3033"
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.