H5W3
当前位置:H5W3 > 其他技术问题 > 正文

MVVM-ViewModel解析

一 概述

ViewModel 在Activity或者Fragment生命周期内只有一个的存储数据。ViewModel 里面的数据不会因为屏幕的旋转或者其他配置(比如切换到多窗口模式)而丢失。但是在正常的finish()或者按返回键的时候,在Activity或者Fragment走到onDestroy的时候回清除ViewModel里面的数据,避免内存泄漏。虽然屏幕旋转Activity也会走onDestroy,但是会判断是否是因为屏幕旋转而导致的。所以ViewModel是一个很合格的存储数据的类

二 ViewModel生命周期

ViewModel 对象存在的时间比Activity的生命周期要长,禁止在ViewModel的持有Activity、Fragment、View等对象,这样会引起内存泄漏。如果需要在ViewModel中引用Context的话,google为我们提供了AndroidViewModel 类来供我们使用。


![](https://user-gold-cdn.xitu.io/2020/5/21/17237572af65339b?w=1018&h=1090&f=png&s=126007)

三 ViewModel的好处

  1. 不会因为屏幕旋转或者其他配置改变(比如切换到多窗口模式)而销毁,避免数据重新创建,比如网络数据重新加载的情况
  2. 当Activity或者Fragment的正常销毁的时候,自动清除数据,也就是绑定了Activity或者Fragment的生命周期,避免了内存泄漏,
  3. 多个Fragment之间或者Activity和Fragment之间更好地传递数据,进行交互
  4. 减少Activityu或者Fragment的压力,Activityu或者Fragment的只是显示数据的界面,帮助Activity和Fragment处理一些数据逻辑

3.1 由于官网的例子有点老,所以写一个共享的例子

Activity 中的两个或更多 Fragment 需要相互通信是一种很常见的情况。Activity和Fragment的之间互相调用,下面是一个Activity和两个Fragment之间的互相调用

具体的demo地址github

3.1.1 构建一个Avtivity

class ViewModelDemoActivity : AppCompatActivity() {
// 创建以Activity为维度的ViewModel
private val viewModel: DemoViewModel by lazy { ViewModelProvider(this).get(DemoViewModel::class.java) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var binding = DataBindingUtil.setContentView<ActivityViewmodelDemoBinding>(
this,
R.layout.activity_viewmodel_demo
)
viewModel.dataLive.observe(this, object : Observer<String> {
override fun onChanged(s: String?) {
// 当横竖屏变换时,会重新走这里,毕竟是View也都重新绘制了,只不过user里面立马有值
tv_name.text = s
}
})
tv_name.setOnClickListener{
// activity 里面的点击去改变值
viewModel.dataLive.value= "Activity触发的改变"
}
viewModel.getName()
}
companion object {
fun startMe(activity: Activity) {
activity.startActivity(Intent(activity, ViewModelDemoActivity::class.java))
}
}
}
 

3.1.2 ViewModel的代码

class DemoViewModel : ViewModel() {
val dataLive: MutableLiveData<String> =
MutableLiveData<String>()
fun getName(){
viewModelScope.launch {
delay(1000)
dataLive.value = "王者荣耀"
}
}
}
 

3.1.3 布局文件

两个Fragment是通过静态方式添加到Activity的

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#ED6E6E"
android:textSize="30sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<fragment
android:id="@+id/one"
android:name="com.nzy.mvvmsimple.viewmodel.ShareOneFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="@+id/guidline"
app:layout_constraintTop_toBottomOf="@+id/tv_name" />
<fragment
android:id="@+id/two"
app:layout_constraintTop_toBottomOf="@+id/tv_name"
android:name="com.nzy.mvvmsimple.viewmodel.ShareTwoFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="@+id/guidline"
app:layout_constraintRight_toRightOf="parent"
/>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guidline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
 

3.1.4 两个 Fragment

注意: 两个Fragment获取ViewModel的维度,要以Activity为维度

class ShareOneFragment : Fragment() {
// 创建以Activity为维度的ViewModel,注意 这里是 requireActivity() 不是 getActivity(),requireActivity()不可能为空,getActivity()有可能为空
private val viewModel: DemoViewModel
by lazy { ViewModelProvider(requireActivity()).get(DemoViewModel::class.java) }
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_share_one, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// 这里传入是 viewLifecycleOwner(getViewLifecycleOwner()),而不会Fragment本身
viewModel.dataLive.observe(viewLifecycleOwner, Observer {
tv_name_one.text = it
})
// 在 FragmentOne 中去改变值
tv_one.setOnClickListener {
viewModel.dataLive.value = "Fragment-One,改变的值"
}
}
companion object {
@JvmStatic
fun newInstance() =
ShareOneFragment()
}
}
 

四 ViewModel 和 onSaveInstanceState 区别

4.1 ViewModel

  1. 缓存在内存中的,相当于本地缓存和网络缓存读写比较快
  2. 可以存较大的值,比如bitmap、大对象等等
  3. 数据绑定了Activity或者Fragment的生命周期,当Activity正常关闭的时候,都会清除ViewModel中的数据.比如
    • 调用finish()
    • 按返回键退出,
    • 用户直接杀进程或者是放后台内存不足,被回收了
  4. 在Activity旋转或者其他配置改变的时候,ViewModel里面是还有值得,不会销毁,
  5. ViewModel也可以使用本地存储,通过SavedStateHandle实现的,使用SavedStateViewModelFactory这个工厂,这里就不多介绍了,已经是正式版本了,引用是implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0

4.2 onSaveInstanceState

  1. 仅适合可以序列化再反序列化的少量数据
  2. 不适合数量可能较大的数据,如用数组或Bitmap。可以存一些简单的基本类型和简单的小对象、例如字符串,和一些id
  3. 会把数据存储到磁盘上

注意 对于复杂的大型数据,请使用 数据库 或者 SharedPreferences

五 ViewModel源码解析

5.1 源码

ViewModel的源码很少,基本上就是一个map 里面用来存储关于协程和SavedStateHandleController(暂时知道这个),onCleared() 方法是供我们自己调用,就是当Activity或者Fragmengt的关闭的时候,需要清理一些资源,比如Rxjava的流

//来存储 一些东西,当Activity 关闭的时候 会清除这里的数据
private final Map<String, Object> mBagOfTags = new HashMap<>();
private volatile boolean mCleared = false;
//来存储 一些东西,当Activity 关闭的时候 自己实现去清楚一些数据,比如Rxjava中的流
@SuppressWarnings("WeakerAccess")
protected void onCleared() {
}
 

5.1.1 获得ViewModelProvider

private val viewModel: DemoViewModel by lazy { ViewModelProvider(this).get(DemoViewModel::class.java) }
 

看ViewModelProvicder的构造方法

//创建一个ViewModelProvider , 用来创建ViewModels的,并将其保留在给定ViewModelStoreOwner的存储区中,如果owner是 HasDefaultViewModelProviderFactory 的子类,就用HasDefaultViewModelProviderFactory的工厂,像Fragment 和 ComponentActivity 都是实现了HasDefaultViewModelProviderFactory的接口 
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
 

上面默认的方法是;
用来创建ViewModels的,并将其保留在给定ViewModelStoreOwner的存储区中,如果owner是 HasDefaultViewModelProviderFactory 的子类,就用HasDefaultViewModelProviderFactory的工厂,像Fragment 和 ComponentActivity(是FragmentActivity的爹,AppCompatActivity的爷爷) 都是实现了HasDefaultViewModelProviderFactory的接口,获取到的Frctory是 SavedStateViewModelFactory。 否则就用NewInstanceFactory去创建ViewModel,比如需要我们在ViewModel中传递参数的时候,我们可以写自己的Factory继承NewInstanceFactory,走我们自己方法Create()

5.1.1.1 ComponentActivity中获取Factory的方法

public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
if (mDefaultFactory == null) {
mDefaultFactory = new SavedStateViewModelFactory(
getApplication(),
this,
getIntent() != null ? getIntent().getExtras() : null);
}
return mDefaultFactory;
}
 

5.1.1.2 Fragment中获取Factory的方法

    public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
if (mFragmentManager == null) {
throw new IllegalStateException("Can't access ViewModels from detached fragment");
}
if (mDefaultFactory == null) {
mDefaultFactory = new SavedStateViewModelFactory(
requireActivity().getApplication(),
this,
getArguments());
}
return mDefaultFactory;
}
 

5.1.2 自己定义的Factory

使用

private val viewModel:UserViewModel by lazy { ViewModelProvider(this,UserViewModelFactory(repository)).get(UserViewModel::class.java) }
 

自己定义的Factory

class UserViewModelFactory(
private val repository: UserRepository
) : ViewModelProvider.NewInstanceFactory() {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
// 传入一个 Repository 参数
return UserViewModel(repository) as T
}
}
 

5.1.3 ViewModelStoreOwner: 存储ViewModel的拥有者

Fragment 和 Fragment 和 ComponentActivity(是FragmentActivity的爹,AppCompatActivity的爷爷)都实现了 ViewModelStoreOwner 这个接口,

public interface ViewModelStoreOwner {
// ViewModelStore 是用来存储 ViewModel的实例的
@NonNull
ViewModelStore getViewModelStore();
}
 

5.1.4 ViewModelStore 用来存储ViewModel的容器,,里面有个HashMap,用来存储ViewModel的,存储的key下面会讲到

public class ViewModelStore {
//  这个就是存储ViewModel的Hashmap
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
final ViewModel get(String key) {
return mMap.get(key);
}
Set<String> keys() {
return new HashSet<>(mMap.keySet());
}
// 清除viewmodel的里面的东西
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
 

5.1.5 获得ViewModel

 public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
// 获得 viewmodel这个类的全限定名
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
}
// 用一个DEFAULT_KEY常量 + 一个这个类的全限定名当做key
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
 

继续看get(String,Class) 方法

 public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
// 你可以认为mViewModelStore就是个map,上面也讲了,其实它里面就是一个map来存储ViewModel的
// 从map里面拿到 拿到这个 ViewModel
ViewModel viewModel = mViewModelStore.get(key);
// viewModel 这个对象是否可以转化成这个类,如果 viewModel 是null的话,这里也返回false
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
// mFactory 如果不是自己实现的话,那就是SavedStateViewModelFactory 实现于 KeyedFactory,所以第一次拿的时候 ,走到这里
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
viewModel = (mFactory).create(modelClass);
}
// 把获取到的ViewModel缓存到HashMap中
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
 

5.1.6 在什么时候清楚ViewModel

当 ComponentActivity 的构造方法中有以下代码,当Activity走到destroy的时候清楚ViewModel,里面有个isChangingConfigurations方法,表示 是否是配置改变引起的(比如 Activity屏幕旋转),如果是就不清除。

 getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
 

本文地址:H5W3 » MVVM-ViewModel解析

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址