使用动态屏幕时如何防止内存泄漏?

时间:2019-04-10 08:30:42

标签: android memory-leaks leakcanary

我正在创建一个应用程序,该应用程序的屏幕可以向用户显示数据。 每个Screen都有自己的数据和布局,因此它有一种方法返回一个int来表示用于对其进行膨胀的布局,然后将此View传递给函数来查找特定的视图并向其中填充数据。

生命周期如下: MainPresenter:

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

Screens中还需要这些SettingsActivity来启用\禁用它们。

所以我创建了一个单例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()

1 个答案:

答案 0 :(得分:0)

好的。从您所说的内容和所显示的内容来看,似乎您正在将某些生成的视图的实例保留在Singleton中。别。每个视图都需要通过代码或通货膨胀(基本上是XML支持的,基于反射的工厂方法)来创建上下文,以访问应用程序和系统的资源,并保留对所述上下文的引用,例如只要他们活着。在您的方案中,这意味着保留对您在其中生成视图的活动的引用。通常,关于视图和活动,这是关于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文件的每个部分的引用,以便通过反射来删除上下文,要么遍历视图的子列表,删除上下文,并继续进行操作,直到出现不再有任何级别的子视图。之后,您将必须以相同的方式为新活动设置引用。这听起来像是一个巨大的hack,因为它一定会在某种程度上破坏一切。上下文毕竟代表了视图中存在的环境和状态。

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

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

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