【问题标题】:How to prevent memory leaks when using dynamic screens?使用动态屏幕时如何防止内存泄漏?
【发布时间】:2019-08-31 15:32:19
【问题描述】:

我正在创建一个应用程序,该应用程序具有向用户呈现数据的屏幕。 每个Screen 都有自己的数据和自己的布局,所以它有一个方法可以返回一个int,它表示用来膨胀它的布局,然后这个View 被传递给一个函数来查找特定的视图并用数据填充它。

生命周期是这样的: 主讲人:

screen.getNextScreen ->
screen.getLayout -> 
view = inflateScreen ->
screen.populateScreen(view) ->
(wait for time elappsed or click) -> repeat

SettingsActivity 中也需要那些 Screens 来启用\禁用它们。

所以我创建了一个单例ScreenProvider,它初始化一次然后返回列表。

public class ScreenProvider {

    private List<Screen> screens;

    private static ScreenProvider instance = new ScreenProvider();

    public static ScreenProvider getInstance(){
        return instance;
    }

    private ScreenProvider() {
        screens = new ArrayList<>();

        screens.add(new Welcome());
        screens.add(new CompoundScreen());
        screens.add(new Times());
        screens.add(new Messages());
        screens.add(new Weekly());
    }

    public List<Screen> getScreenList() {
        return Lists.newArrayList(screens);
    }
}

它的接缝是,当应用程序运行时间过长时会崩溃或因内存泄漏而关闭,所以我添加了leakcanary,这是它的报告示例:

MainActivity has leaked:
D: * static ScreenProvider.!(instance)!
D: * ↳ ScreenProvider.!(screens)!
D: * ↳ ArrayList.!(array)!
D: * ↳ array Object[].!([0])!
D: * ↳ CompoundScreen.!(disposable)!
D: * ↳ LambdaObserver.!(onNext)!
D: * ↳ -$$Lambda$Screen$67KdQ1jl3VSjSvoRred5JqLGY5Q.!(f$1)!
D: * ↳ AppCompatTextView.mContext
D: * ↳ MainActivity

这只是一个例子,但几乎每个屏幕都有这样的泄漏。 LeakCanary 报告显示 TextView 有这个:D: | mAttachInfo = null 所以我认为这不是问题。 此外,每个Screen 都有一个onHide() 来清除一次性用品,当当前Screen 隐藏并在MainActivity.onStop() 中时调用它。

如何解决此泄漏? 我不应该在屏幕上使用单例吗? 如果没有,我如何从其他活动访问屏幕列表?

** 编辑 ** 添加一些 Screen 每个屏幕覆盖的主要方法。

public abstract int getLayout();

public boolean shouldShow()

public void populateData(View view)

public void onHide()

public abstract int getScreenIndex();

public boolean shouldCacheView()

public int getDuration()

【问题讨论】:

  • 您有什么理由在 MainActivity 中管理所有屏幕吗?这听起来一点都不好。如果你继续添加屏幕,它会如何结束?我会在每个屏幕上使用不同的片段和演示者,具有自己的生命周期、业务逻辑等。
  • 屏幕是用于提供视图的接口或包装器,还是视图本身? Welcome 和其他人的同样问题
  • @DavidMiguel 是一个 Activity,带有一个单独的 FrameLayout,它附加一个视图、删除它并附加另一个视图。视图在 MainPresenter 中膨胀。我认为当视图将被删除时,它将被垃圾收集,除了一些我标记为缓存的视图。 @FcoP。它是一个包装器。它有一个获取视图并填充其数据的方法。我将编辑添加 Screen 代码 smaple。
  • @DavidMiguel 我认为Fragment 有一个不必要的复杂生命周期。

标签: android memory-leaks leakcanary


【解决方案1】:

好的。从你所说的和你所展示的来看,你似乎将一些生成的视图的实例保留在单例中。别。每个 View 都需要通过代码或通过膨胀(基本上是 XML 支持的、基于反射的工厂方法)创建一个 Context,以访问应用程序和系统的资源,并保留对所述 Context 的引用,例如只要他们活着。在您的场景中,这意味着保留对您生成视图的活动的引用。通常,关于视图和活动,GC 会发生以下情况:

GC: Hey! Does anybody need this... MainActivity class?
View: I do! I do! I have a reference!
GC: Okay... and besides MainActivity, Does anybody else need this View class?
-Nobody answers-
GC: It does not matter my friend, you are being collected as well. Come with me.
And they both go.

在你的情况下:

GC: Hey! Does anybody need this... MainActivity class?
View: I do! I do! I have a reference! and MainActivity references me as well.
GC: Okay... and besides MainActivity, Does anybody else need this View class?
ScreenProvider: I do.
GC: Okay, keep moving View, and take MainActivity with you. Let me know when you folks are done so I can collect you.

And thus the leak.

为了将视图从一个活动传递到另一个活动,您需要删除对前一个活动的引用(mContext 字段)。由于没有用于执行此操作的 API,因此您需要使用反射。并且出现了另一个问题:每个 UI 片段都是 View 的子类。布局、小部件等。因此,要么保留对 XML 文件的每一部分的引用,以便通过反射删除上下文,要么遍历视图的子列表,删除上下文,然后继续直到有没有更多的子视图,在任何级别。之后,您必须以相同的方式设置对新活动的引用。这听起来像是一个巨大的黑客,因为它是,而且事情一定会在某种程度上打破。一个 Context 毕竟代表了你视图中存在的环境和状态。

对于您的情况,更好的解决方案是从单例中删除视图引用,并仅使用它来保留给定视图的状态/配置的表示。创建一个回调支持的方法(或类似方法),在后台膨胀视图并在返回所述视图之前执行必要的配置。如果您仍想保留 Activity 可能拥有的所有 Screens 的单一存储库,请将其作为成员添加到 Activity 类中,以便与 Activity 一起收集。

作为旁注,您的情况建议您应该使用单个活动,然后只需交换由“屏幕”组成的“主屏幕”,或者根据情况在屏幕之间切换。这会更有意义,风险也会更小。

最后,引用我自己的话:Remember the first rule of the android fight club

【讨论】:

  • 嗨,很抱歉回复晚了,办公室关门了。你能解释一下更好的解决方案吗?
猜你喜欢
  • 2021-10-22
  • 1970-01-01
  • 2019-10-17
  • 2010-12-20
  • 1970-01-01
  • 2010-09-21
  • 1970-01-01
  • 2015-02-05
相关资源
最近更新 更多