带有 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 中,您通常会保存一些 Views 作为实例状态以加快访问速度。例如指向您的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!

除了保留对Views 的引用之外,您显然不应该保留对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 绑定的对象,例如 DrawableAdapter、视图或与上下文关联的任何其他对象。如果这样做,它将泄漏原始活动实例的所有视图和资源。 (资源泄漏意味着您的应用程序会保留它们,并且无法对它们进行垃圾回收,因此可能会丢失大量内存。)

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 2x System.gc() 为了安全?永远不要依赖gc()

以上是关于带有 UI 和内存泄漏的保留片段的主要内容,如果未能解决你的问题,请参考以下文章

避免android片段中内存泄漏的最佳方法是啥

片段 - 全局视图变量与本地和内部类侦听器和内存泄漏

为啥保留循环泄漏内存?

如何使用模块化代码片段中的LeakCanary检测内存泄漏?

使用导致内存泄漏的音频片段

在片段中保存活动实例:是否会导致内存泄漏?