如何解决 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 沙箱请求更多堆。
管理内存缓存和磁盘缓存:
图像和其他数据可能在应用程序运行时缓存在内存中(本地在活动/片段中和全局中);应该管理或删除。使用 WeakReference,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”问题?