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

Posted

技术标签:

【中文标题】对(可能的)Android 内存泄漏一无所知【英文标题】:Clueless About a (Possible) Android Memory Leak 【发布时间】:2014-07-02 09:13:28 【问题描述】:

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

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

我已经完成了内存转储并使用 eclipse MAT 分析了 3 个不同的时间点:

我怀疑内存泄漏,但我无法真正找出原因。根据内存转储,看起来是“剩余”和java.lang.FinalizerReference 在不断增加。 this question 的用户在他的内存转储中也有很多FinalizerReferences,但答案不是很清楚。

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

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

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

如何找到(不明显?)内存泄漏?很长时间以来我一直在摸不着头脑,所以任何帮助和提示都会很棒。

编辑:

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

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

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

基于@NickT 的评论:我可以看到我有很多这两种活动的实例。这些内存增加可能是只是后台堆栈活动数量增加的结果,而不是内存泄漏(我虽然操作系统处理了这个问题,apparently not)?如果我使用FLAG_ACTIVITY_REORDER_TO_FRONT 意图标志,那么内存只会增加,直到所有不同的活动都被实例化(一次)。对此事有用:Android not killing activities from stack when memory is low。

【问题讨论】:

您是否在每次转换时都启动新版本的活动(而不是重用后台实例)?也许我对此的回答可能有用:***.com/questions/6835398 完全删除位图会发生什么? 没有看到你的代码,我们只能猜测发生了什么。 @Squonk - 我同意你的观点,但在这种特殊情况下它很复杂:因为我真的不知道问题的根源,所以我不得不发布大量代码。跨度> @OrhanC1 - 我已经评论了两个示例活动中的任何位图创建。内存仍在增加。 【参考方案1】:

看起来Remainder 增长的原因是后堆栈中Activity 实例数量的增长(例如,13 个“列表”实例Activity + 13 个“主”实例Activity)。

我已经更改了导航抽屉,以便当用户单击“主页”按钮(将他带到应用程序的“仪表板”)时,我设置了 Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK 标志:重新使用活动并返回堆栈被清除 (as recommended by the Android guidelines, actually)。事实上,我应该已经这样做了,因为我不想创建 Dashboard(“Home”)活动的多个实例。

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

在修复此问题时,我还注意到我的一个活动和它使用的位图没有被破坏,即使后台堆栈已被清除(泄漏)。在用 MAT 分析后,我得出结论,这个子问题的根源是对我保存在 Activity 中的 ImageView 的引用。通过将此代码添加到 onStop() 方法中,我设法使活动和 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 为我指明了正确的方向。

【讨论】:

以上是关于对(可能的)Android 内存泄漏一无所知的主要内容,如果未能解决你的问题,请参考以下文章

如何在长时间运行的 Perl 程序中找到内存泄漏?

分析 ThreadLocal 内存泄漏问题

javascript中的关闭和回调内存泄漏

万恶的前端内存泄漏及万善的解决方案

内存泄漏与垃圾回收机制

Android遇到内存泄漏和性能优化,需要采取以下措施