关于android.arch.lifecycle.ViewModel
类。
ViewModel
的范围是与其相关的UI组件的生命周期,因此在基于Fragment
的应用程序中,这将是片段的生命周期。这是一件好事。
在某些情况下,您想在多个片段之间共享一个ViewModel
实例。我特别感兴趣的是许多屏幕与相同的基础数据相关的情况。
(当多个相关片段显示在同一屏幕上但this can be worked around by using a single host fragment as per answer below时,文档建议采用类似的方法。)
这在official ViewModel documentation中进行了讨论:
ViewModels也可以用作不同层之间的通信层 活动的片段。每个片段都可以获取ViewModel 通过他们的活动使用相同的键。这样可以进行交流 在片段之间以解耦的方式,这样它们就永远不需要 直接与另一个Fragment通话。
换句话说,要在代表不同屏幕的片段之间共享信息,ViewModel
的作用域应为Activity
生命周期(根据Android文档,这也可以在其他共享实例中使用)
现在,在新的Jetpack导航模式中,建议使用“一个活动/很多片段”架构。这意味着该活动在应用程序的整个使用过程中一直存在。
即范围为ViewModel
生命周期的任何共享Activity
实例都不会被清除-内存将保持不变。
为了保留内存并在任何时间点使用最少的内存,最好能在不再需要时清除共享的ViewModel
实例。
如何从ViewModel
或持有人片段中手动清除ViewModelStore
?
答案 0 :(得分:14)
无需使用Navigation Component
库的快速解决方案:
getActivity().getViewModelStore().clear();
这将解决此问题,而无需合并Navigation Component
库。这也是简单的一行代码。它将清除通过ViewModels
在Fragments
之间共享的Activity
答案 1 :(得分:5)
正如OP和Archie所说,Google使我们能够将ViewModel的作用域范围扩展到导航图。如果您已经在使用导航组件,我将在此处添加操作方法。
您可以在导航图和right-click->move to nested graph->new graph
中选择所有需要组合在一起的片段
现在,这会将选定的片段移动到主导航图内的嵌套图,如下所示:
<navigation app:startDestination="@id/homeFragment" ...>
<fragment android:id="@+id/homeFragment" .../>
<fragment android:id="@+id/productListFragment" .../>
<fragment android:id="@+id/productFragment" .../>
<fragment android:id="@+id/bargainFragment" .../>
<navigation
android:id="@+id/checkout_graph"
app:startDestination="@id/cartFragment">
<fragment android:id="@+id/orderSummaryFragment".../>
<fragment android:id="@+id/addressFragment" .../>
<fragment android:id="@+id/paymentFragment" .../>
<fragment android:id="@+id/cartFragment" .../>
</navigation>
</navigation>
现在,在初始化视图模型时在片段内部执行此操作
val viewModel: CheckoutViewModel by navGraphViewModels(R.id.checkout_graph)
如果您需要通过视图模型工厂(可能是用于注入视图模型),则可以这样做:
val viewModel: CheckoutViewModel by navGraphViewModels(R.id.checkout_graph) { viewModelFactory }
请确保其R.id.checkout_graph
而不是R.navigation.checkout_graph
出于某种原因,创建导航图并使用include
将其嵌套在主导航图内对我不起作用。可能是一个错误。
感谢OP和@Archie向我指出正确的方向。
答案 2 :(得分:4)
如果您检查代码here,就会发现,您可以从ViewModelStore
和ViewModelStoreOwner
,例如Fragment
中获得FragmentActivity
实现该接口。
从那里开始,您可以致电viewModelStore.clear()
,正如文档所述:
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
N.B。::这将清除特定LifeCycleOwner的所有可用ViewModel,这不允许您清除一个特定的ViewModel。
答案 3 :(得分:2)
如果您不希望将ViewModel
的范围划分为Activity
的生命周期,则可以将其划分为父片段的生命周期。因此,如果您要与一个屏幕中的多个片段共享ViewModel
的实例,则可以对这些片段进行布局,以使它们都共享一个公共的父片段。这样,在实例化ViewModel
时,您可以执行以下操作:
CommonViewModel viewModel = ViewModelProviders.of(getParentFragment()).class(CommonViewModel.class);
希望这会有所帮助!
答案 4 :(得分:1)
我认为我有一个更好的解决方案。
如@Nagy Robi所述,您可以通过调用ViewModel
来清除viewModelStore.clear()
。这样做的问题是,它将清除此ViewModelStore
内的所有视图模型。换句话说,您将无法控制要清除的ViewModel
。
但是根据@mikehc here。实际上,我们实际上可以创建自己的ViewModelStore
。这将使我们能够精确控制ViewModel必须存在的范围。
注意:我没有看到任何人这样做,但是我希望这是有效的。这将是在单一活动应用程序中控制范围的一种非常好的方法。
请对此方法提供一些反馈。一切都会感激的。
答案 5 :(得分:1)
我只是在编写库来解决此问题:scoped-vm,请随时进行检查,我们将非常感谢您提供任何反馈。 在后台,它使用上述方法@Archie-每个范围维护一个单独的ViewModelStore。但是它又向前迈进了一步,一旦从该范围请求视图模型的最后一个片段销毁了,它就会清除ViewModelStore本身。
我应该说,当前的整个视图模型管理(尤其是这个lib)都受到serious bug和backstack的影响,希望它将得到修复。
摘要:
ViewModel.onCleared()
不被调用,最好的方法(目前)是自己清除它。由于该错误,您无法保证将清除fragment
的视图模型。 ViewModel
-不用担心,它们将像其他任何未引用的对象一样被垃圾回收。如果需要,请随意使用我的lib进行细粒度的范围设置。答案 6 :(得分:1)
正如已经指出的那样,无法使用体系结构组件API清除ViewModelStore的单个ViewModel。解决此问题的一种可能方法是拥有一个每个ViewModel的存储,可以在必要时安全地清除它们:
class MainActivity : AppCompatActivity() {
val individualModelStores = HashMap<KClass<out ViewModel>, ViewModelStore>()
inline fun <reified VIEWMODEL : ViewModel> getSharedViewModel(): VIEWMODEL {
val factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
//Put your existing ViewModel instantiation code here,
//e.g., dependency injection or a factory you're using
//For the simplicity of example let's assume
//that your ViewModel doesn't take any arguments
return modelClass.newInstance()
}
}
val viewModelStore = this@MainActivity.getIndividualViewModelStore<VIEWMODEL>()
return ViewModelProvider(this.getIndividualViewModelStore<VIEWMODEL>(), factory).get(VIEWMODEL::class.java)
}
val viewModelStore = this@MainActivity.getIndividualViewModelStore<VIEWMODEL>()
return ViewModelProvider(this.getIndividualViewModelStore<VIEWMODEL>(), factory).get(VIEWMODEL::class.java)
}
inline fun <reified VIEWMODEL : ViewModel> getIndividualViewModelStore(): ViewModelStore {
val viewModelKey = VIEWMODEL::class
var viewModelStore = individualModelStores[viewModelKey]
return if (viewModelStore != null) {
viewModelStore
} else {
viewModelStore = ViewModelStore()
individualModelStores[viewModelKey] = viewModelStore
return viewModelStore
}
}
inline fun <reified VIEWMODEL : ViewModel> clearIndividualViewModelStore() {
val viewModelKey = VIEWMODEL::class
individualModelStores[viewModelKey]?.clear()
individualModelStores.remove(viewModelKey)
}
}
使用getSharedViewModel()
获取绑定到Activity生命周期的ViewModel实例:
val yourViewModel : YourViewModel = (requireActivity() as MainActivity).getSharedViewModel(/*There could be some arguments in case of a more complex ViewModelProvider.Factory implementation*/)
稍后,当需要处置共享ViewModel时,请使用clearIndividualViewModelStore<>()
:
(requireActivity() as MainActivity).clearIndividualViewModelStore<YourViewModel>()
在某些情况下,如果不再需要ViewModel,则可能希望尽快清除它(例如,如果其中包含一些敏感的用户数据,如用户名或密码)。这是一种在每次切换片段时记录individualModelStores
状态的方法,以帮助您跟踪共享的ViewModel:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (BuildConfig.DEBUG) {
navController.addOnDestinationChangedListener { _, _, _ ->
if (individualModelStores.isNotEmpty()) {
val tag = this@MainActivity.javaClass.simpleName
Log.w(
tag,
"Don't forget to clear the shared ViewModelStores if they are not needed anymore."
)
Log.w(
tag,
"Currently there are ${individualModelStores.keys.size} ViewModelStores bound to ${this@MainActivity.javaClass.simpleName}:"
)
for ((index, viewModelClass) in individualModelStores.keys.withIndex()) {
Log.w(
tag,
"${index + 1}) $viewModelClass\n"
)
}
}
}
}
}
答案 7 :(得分:1)
就我而言,我观察到的大多数事物都与View
有关,因此,在View
被销毁(但{{1 }}。
如果我需要像Fragment
这样的东西将我带到另一个LiveData
(或者只做一次),我会创建一个“消费观察者”。
可以通过扩展Fragment
来完成:
MutableLiveData<T>
,一旦观察到,它将从fun <T> MutableLiveData<T>.observeConsuming(viewLifecycleOwner: LifecycleOwner, function: (T) -> Unit) {
observe(viewLifecycleOwner, Observer<T> {
function(it ?: return@Observer)
value = null
})
}
中清除。
现在您可以这样称呼:
LiveData
答案 8 :(得分:0)
我找到了一种解决此问题的简单而优雅的方法。诀窍是使用DummyViewModel和模型密钥。
该代码有效,因为AndroidX在get()上检查了模型的类类型。如果不匹配,则使用当前的ViewModelProvider.Factory创建一个新的ViewModel。
public class MyActivity extends AppCompatActivity {
private static final String KEY_MY_MODEL = "model";
void clearMyViewModel() {
new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).
.get(KEY_MY_MODEL, DummyViewModel.class);
}
MyViewModel getMyViewModel() {
return new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication()).
.get(KEY_MY_MODEL, MyViewModel.class);
}
static class DummyViewModel extends ViewModel {
//Intentionally blank
}
}
答案 9 :(得分:0)
据我所知,您无法通过程序手动删除ViewModel对象,但是可以清除存储在其中的数据,在这种情况下,您应该手动调用Oncleared()
方法
为此:
Oncleared()
类扩展的该类中的ViewModel
方法答案 10 :(得分:0)
似乎在最新的体系结构组件版本中已经解决了该问题。
ViewModelProvider 具有以下构造函数:
/**
* Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
* {@code Factory} and retain them in a store of the given {@code ViewModelStoreOwner}.
*
* @param owner a {@code ViewModelStoreOwner} whose {@link ViewModelStore} will be used to
* retain {@code ViewModels}
* @param factory a {@code Factory} which will be used to instantiate
* new {@code ViewModels}
*/
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
在使用Fragment的情况下,将使用范围限定的ViewModelStore。
androidx.fragment.app.Fragment#getViewModelStore
/**
* Returns the {@link ViewModelStore} associated with this Fragment
* <p>
* Overriding this method is no longer supported and this method will be made
* <code>final</code> in a future version of Fragment.
*
* @return a {@code ViewModelStore}
* @throws IllegalStateException if called before the Fragment is attached i.e., before
* onAttach().
*/
@NonNull
@Override
public ViewModelStore getViewModelStore() {
if (mFragmentManager == null) {
throw new IllegalStateException("Can't access ViewModels from detached fragment");
}
return mFragmentManager.getViewModelStore(this);
}
androidx.fragment.app.FragmentManagerViewModel#getViewModelStore
@NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
if (viewModelStore == null) {
viewModelStore = new ViewModelStore();
mViewModelStores.put(f.mWho, viewModelStore);
}
return viewModelStore;
}
答案 11 :(得分:0)
如果您共享了 ViewModel 并希望将它们全部清除并且这些都基于单个活动,那么添加以下代码行即可。
getActivity().getViewModelStore().clear();
答案 12 :(得分:-2)
通常,您不会手动清除ViewModel,因为它是自动处理的。如果您需要手动清除ViewModel,则可能是在该ViewModel中做的太多了。
使用多个视图模型没有错。第一个可以限定为Activity,而另一个可以限定为片段。
尝试仅将活动范围的Viewmodel用于需要共享的内容。并在Fragment Scoped Viewmodel中放入尽可能多的内容。片段范围内的视图模型在片段被销毁时将被清除。减少整体内存占用。