【问题标题】:OutOfMemoryException in the Emulator模拟器中的 OutOfMemoryException
【发布时间】:2014-06-19 07:52:29
【问题描述】:

我真的不知道如何处理这种情况。突然之间,我在特定 Activity 上收到 OutOfMemoryException ,并且每次在我正在开发的游戏中遵循相同的路径时,它都是相同的。我在我的游戏中使用了很多 Drawable。我在某处读到您应该经常执行 Bitmap.recycle() 以释放内存。但我几乎从不直接使用 Bitmaps,而我使用的 Drawabels,我需要它们出现在屏幕上。

我使用 Facebook 和 Parse SDK 可能没有帮助。

现在我正在手机和模拟器上进行开发。在模拟器中,堆大小不能超过 30 MB。我应该增加这个数字还是应该不理会并尽量减少我的应用程序的内存消耗?

直到最近,我一直认为,一旦我完成一个 Activity 并进入另一个 Activity,一切都会被垃圾回收,我会重新开始 示例代码:

private void startGamesActivity() {    
    Intent intent = new Intent(this, SS3GamesActivity.class);
    startActivity(intent);      
finish();   
}

这不是真的吗? 让我进一步详细说明。我有一个包含两个可能的 Facebook 播放器的 ListView 的活动。当我单击其中一个以启动一项新活动时会发生 OutOfMemoryException,该活动应该比以前消耗更少的内存!这就是为什么我不明白为什么 OutOfMemoryException 甚至会发生!两个 ListView 被垃圾回收后应该还有足够的内存!

所以我现在要做的是分析我的堆消耗。我还下载了 Eclipse 内存分析器。正在使用的最大内存块是 1 字节数组(byte[], boolean[]),计数为 702,总大小为 20,096 MB,其中最大为 6,240 MB,平均为 29,314 MB。这让我很震惊!它甚至不会在整个应用程序中增加很多。但是在模拟器中看到堆大小不会增加超过 30MB,因为有很大的潜力。

使用 Eclipse 内存分析器还给了我两个可能的内存泄漏罪魁祸首。

问题嫌疑人 1 “”加载的“android.graphics.Bitmap”的一个实例占用 6.543.416 (28,00%) 个字节。内存是在 "" 加载的 "byte[]" 的一个实例中累积的。

问题嫌疑人 2 由“”加载的“android.content.res.Resources”类占用5.212.968(22,31%)字节。内存在“”加载的“java.lang.Object[]”的一个实例中累积。

结果并不像我想象的那么简单。我认为从类名来看可能的嫌疑人是我的 LruCache。我使用它来填充可能的玩家照片的 Facebook 缩略图,以便每次用户在 ListView 中滚动时都不会从网络加载它们。我不确定为什么它在我的应用程序开始时就已经这么大了。我已经尝试过让它更小,但没有运气。

这是在我的 MainActivity 的 onCreate 中:

// caching system
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);      
final int cacheSize = maxMemory / 16; // used to be 8, didnt help though

   mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // The cache size will be measured in kilobytes rather than
            // number of items.
            return bitmap.getByteCount() / 1024;
        }
    };  

我还检查了第二个可能的嫌疑人,即资源,我真的无能为力。似乎有我正在使用的所有布局对象,而且我现在正在做的事情不能少用。

在我们这样做的时候,“onDestroy”之类的东西会有所帮助吗?

public class ExampleActivity extends Activity {

    HashMap<String, String> meMap=new HashMap<String, String>();
    static ArrayList<String> meArray = new ArrayList<String>();

    protected void onDestroy() {        
        super.onDestroy();
        meMap.clear();
        meMap = null;

        meArray.clear();
        meArray = null;
    }

}

因为我没有看到内存消耗有太大差异,所以我认为 gc 会自动执行此操作。

对于我的问题,我很乐意接受所有可能的建议。我什至可以提供更多信息,但目前我什至不知道是什么。

绿色的 StackTrace:

05-06 07:37:12.373: I/dalvikvm(529): Wrote stack traces to '/data/anr/traces.txt'
05-06 07:37:12.523: E/dalvikvm-heap(529): Out of memory on a 3601936-byte allocation.
05-06 07:37:12.523: I/dalvikvm(529): "main" prio=5 tid=1 RUNNABLE
05-06 07:37:12.533: I/dalvikvm(529):   | group="main" sCount=0 dsCount=0 obj=0x409c1460 self=0x12810
05-06 07:37:12.533: I/dalvikvm(529):   | sysTid=529 nice=0 sched=0/0 cgrp=default handle=1074082952
05-06 07:37:12.533: I/dalvikvm(529):   | schedstat=( 8842421920 6327029011 3892 ) utm=778 stm=106 core=0
05-06 07:37:12.533: I/dalvikvm(529):   at android.graphics.Bitmap.nativeCreate(Native Method)
05-06 07:37:12.533: I/dalvikvm(529):   at android.graphics.Bitmap.createBitmap(Bitmap.java:605)
05-06 07:37:12.533: I/dalvikvm(529):   at android.graphics.Bitmap.createBitmap(Bitmap.java:551)
05-06 07:37:12.533: I/dalvikvm(529):   at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:437)
05-06 07:37:12.533: I/dalvikvm(529):   at android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:524)
05-06 07:37:12.533: I/dalvikvm(529):   at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:499)
05-06 07:37:12.533: I/dalvikvm(529):   at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:351)
05-06 07:37:12.533: I/dalvikvm(529):   at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:773)
05-06 07:37:12.533: I/dalvikvm(529):   at android.content.res.Resources.loadDrawable(Resources.java:1935)
05-06 07:37:12.533: I/dalvikvm(529):   at android.content.res.TypedArray.getDrawable(TypedArray.java:601)
05-06 07:37:12.533: I/dalvikvm(529):   at android.widget.ImageView.<init>(ImageView.java:119)
05-06 07:37:12.533: I/dalvikvm(529):   at android.widget.ImageView.<init>(ImageView.java:109)
05-06 07:37:12.533: I/dalvikvm(529):   at java.lang.reflect.Constructor.constructNative(Native Method)
05-06 07:37:12.533: I/dalvikvm(529):   at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
05-06 07:37:12.533: I/dalvikvm(529):   at android.view.LayoutInflater.createView(LayoutInflater.java:586)
05-06 07:37:12.533: I/dalvikvm(529):   at com.android.internal.policy.impl.PhoneLayoutInflater.onCreateView(PhoneLayoutInflater.java:56)
05-06 07:37:12.533: I/dalvikvm(529):   at android.view.LayoutInflater.onCreateView(LayoutInflater.java:653)
05-06 07:37:12.543: I/dalvikvm(529):   at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:678)
05-06 07:37:12.543: I/dalvikvm(529):   at android.view.LayoutInflater.rInflate(LayoutInflater.java:739)
05-06 07:37:12.543: I/dalvikvm(529):   at android.view.LayoutInflater.rInflate(LayoutInflater.java:742)
05-06 07:37:12.543: I/dalvikvm(529):   at android.view.LayoutInflater.inflate(LayoutInflater.java:489)
05-06 07:37:12.543: I/dalvikvm(529):   at android.view.LayoutInflater.inflate(LayoutInflater.java:396)
05-06 07:37:12.543: I/dalvikvm(529):   at android.view.LayoutInflater.inflate(LayoutInflater.java:352)
05-06 07:37:12.543: I/dalvikvm(529):   at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:251)
05-06 07:37:12.543: I/dalvikvm(529):   at android.app.Activity.setContentView(Activity.java:1835)
05-06 07:37:12.543: I/dalvikvm(529):   at com.quizdom.SS7ChooseTopicsActivity.onCreate(SS7ChooseTopicsActivity.java:36)
05-06 07:37:12.543: I/dalvikvm(529):   at android.app.Activity.performCreate(Activity.java:4465)
05-06 07:37:12.543: I/dalvikvm(529):   at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1049)
05-06 07:37:12.543: I/dalvikvm(529):   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1920)
05-06 07:37:12.543: I/dalvikvm(529):   at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1981)
05-06 07:37:12.543: I/dalvikvm(529):   at android.app.ActivityThread.access$600(ActivityThread.java:123)
05-06 07:37:12.543: I/dalvikvm(529):   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1147)
05-06 07:37:12.543: I/dalvikvm(529):   at android.os.Handler.dispatchMessage(Handler.java:99)
05-06 07:37:12.543: I/dalvikvm(529):   at android.os.Looper.loop(Looper.java:137)
05-06 07:37:12.543: I/dalvikvm(529):   at android.app.ActivityThread.main(ActivityThread.java:4424)
05-06 07:37:12.543: I/dalvikvm(529):   at java.lang.reflect.Method.invokeNative(Native Method)
05-06 07:37:12.543: I/dalvikvm(529):   at java.lang.reflect.Method.invoke(Method.java:511)
05-06 07:37:12.543: I/dalvikvm(529):   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
05-06 07:37:12.543: I/dalvikvm(529):   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
05-06 07:37:12.543: I/dalvikvm(529):   at dalvik.system.NativeStart.main(Native Method)

【问题讨论】:

  • 您是否尝试过清单中的 Large heap size = true。 xml
  • 是的,对模拟器没有影响
  • 也许您的布局正遭受过度绘制的困扰?如果是这样,您的布局使用的内存比必要的多。在此处阅读有关透支的信息:udinic.wordpress.com/tag/overdraw

标签: java android memory


【解决方案1】:

显然我没有看到您正在加载的位图,但我猜它们比您显示它们的空间所需的要大得多。例如,如果您下载 3000 x 2000 的图像,然后将其指定为只有 300 x 200 的 ImageView 的源,则原始位图大小仍存储在内存中,只是显示得更小。重要的是,从 Web 下载图像时,在将接收到的 Bitmap 设置为 ImageView 的源之前,将其调整为显示大小。不这样做会消耗大量内存。

这是相当多的代码,因为您需要在此过程中防范许多各种错误情况。我可以为您提供的最简单的解决方案是尝试Picasso 库。您可以使用类似于下面显示的代码将要下载的 URL 传递给它,它将在后台线程上下载,调整大小并在准备好时将其放入 ImageView。它还将缓存调整大小的版本,因此,如果您需要下载相同的图像,如果它适合您正在加载的 ImageView,它会使用缓存的版本。这意味着您也不会浪费网络流量。如果您需要在将图像放入 ImageView 之前对图像进行一些转换,您也可以通过提供 Transform 对象来实现。您还可以设置一个占位符图像,您应该在 drawable 文件夹中拥有该图像,它会显示该图像,直到下载实际图像为止。

Picasso.with(context)
       .load("http://i.imgur.com/DvpvklR.png")
       .into(imageView);

【讨论】:

  • 感谢这个库的回答和建议。我肯定会在未来的项目中检查它,但我认为这不是我问题的答案。我使用的所有图片(除了我已经使用最小尺寸的 facebook 个人资料图片)都捆绑在我的应用程序中
  • 您还可以在本地图像上使用 Picasso,然后您将获得与加载远程 URL 相同的所有好处。我很想知道您是否解决了 OutOfMemoryException 以及您必须做什么。
【解决方案2】:

我认为问题在于您正在泄漏当前的Activity。您将this 作为第一个参数传递,在本例中为Context。因此,您将当前活动用作上下文,接下来您要做的就是完成它。 Activity 被关闭,但由于您传递了对 Intent 的引用,它的内存被泄露。

试试这个:

Intent intent = new Intent(getApplicationContext(), SS3GamesActivity.class);

【讨论】:

  • 我更改了所有使用“this”的实例来启动 getApplicationContext() 的新意图,但这对内存消耗没有任何影响
  • 为什么不能在那里使用它而不是 getApplicationContext()?
  • 我已经在回答中解释过了。如果你使用“this”然后完成Activity,它的内存将是leaked
  • 不正确 - 调用 finish() 删除引用不会导致内存泄漏。你不需要在一个正在打开另一个活动的活动上调用finish()——这会将它从后退堆栈中删除,这样后退按钮就不能正常工作。不删除引用时内存泄漏。这通常是由与父类有循环引用的匿名子类引起的。
【解决方案3】:

基本步骤

根据this answer,在这种情况下您应该遵循一些基本步骤:

  • 在 Android 3.0+ 上,在传递给 BitmapFactory 的 BitmapOptions 上使用 inBitmap,以重用现有内存而不是分配新内存
  • recycle() 完成后的位图对象
  • 通常注意内存分配,因为 Android 的垃圾收集器是非压缩的,因此最终您将无法再次分配大块内存
  • 使用MAT 查看您是否在导致问题的某个地方泄漏内存。

一些可能有用的帖子

请参阅有关Managing Bitmap Memory 的帖子,以及有关Loading Large Bitmaps 的帖子。这些可能会对您有所帮助。

您还应该在ListView 上处理您的视图回收。请参阅这篇关于Handling ListView Recycling 的帖子。

希望对你有所帮助。

【讨论】:

    猜你喜欢
    • 2011-06-08
    • 1970-01-01
    • 1970-01-01
    • 2015-02-01
    • 2015-08-31
    • 2017-07-28
    • 1970-01-01
    • 2015-01-08
    • 2010-12-09
    相关资源
    最近更新 更多