在片段中保留对视图的引用会导致内存泄漏? [英] Keeping a reference to a View in a Fragment causes memory leaks?

查看:58
本文介绍了在片段中保留对视图的引用会导致内存泄漏?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有人告诉我以下内容,但我有些困惑.

请,您可以确认或提出异议吗?

(片段不是通过 setRetainInstance()

保留的

目前,通常的做法是像这样在Fragments中初始化视图:

  private lateinit var myTextView:TextView有趣的onViewCreated(view:View,bundle:Bundle){...myTextView = view.findViewById(R.id.myTextViewId)...} 

然后我们永远不会使该属性无效.尽管这是一种常见的做法,但它会导致内存泄漏.

背景:

比方说, FragmentA 作为实例字段引用了其 View 的childView.由片段管理器使用特定的FragmentTransaction执行从片段A到B的导航.根据交易的类型,管理中心可能只想杀死 View ,但仍然保留 FragmentA 的实例(请参阅下面的生命周期部分,其中说片段返回到后堆栈的布局").当用户从 FragmentB 导航回 FragmentA 时, FragmentA 的先前实例将显示在最前面,但是会出现一个新的 View 将被创建.

问题是,如果我们在lateinit属性中将实例保留到我们的视图中,并且从不清除对它的引用,则该视图无法完全销毁,从而导致内存泄漏.

解决方案

请问,对此事有官方答复吗?

关于此事的官方答案是

Memory Profiler是Android Profiler中的一个组件,可帮助您会识别出可能导致结结的内存泄漏和内存搅动,冻结,甚至应用程序崩溃.

此报告基于以下操作在上述应用程序上执行(仅考虑我创建的视图,即ConstraintLayout,Framelayout,Button和TextView)

  1. 打开应用程序:可见活动
  2. 在活动"中按下了按钮: FragmentA 可见
  3. 按下FragmentA中的按钮: FragmentB 可见, FragmentA onDestroyView()
  4. 在活动"中按下了按钮:第二个实例 FragmentA 可见,并且 FragmentB onDestroyView().(与上一个示例中的步骤2相同,除了 FragmentB 充当A, FragmentA 的第二个实例充当B)
  5. 在第二个实例FragmentA中按下按钮:可见 FragmentB 的第二个实例和 FragmentA 的第二个实例 onDestroyView().
  6. 按下后退按钮:可见 FragmentA 的第二个实例和 FragmentB onDetach()的第二个实例
  7. 按下后退按钮:第一个实例 FragmentB 可见,第二个实例 FragmentA onDetach()
  8. 按下后退按钮:可见 FragmentA 的第一个实例和 FragmentB onDetach()的第一个实例li>
  9. 按下后退按钮: FragmentA 的第一个实例 onDetach()
  10. 按下后退按钮:,它关闭了应用程序.

观察

如果您查看报告,则可以请参阅第1步中的每个视图,直到关闭该应用程序为止.在第2步中,分配FragmentA的View(即FrameLayout)及其子项,Button,并在第3步中将其清除,这是预期的.在第3步中,分配了FragmentB的View,即FrameLayout及其子TextView,但在第4步中未清除它,因此导致内存泄漏,但在再次创建View并分配新创建的View时在第7步中将其清除了.另一方面,步骤5中创建的视图只是在步骤6中被清除,不会导致内存泄漏,因为片段已分离,并且它们并没有阻止片段被清除.

结论

我们观察到,将视图保存在片段中的泄漏一直持续到用户返回片段为止.当片段被带回时,即调用onCreateView(),泄漏得以恢复.另一方面,当片段在顶部且只能返回时,不会发生泄漏.基于此,我们可以得出以下结论,

  • 当片段中没有远期交易时,将视图保存为强引用没有错,因为它们将在 onDetach()
  • 中清除
  • 如果有远期交易,我们可以存储弱引用视图,以便在 onDestroyView()
  • 中清除它们

PS 如果您不了解堆转储,请观看此链接提供了有关内存泄漏的有价值的信息.

我希望我的回答能帮助您清除困惑.让我知道您是否仍然感到困惑?

Somebody told me the following, but I am a bit perplexed.

Please, would you be able to confirm or dispute it?

(the Fragment is not retained via setRetainInstance()


At the moment it is a common practice to initialize views in Fragments like this:

private lateinit var myTextView: TextView

fun onViewCreated(view: View, bundle: Bundle) {

     ...

     myTextView = view.findViewById(R.id.myTextViewId)

     ...

}

And then we never nullify this property. Though this is a common practise, it is causing a memory leak.

Background to this:

Let's say, FragmentA has a reference to a childView of it's View, as an instance field. Navigation from fragment A to B is executed by FragmentManager using a specific FragmentTransaction. Depending on the type of transaction, the Manager might want to kill the View only but still persist the instance of FragmentA(see below lifecycle part where it says "The fragment returns to the layout from the back stack"). When user navigates back from FragmentB to FragmentA, the previous instance of FragmentA will be brought to the front, but a new View will be created.

The issue is that if we keep instance to our view in the lateinit property and never clear the reference to it, the view cannot be fully destroyed, causing memory leak.

解决方案

Please, is there an official answer on the matter?

An official answer regarding this matter is,

The Memory Profiler is a component in the Android Profiler that helps you identify memory leaks and memory churn that can lead to stutter, freezes, and even app crashes.

In this documentation, android officials teach you how to figure out memory leak by yourself so that they don't have to answer on each and every test cases a user may perform. Besides you can use LeakCanary which does a great job in detecting memory leak.

For your convenience, I performed a heap analysis (a similar but extended version of your use case). Before showing the analysis report I would like to give a step by step basic overview of how the memories will be de/allocated in your case,

  1. On open FragmentA: It's content/root View and the TextView will be allocated into the memory.

  2. On Navigate to FragmentB: onDestroyView() of FragmentA will be called, but FragmentA's View can not be destroyed because the TextView holds a strong reference to it and FragmentA holds a strong reference to TextView.

  3. On Navigate back to FragmentA from FragmentB: The previous allocation of the View and TextView will be cleared. At the same time, they will get new allocations as the onCreateView() is called.

  4. On Back press from FragmentA: The new allocations will be cleared as well.

Answer to your question:

In step 2, we can see there is a memory leak as the retain memory of View is not freed up what it was supposed to be. On the other hand, from step 3 we can see that the memory is recovered as soon as the user returns back to the Fragment. So we can figure out that this kind of memory leak persist till the FragmentManager brings the Fragment back.

Example / Statical Analysis

To test your case, I have created an application. My application has an Activity with a Button and a FrameLayour which is the container for fragments. Pressing the Button will replace the container with FragmentA. FragmentA contains a Button, pressing that will replace the container with FragmentB. FragmentB has a TextView which is stored in the fragment as an instance field.

This report is based on the following operation performed on the above application (Only the Views that I created i.e. ConstraintLayout, Framelayout, Button and TextView are taken in consideration),

  1. Opened the app: Activity visible
  2. Pressed the Button in the Activity: FragmentA visible
  3. Pressed the Button in FragmentA: FragmentB visible and FragmentA onDestroyView()
  4. Pressed the Button in the Activity: 2nd instance FragmentA visible and FragmentB onDestroyView(). (This is the same as step 2 in the previous example except, FragmentB acts as A and the 2nd instance of FragmentA acts as B)
  5. Pressed the Button in the 2nd instance FragmentA: 2nd instance of FragmentB visible and 2nd instance of FragmentA onDestroyView().
  6. Pressed Back Button: 2nd instance of FragmentA visible and 2nd instance of FragmentB onDetach()
  7. Pressed Back Button: 1st instance of FragmentB visible and 2nd instance of FragmentA onDetach()
  8. Pressed Back Button: 1st instance of FragmentAvisible and 1st instance of FragmentB onDetach()
  9. Pressed Back Button: 1st instance of FragmentA onDetach()
  10. Pressed Back Button: which closed the application.

Observation

If you look into the report you can see, in step 1, each and every view lived until the app is closed. In step 2, the View of FragmentA i.e. FrameLayout and it's child, Button are allocated and got both cleared in step 3 which is expected. In step 3, the View of FragmentB i.e. FrameLayout and its child TextView is allocated but did not get cleared in step 4 hence, caused memory leak but cleared in step 7 when it's View is created again and allocated newly created View. On the other hand, the Views that are created in step 5 just got cleared in step 6 causing no memory leak, because the fragment was detached and they didn't prevent the fragment from being cleared up.

Conclusion

We observed that the leak from saving views in fragment lasts until the user returns back to the fragment. When the fragment is brought back i.e. onCreateView() is called, the leak is recovered. On the other hand, no leak happens when the fragment is on top and can only go back. Based on it, we can make the following conclusion,

  • When there is no forward transaction from a fragment, there is nothing wrong with saving views as a strong reference as they will be cleared in onDetach()
  • If there is a forward transaction we can store weak references of views so that they are cleared in onDestroyView()

P.S. If you don't understand heap dump, please watch Google I/O 2011: Memory management for Android Apps. Also, this link provides valuable information about memory leak.

I hope my answer helped you clear your confusion. Let me know if you still have confusion?

这篇关于在片段中保留对视图的引用会导致内存泄漏?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆