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

我一直面临一些烦人的问题,即使在确保我所有的位图都正确缩放等之后。事实上,这个问题似乎与位图完全无关,但我可能是错的。OutOfMemoryErrors

出于测试和错误隔离的目的,我一直在使用导航抽屉(不使用后退按钮)在两个活动(我们称之为“主”和“列表”)之间切换。我可以看到在DDMS中,每次返回时,分配的内存都会增加约180 KB。

我做过内存转储,并使用日食MAT来分析3个不同的时间点:

Screen1

Screen2

Screen3

我怀疑内存泄漏,但我无法真正找出其原因。根据内存转储,它看起来像是“剩余”,并且还在不断增加。这个问题中的用户在他的内存转储中也有很多,但答案还不是很清楚。java.lang.FinalizerReferenceFinalizerReferences

我在上一个时间点所做的泄漏嫌疑人报告并不是很有帮助,因为它怀疑并且似乎没有随着时间的推移而增长:android.content.res.Resourcesandroid.graphics.Bitmap

Screen3LeakReport

在其中一份报告中(可悲的是,这里没有出现),我看到13个实例被指出为潜在的泄漏嫌疑人。android.widget.ListView

这些内存增加发生在活动之间的任何转换中(不仅仅是我在此示例中使用的Main和List)。

如何找到(不明显的?)内存泄漏?我已经挠了很长时间的头,所以任何帮助和提示都会很棒。

编辑:

  • 位图(@OrhanC1):我已经在上面提到的两个活动中注释了任何实例化,并且内存仍然增加。内存转储仍然显示一些位图,但我相信它们与资源相关,而不是我分配的实际位图。Bitmap

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

  • 我不认为我正在泄漏(@DigCamara):我没有在这两个活动中持有任何静态引用,我使用的是上下文而不是适配器中的's。如果我保持原样并进行一些屏幕旋转,则内存不会增加。ContextApplicationActivityActivity

  • 根据@NickT的评论:我可以看到我有很多两个活动的例子。这些内存增加是否只是由于后退堆栈的活动数量增加而不是内存泄漏(我虽然操作系统处理过,但显然没有)?如果我使用 intent 标志,则内存只会增加,直到所有不同的活动都实例化(一次)。对此很有用:当内存不足时,Android不会从堆栈中杀死活动FLAG_ACTIVITY_REORDER_TO_FRONT


答案 1

看起来增长的原因是后退堆栈中实例数量的增长(例如,“List”的13个实例+ “Main”的13个实例)。RemainderActivityActivityActivity

我已经更改了我的导航抽屉,以便当用户单击“主页”按钮(将他带到应用程序的“仪表板”)时,我设置了标志:活动被重用,后退堆栈被清除(实际上,正如Android指南所建议的那样)。实际上,我应该已经这样做了,因为我不希望创建仪表板(“主页”)活动的多个实例。Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK

通过这样做,我可以看到活动被销毁并且分配的堆大小(包括“”片)减小:Remaining

Fixing problem with increasing memory.

在修复此问题时,我还注意到我的一个活动及其使用的位图没有被破坏,即使后堆栈已被清除(泄漏)。在对MAT进行分析后,我得出结论,这个子问题的根源是我对我保存在.通过将此代码添加到该方法中,我设法同时获取了活动并销毁了该活动:ImageViewActivityonStop()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);


}

然后,我概括了所有的,以便他们调用:ActivityFragmentActivityunbindDrawables(View view)onDestroy()

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为我指出了正确的方向。


答案 2

推荐