安卓:了解堆大小

30

我对Android开发比较新,但是Java中的内存溢出异常让我困扰已久。我知道这意味着我的应用程序已经超出了VM预算,但是经过多次搜索后,我仍然无法理解这个概念。我担心我的应用程序使用了太多内存,因为每个屏幕都有六个按钮选择器,每个选择器有两个位图,根据属性选项卡,每个位图大约为20 kb。在我的rooted G2x上,我将VM预算设置为12MB,在重新启动手机并运行我的应用程序时没有任何问题。我在每次onDestroy()中取消绑定可绘制对象并提示GC在此处运行。在模拟器中使用应用程序一段时间后,我点击DDMS屏幕上的“Cause GC”按钮,结果如下:ID=1,堆大小6.133 MB,已分配2.895MB,空闲3.238 MB,%使用率47.20,对象数52,623。

这就是我不明白的地方,我的模拟器设置了24MB的VM,这个数字从哪里来?我面临的实际问题是,如果我将模拟器的VM设置为16MB,在第二个活动上我的应用程序就会崩溃并显示内存溢出异常。为什么我设置VM预算为12 MB的手机或12 MB的股票HTC Magic手机上没有崩溃?另外,我的应用程序是否占用了太多内存?我不知道那些DDMS数字是否合适。

至于我的代码,我在XML布局中指定了每个图像。我没有通过编程对它们进行任何操作,只是添加了监听器。我在这里找到了这段代码,并将其添加到我拥有的每个活动中......

@Override
protected void onDestroy() {
    super.onDestroy();

    unbindDrawables(findViewById(R.id.myRootLayout));
    System.gc();
}

private void unbindDrawables(View view) {
    if (view.getBackground() != null) {
        view.getBackground().setCallback(null);
    }
    if (view instanceof ViewGroup && !(view instanceof AdapterView)) {
        for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
            unbindDrawables(((ViewGroup) view).getChildAt(i));
        }
        ((ViewGroup) view).removeAllViews();
    }
}
否则,我只是将onClickListeners添加到具有PNG背景的按钮上。我想学习如何以编程方式指定按钮背景,但我需要使用选择器函数(例如在焦点、按下、非聚焦但已按等情况下)来使按钮背景根据用户交互而更改。我已经查看了相关文档,但感觉有些压力,所以我想从管理堆的基础知识开始,逐步提升到在代码中指定选择器。这可能没有意义,但一个应用程序可以分配的“健康”内存分配量是多少,而不会接近内存不足异常?例如,如果应用程序分配了6MB,则应该没问题,但如果分配8MB,则会有一些问题。内存分配是否有类似于此类的界限?

在处理OutOfMemoryError时需要考虑的另一件事是内存泄漏。请查看Wrangling Dalvik: Memory Management in Android - jk7
2个回答

51
当您在模拟器/设备上设置VM预算时,实际上是告诉堆允许的最大大小。在运行时,堆根据Dalvik VM从操作系统请求的系统内存动态增长。Dalvik VM通常通过分配相对较小的堆来开始。然后,在每次垃圾回收之后,它会检查有多少自由堆内存。如果自由堆与总堆的比率太小,Dalvik VM将向堆中添加更多内存(最多配置的堆大小)。
话虽如此,导致您在DDMS屏幕上看不到“24 mb”的原因是堆尚未增长到其最大大小。这使得Android可以充分利用手持设备上已有的少量内存。
至于为什么您的应用程序在模拟器上崩溃而不是在手机上,这似乎很奇怪(您确定数字是正确的吗?)。但是,您应该牢记,内存是动态管理的,并且总内存利用率是基于许多外部因素(执行垃圾回收的速度/频率等)确定的。
最后,正如我上面提到的原因,根据您提供的单行信息,很难确定您的应用程序管理内存的情况。我们确实需要看到一些您的代码。OutOfMemoryError绝对值得担心,因此我肯定会查看您的应用程序内存使用情况。您可以考虑在运行时使用BitmapFactory类的inSampleSize调用对位图进行采样。这有助于减少加载可绘制位图所需的内存量。或者,您可以减小可绘制物的分辨率(虽然每个20 kb听起来对我来说很好)。

非常感谢!我从中学到了很多东西。我正在将一些信息添加到问题的底部。 - John P.
2
关于分辨率评论“每个20 kb听起来不错” - 据我所知,文件大小并不重要(对于压缩效果更好的图像,文件大小会更小),重要的是尺寸;在内存中,对于全彩色,每个像素需要4B,对吧?因此,在加载图像时根据当前设备的实际需要大小调整图像大小非常重要[Android文档-加载缩小版本到内存中]。 - ToolmakerSteve

19
即使您的应用程序没有达到“24mb”(在设备内变化)堆限制,由于Android需要一段时间来增加应用程序的堆空间,您仍然可能会遇到崩溃。
在我的情况下,我在短时间内创建和转储了几个图像。经常出现OutOfMemoryError
似乎Android无法快速增加应用程序的堆空间。
我相信通过在清单文件中使用largeHeap设置解决了这个问题。启用该设置后,每次Android增加堆时都会留下更多的空闲内存,最小化命中当前限制的机会。
我不使用24MB限制,但这个largeHeap配置非常方便。
您只需要在 AndroidManifest.xml 的应用程序标记上设置 largeHeap =“true”即可。
<application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:largeHeap="true"
    android:theme="@style/AppTheme" >

不过,像 @Alex Lockwood 建议的那样,处理图片时要确保你非常谨慎。


18
谨慎使用 largeHeap="true"。因为这会对您的应用程序性能产生不利影响。这是因为您要告诉系统增加最大堆限制。这样做会导致垃圾回收需要更长的时间。如果您查看日志,可以看到 GC 暂停时间会更长。理想情况下,它应该在2-5ms之间。但是在这种情况下,它可能会变化,甚至高达30-45ms。因此,不要仅仅因为内存不足就将 large heap 属性设置为true。请将其作为最后一步使用。否则,这会影响性能。 - Sanal Varghese
事实上,在我的情况下,我需要动态处理大量高质量的图像,并且在使用largeHeap选项之前,我已经实现了以下代码:
  • 加载采样图像
  • 调整图像大小以完美适配
  • 清除内存中未使用的图像
  • 在“磁盘”中缓存图像 只有使用largeHeap选项才能解决我仍然存在的问题。 但是,像@SanalVarghese建议的那样,在尝试其他所有方法之后再使用它。
- tbraun
只是补充一下,我的应用程序内存存在很多问题,它的大小会大幅增加。也许此时它还没有被有效地设置在一起,但它本应该能够不超过堆限制。然而,它从来不会释放很多资源。出于某种原因,在应用largeHeap选项后,它表现得非常好。非常感谢。 - Mathijs Segers

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