我正在创建一个应用程序,该应用程序的屏幕可以向用户显示数据。
每个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()
答案 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,因为它一定会在某种程度上破坏一切。上下文毕竟代表了视图中存在的环境和状态。
针对您情况的更好解决方案是从单例中删除视图引用,并仅使用它保留给定视图的状态/配置的表示形式。创建一个回调支持的方法(或类似方法),该方法在后台扩大视图并在返回该视图之前执行必要的配置。如果您仍想保留活动可能拥有的所有屏幕的单个存储库,请将其作为成员添加到活动类中,以便与活动一起收集。
作为旁注,您的情况建议您应该使用单个活动,然后仅交换由“屏幕”组成的“主屏幕”,或者根据情况在屏幕之间进行切换。这样会更有意义,并且风险也会更低。