带有 UI 和内存泄漏的保留片段
Posted
技术标签:
【中文标题】带有 UI 和内存泄漏的保留片段【英文标题】:Retained Fragments with UI and memory leaks 【发布时间】:2012-11-05 11:52:07 【问题描述】:我了解到,在呈现 UI 的片段上设置 .setOnRetainInstance(true)
可能会导致内存泄漏。
有人能解释一下为什么会发生这种情况吗?我在任何地方都没有找到详细的解释。
【问题讨论】:
只是为了记录主题,这里有类似的主题:***.com/q/11182180/693752 ***.com/q/11160412/693752 【参考方案1】:在带有 UI 的 Fragment
中,您通常会保存一些 View
s 作为实例状态以加快访问速度。例如指向您的EditText
的链接,这样您就不必一直使用findViewById
。
问题在于View
保留了对Activity
上下文的引用。现在,如果您保留 View
,您还保留了对该上下文的引用。
如果上下文仍然有效,但典型的保留情况是重新启动 Activity,那没问题。例如,经常用于屏幕旋转。活动重新创建将创建一个新的上下文,而旧的上下文将被垃圾收集。但它现在不能被垃圾收集,因为你的 Fragment
仍然引用旧的。
下面的例子展示了如何不这样做
public class LeakyFragment extends Fragment
private View mLeak; // retained
@Override
public void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setRetainInstance(true);
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
mLeak = inflater.inflate(R.layout.whatever, container, false);
return mLeak;
@Override
public void onDestroyView()
super.onDestroyView();
// not cleaning up.
要解决这个问题,您需要清除 onDestroyView
中对您的 UI 的所有引用。一旦Fragment
实例被重新使用,您将被要求在onCreateView
上创建一个新的用户界面。在onDestroyView
之后保留 UI 也没有意义。 Ui 不会被使用。
此示例中的修复只是将onDestroyView
更改为
@Override
public void onDestroyView()
super.onDestroyView();
mLeak = null; // now cleaning up!
除了保留对View
s 的引用之外,您显然不应该保留对Activity
的引用(例如来自onAttach
- 在onDetach
上清除)或任何Context
(除非它是Application
上下文) .
【讨论】:
有任何想法,如何处理这可能导致的大量空指针?我有几个动画侦听器、线程,每个都使用其中一个引用,这些引用在 onDestroyView 中被取消。因此,每当我使用其中一个引用时,我首先必须检查是否为空。这很不方便。 @Tamas Listeners, Threads, ... 只要不保留对Activity
的任何引用或引用,都可以继续引用。如果他们引用了类似的内容并且使用它重新创建了 Activity 将不会产生任何有效的结果,因此您无论如何都必须对其进行更新。
@Tamas 示例:pastebin.com/8A18kMym 您基本上需要将onAttach
/ onDetach
传播到任何引用上下文的事物,并将onCreateView
/ onDestroyView
传播到任何保持对视图的引用的事物.
在第一个示例中,我只是想知道不清理 onDestroyView 的视图有多糟糕。我猜有两种情况:onConfigChange,因此调用了 onDestroy,然后调用了 onCreateView(它将重置 mLeak 变量),当活动完成时,片段被分离并销毁。但我确实明白你的意思,最好不要保留任何观点
@zapl 当 onCreateView 将在几毫秒内再次调用并用新对象重写这些引用时,手动清理 onCreateView 在 onDestroyView 中实例化的变量有什么意义,所以旧对象将没有参考和将是GC?【参考方案2】:
setRetainInstance(true)
用于在 Activity 重新创建期间保留动态片段的实例,例如屏幕旋转或其他配置更改。但这并不意味着 Fragment 将被系统永远保留。
当 Activity 因其他原因终止时,例如用户完成 Activity(即按回),Fragment 应该有资格进行垃圾回收。
【讨论】:
【参考方案3】:在保留与 Activity 耦合的某些对象时要小心。
警告:虽然您可以返回任何对象,但绝不应传递与 Activity 绑定的对象,例如 Drawable、Adapter、视图或与上下文关联的任何其他对象。如果这样做,它将泄漏原始活动实例的所有视图和资源。 (资源泄漏意味着您的应用程序会保留它们,并且无法对它们进行垃圾回收,因此可能会丢失大量内存。)
http://developer.android.com/guide/topics/resources/runtime-changes.html#RetainingAnObject
【讨论】:
【参考方案4】:“setRetainInstance”用于在重新创建活动时维护片段的状态。 根据官方文档:如果我们使用“setRetainInstance”,片段生命周期的2个方法(onCreate,onDestroy)将不会被执行。 但是,片段中包含的视图将被重新创建,这是因为生命周期将从“onCreateView”执行。 在这些情况下,如果我们在“onSaveInstanceState”中保存了一些数据,我们应该在“onActivityCreated”而不是“onCreate”中请求它。
官方信息:https://developer.android.com/reference/android/app/Fragment.html#setRetainInstance(boolean)
更多信息:https://inthecheesefactory.com/blog/fragment-state-saving-best-practices/en
【讨论】:
【参考方案5】:您可以覆盖 onDestroy()
并调用垃圾收集器。
@Override
public void onDestroy()
super.onDestroy();
System.gc();
System.gc();
【讨论】:
20% 的机会它会工作:P 2xSystem.gc()
为了安全?永远不要依赖gc()
以上是关于带有 UI 和内存泄漏的保留片段的主要内容,如果未能解决你的问题,请参考以下文章