对可能存在的Android内存泄漏一无所知

24
我一直面临着令人烦恼的OutOfMemoryErrors,即使在确保所有位图都已正确缩放的情况下。实际上,问题似乎与位图无关,但我可能是错的。
为了测试和隔离错误,我一直在使用我的导航抽屉(而不是使用返回按钮)在两个活动之间切换(让我们称它们为Main和List)。我可以看到在每次返回时分配的内存增加了约180 KB。
我已经进行了内存转储,并使用Eclipse MAT分析了三个不同的时间点:

Screen1

Screen2

Screen3

我怀疑存在内存泄漏,但我无法找到其原因。根据内存转储文件,似乎是“Remainder”和java.lang.FinalizerReference不断增加。此问题中的用户在内存转储文件中也有很多FinalizerReferences,但答案不太清楚。
我在上一个时间点制作的“泄漏嫌疑报告”并没有提供太多帮助,因为它怀疑android.content.res.Resourcesandroid.graphics.Bitmap,但这些内容似乎并没有随着时间增长而增加:

Screen3LeakReport

在其中一个报告中(遗憾的是,此处未提供),我看到有13个android.widget.ListView实例被指定为可能的泄漏嫌疑对象。
这些内存增加发生在任何活动之间的转换中(不仅仅是我在示例中使用的主列表)。
我如何找到(不明显的?)内存泄漏?我已经苦思冥想很长时间了,所以任何帮助和提示都将非常感激。
编辑:
  • 位图(@OrhanC1):我已经在上述两个活动中注释了任何位图实例化,但内存仍然增加。内存转储仍然显示一些位图,但我认为它们与资源相关,而不是我分配的实际位图。

  • 关于自定义字体(@erakitin):我正在使用它们,但我在我的Application上下文(public class MyApp extends Application)中保留每个Typeface的单个实例,使用单例模式。我尝试注释掉上述两个活动中对字体的任何引用,但内存仍然增加。

  • 我不认为我泄漏了Context (@DigCamara) :我没有在这两个活动中保存任何静态引用,我使用的是ActivityApplication上下文,除了适配器之外。如果我呆在同一个Activity并进行一些屏幕旋转,内存不会增加。

  • 根据@NickT的评论:我可以看到我有许多这两个活动的实例。这些内存增加可能只是后退堆栈中活动数量增加的结果,而不是内存泄漏(我认为操作系统会处理,显然不是)?如果我使用FLAG_ACTIVITY_REORDER_TO_FRONT意图标志,则内存仅在所有不同的活动被实例化(一次)之前增加。对于此问题很有用:Android not killing activities from stack when memory is low


1
你是否每次转换(而不是重用背景实例)都会启动活动的新版本?也许我的回答对您有所帮助:https://dev59.com/RGw15IYBdhLWcg3wFH3f - NickT
当您完全删除位图时会发生什么? - OrhanC1
1
没有看到你的代码,我们只能猜测发生了什么。 - Squonk
@OrhanC1 - 我已经在这两个示例活动中注释了任何位图创建。内存仍然增加。 - user1987392
@user1987392 你的应用程序中使用了自定义字体吗?我之前也遇到过同样的问题,直到我开始“缓存”从资源创建的字体。 - erakitin
显示剩余8条评论
1个回答

11
看起来Remainder增长的原因是后台堆栈中Activity实例数量的增加(例如13个"List" Activity实例和13个"Main" Activity实例)。
我已经更改了我的导航抽屉,这样当用户点击"Home"按钮(带他到应用程序的"Dashboard")时,我设置了Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK标志:该Activity被重用并清除了后台堆栈(实际上是Android指南建议的)。实际上,我应该早就这样做了,因为我不希望创建几个Dashboard("Home")活动的实例。
通过这样做,我可以看到活动被销毁,分配的堆大小(包括"Remaining"片段)减少:

Fixing problem with increasing memory.

在修复这个问题的同时,我也注意到我的一个Activity和它使用的一个Bitmap即使返回栈已经被清除(泄漏),它们仍然没有被销毁。通过使用MAT进行分析,我得出了这个子问题的源头是我在Activity中保留的一个ImageView的引用。通过将以下代码添加到onStop()方法中,我成功地让Activity和Bitmap都被销毁:

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

    ImageView myImage = (ImageView) findViewById(R.id.myImage );
    if(myImage .getDrawable() != null)
        myImage.getDrawable().setCallback(null);

    RoundedImageView roundImage = (RoundedImageView) findViewById(R.id.roundImage); // a custom View
    if(roundImage.getDrawable() != null)
        roundImage.getDrawable().setCallback(null);


}

我随后将所有的ActivityFragmentActivity进行了泛化,以便它们在onDestroy()中调用unbindDrawables(View view)

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

感谢@NickT指引我正确的方向。

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