场景
我有一个测验生成器,它生成了一系列不同类别的测验。序列的长度不受限制。
测验生成器有一个视图模型。每种类型的查询都有一个视图模型。测验生成器视图模型应根据测验的类创建测验的视图模型。
问题
视图模型不能包含对生命周期的引用,但我需要生命周期来创建视图模型。
ViewModelProviders.of(lifecycle).get(classForQuizzType);
问题
在哪里创建测验的子视图模型?
我能想到的一种解决方案是每次都从活动中注入子视图模型。这是一个弯路,尤其是在涉及嵌套视图的情况下。
另一种解决方案是在嵌套视图中创建视图模型,这看起来也很丑陋,因为从视图内部访问生命周期并不常见。
如果没有干净的解决方案,我的架构方法有什么问题?我应该在这种情况下使用片段吗?
我自己给出了第一个答案,灵感来自pskink
。也许我会在使用建议的方法的一些经验后更新答案。
如果视图模型应生成子对象(又名组件),则只要主对象绑定到生命周期并且组件是从主对象引用的,则组件本身就不需要绑定到生命周期。
对于给定的示例,这意味着创建主对象的好位置是活动的顶层位置,lifecycle
可以直接使用。quizz 对象从主对象引用。它们不需要直接访问生命周期,并且可以在任何地方创建,例如在主对象内部。这样就可以按需创建它们。
这些组件可能是也可能不是ViewModel
的子类。我认为扩展ViewModel
是一个很好的做法.此父类引入了onCleared
方法。这是从基础模型中删除观察者的位置。如果不这样做,您可能会创建内存泄漏。
您必须注意在正确的时刻调用onCleared
,至少从主对象的onCleared
方法调用。在这种特殊情况下,必须在生成新的测验之前清除每个以前的测验,以从基础测验模型中删除引用。
组件的视图模型可以简单地使用new
关键字创建。无需使用工厂或供应商。
如果没有干净的解决方案,我的方法有什么问题 建筑?我应该在这种情况下使用片段吗?
是的,片段是正确的选择
总结:
- 除了通过
LiveData
绑定Views
之外,没有真正的替代方案。 - 如果使用
LiveData
则需要LifeCycle
。 - 如果序列中子视图的生命周期应短于活动的生命周期,则片段是要走的路。
详
除了通过LiveData
绑定Views
之外,没有真正的替代方案。
视图模型不应保留对视图的未终止引用,否则只要视图模型存在,视图就存在,从而导致内存泄漏。有三种观察者模式来讨论视图如何观察视图模型。
a.)可变实时数据
它们需要一个生命周期。生命周期结束时,将自动清理引用。这是推荐的解决方案。
b.)弱引用
从理论上讲,这应该有效。当对视图的最后一个硬引用消失时,垃圾回收器应清理弱引用。在实践中,解决方案是不稳定的,引用有时会过早消失。
c.)手工观察者
手工制作的观察者必须调用 remove 方法。不幸的是,当视图消失时,没有定义的破坏钩子。没有位置可以在视图中调用 remove 方法。
因此,根据我的经验,a.) 是唯一可能的解决方案。
由于 LiveData 需要生命周期,因此片段是要走的路
此处提到的子视图是按顺序创建的。如果我们将它们绑定到活动,它们会堆积起来,直到活动消失,尽管它们只需要一小段时间
。片段可以在活动的子部分时间内存在。它们是将序列的子视图绑定到它们的正确解决方案。
示例代码
测验在这里被称为挑战。FragmentManger
始终是活动的,而LifecycleOwner
是活动或片段。
# A view model acceptor interface for views
public interface ViewModelAcceptor<T extends ViewModel> {
void plugViewModel(
T viewModel,
LifecycleOwner lifecycleOwner,
FragmentManager fragmentManager
);
}
# In the parent view class of the challenges new challenges are created
# in sequence
ChallengeFragment challengeFragment = new ChallengeFragment();
challengeFragment.setChallengeViewModel(challengeViewModel);
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(this.getId(), challengeFragment);
fragmentTransaction.commit();
# ChallengeFragment
public class ChallengeFragment extends Fragment {
private ChallengeViewModel challengeViewModel;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
return new ChallengeView(getActivity(), null);
}
public void setChallengeViewModel(ChallengeViewModel challengeViewModel) {
this.challengeViewModel = challengeViewModel;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
ChallengeView challengeView = (ChallengeView) getView();
Objects.requireNonNull(challengeView)
.plugViewModel(challengeViewModel, this, getFragmentManager());
}
}
# Challenge views are the child views of the sequence
public class ChallengeView extends ConstraintLayout implements ViewModelAcceptor<ChallengeViewModel> {
[...]
}