如何解决Android中的java.lang.OutOfMemoryError故障 [英] How to solve java.lang.OutOfMemoryError trouble in Android

查看:141
本文介绍了如何解决Android中的java.lang.OutOfMemoryError故障的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

根据这个 stackTrace,我在这一行出现了这个错误('tv' 是一个 textView):

tv.setBackgroundResource(R.drawable.yanlis);

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

解决方案

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

<块引用>

android:largeHeap="true"

manifest.xml 中,您可以在清单中添加这些行,它适用于某些情况.

<应用程序机器人:allowBackup =真"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"机器人:大堆=真"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 像素的图像最终会在 ImageView 中显示为 128x128 像素的缩略图,那么将其加载到内存中是不值得的.

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

public static int calculateInSampleSize(BitmapFactory.Options 选项, int reqWidth, int reqHeight) {//图像的原始高度和宽度最终 int 高度 = options.outHeight;最终 int 宽度 = options.outWidth;int inSampleSize = 1;if (height > reqHeight || width > reqWidth) {最终 int halfHeight = 高度/2;最终 int halfWidth = 宽度/2;//计算最大的 inSampleSize 值,它是 2 的幂并保留两者//高度和宽度大于请求的高度和宽度.while ((halfHeight/inSampleSize) > reqHeight&&(halfWidth/inSampleSize) >请求宽度) {样本大小 *= 2;}}返回 inSampleSize;}

<块引用>

注意:计算二值的幂是因为解码器使用最终值通过四舍五入到最接近的二的幂,根据inSampleSize 文档.

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

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,int reqWidth, int reqHeight) {//首先使用 inJustDecodeBounds=true 进行解码以检查尺寸final BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeResource(res, resId, options);//计算 inSampleSizeoptions.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);//使用 inSampleSize 设置解码位图options.inJustDecodeBounds = false;返回 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;尝试 {最终 int IMAGE_MAX_SIZE = 1200000;//1.2MPin = mContentResolver.openInputStream(uri);//解码图像大小BitmapFactory.Options o = new BitmapFactory.Options();o.inJustDecodeBounds = true;BitmapFactory.decodeStream(in, null, o);附寄();整数比例 = 1;而 ((o.outWidth * o.outHeight) * (1/Math.pow(scale, 2)) >IMAGE_MAX_SIZE) {规模++;}Log.d(TAG, "scale = " + scale + ", orig-width: " + o.outWidth + ",原点高度:" + o.outHeight);位图 bitmap = null;in = mContentResolver.openInputStream(uri);如果(比例> 1){规模 - ;//缩放到最大可能 inSampleSize 仍然产生图像//大于目标o = 新 BitmapFactory.Options();o.inSampleSize = 比例;位图 = BitmapFactory.decodeStream(in, null, o);//调整到所需的尺寸int height = bitmap.getHeight();int width = bitmap.getWidth();Log.d(TAG, "第 1 比例操作维度 - 宽度:" + 宽度 + ",高度:" + 高度);double y = Math.sqrt(IMAGE_MAX_SIZE/((((双)宽)/高));双 x = (y/高度) * 宽度;位图 scaledBitmap = Bitmap.createScaledBitmap(bitmap, (int) x,(int) y, 真);位图.recycle();位图 = 缩放位图;System.gc();} 别的 {位图 = BitmapFactory.decodeStream(in);}附寄();Log.d(TAG, "位图大小 - 宽度:" +bitmap.getWidth() + ",高度:" +位图.getHeight());返回位图;} catch (IOException e) {Log.e(TAG, e.getMessage(),e);返回空;}

<小时>

如何管理应用的内存:link

<小时>

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

<块引用>

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

在处理完内存不足错误后,我会说将其添加到清单以避免oom问题并不是罪过

<小时>

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

Android 运行时 (ART) 是运行 Android 5.0(API 级别 21)及更高版本的设备的默认运行时.此运行时提供了许多可提高 Android 平台和应用程序的性能和流畅度的功能.您可以在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),因此您实际上绕过了堆大小限制.

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

这里有一个专为它设计的库:

Altough I have very small size image in drawable folder, I am getting this error from users. And I am not using any bitmap function in code. At least intentionally :)

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)

According to this stackTrace I'm gettin this error at this line ('tv' is a textView):

tv.setBackgroundResource(R.drawable.yanlis);

What is the problem? If you need some other information about code, I can add it. Thanks!

解决方案

You can't increase the heap size dynamically but you can request to use more by using.

android:largeHeap="true"

in the manifest.xml,you can add in your manifest these lines it is working for some situations.

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

Whether your application's processes should be created with a large Dalvik heap. This applies to all processes created for the application. It only applies to the first application loaded into a process; if you're using a shared user ID to allow multiple applications to use a process, they all must use this option consistently or they will have unpredictable results. Most apps should not need this and should instead focus on reducing their overall memory usage for improved performance. Enabling this also does not guarantee a fixed increase in available memory, because some devices are constrained by their total available memory.


To query the available memory size at runtime, use the methods getMemoryClass() or getLargeMemoryClass().

If still facing problem then this should also work

 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.

This is the optimal use of BitmapFactory.Options.inSampleSize with regards to speed of displaying the image. The documentation mentions using values that are a power of 2, so I am working with 2, 4, 8, 16 etc.

Lets get more deeper to Image Sampling:

For example, it’s not worth loading a 1024x768 pixel image into memory if it will eventually be displayed in a 128x128 pixel thumbnail in an ImageView.

To tell the decoder to subsample the image, loading a smaller version into memory, set inSampleSize to true in your BitmapFactory.Options object. For example, an image with resolution 2100 x 1500 pixels that is decoded with an inSampleSize of 4 produces a bitmap of approximately 512x384. Loading this into memory uses 0.75MB rather than 12MB for the full image (assuming a bitmap configuration of ARGB_8888). Here’s a method to calculate a sample size value that is a power of two based on a target width and height:

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

Note: A power of two value is calculated because the decoder uses a final value by rounding down to the nearest power of two, as per the inSampleSize documentation.

To use this method, first decode with inJustDecodeBounds set to true, pass the options through and then decode again using the new inSampleSize value and inJustDecodeBounds set to 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);
}

This method makes it easy to load a bitmap of arbitrarily large size into an ImageView that displays a 100x100 pixel thumbnail, as shown in the following example code:

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

You can follow a similar process to decode bitmaps from other sources, by substituting the appropriate BitmapFactory.decode* method as needed.


I found this code also interesting:

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


How to Manage Your App's Memory: link


It's not a good idea to use android:largeHeap="true" here's the extract from google that explains it,

However, the ability to request a large heap is intended only for a small set of apps that can justify the need to consume more RAM (such as a large photo editing app). Never request a large heap simply because you've run out of memory and you need a quick fix—you should use it only when you know exactly where all your memory is being allocated and why it must be retained. Yet, even when you're confident your app can justify the large heap, you should avoid requesting it to whatever extent possible. Using the extra memory will increasingly be to the detriment of the overall user experience because garbage collection will take longer and system performance may be slower when task switching or performing other common operations.

After working excrutiatingly with out of memory errors i would say adding this to the manifest to avoid the oom issue is not a sin


Verifying App Behavior on the Android Runtime (ART)

The Android runtime (ART) is the default runtime for devices running Android 5.0 (API level 21) and higher. This runtime offers a number of features that improve performance and smoothness of the Android platform and apps. You can find more information about ART's new features in Introducing ART.

However, some techniques that work on Dalvik do not work on ART. This document lets you know about things to watch for when migrating an existing app to be compatible with ART. Most apps should just work when running with ART.


Addressing Garbage Collection (GC) Issues

Under Dalvik, apps frequently find it useful to explicitly call System.gc() to prompt garbage collection (GC). This should be far less necessary with ART, particularly if you're invoking garbage collection to prevent GC_FOR_ALLOC-type occurrences or to reduce fragmentation. You can verify which runtime is in use by calling System.getProperty("java.vm.version"). If ART is in use, the property's value is "2.0.0" or higher.

Furthermore, a compacting garbage collector is under development in the Android Open-Source Project (AOSP) to improve memory management. Because of this, you should avoid using techniques that are incompatible with compacting GC (such as saving pointers to object instance data). This is particularly important for apps that make use of the Java Native Interface (JNI). For more information, see Preventing JNI Issues.


Preventing JNI Issues

ART's JNI is somewhat stricter than Dalvik's. It is an especially good idea to use CheckJNI mode to catch common problems. If your app makes use of C/C++ code, you should review the following article:


Also, you can use native memory (NDK & JNI), so you actually bypass the heap size limitation.

Here are some posts made about it:

and here's a library made for it:

这篇关于如何解决Android中的java.lang.OutOfMemoryError故障的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆