很早前写过一篇MVVM架构的文章,当时写的很粗糙,一直想抽空补全一下,自己对MVVM的理解,写一篇让新手都能够容易掌握的文章。
众所周知,Google已经开始倾向MVI架构,但是作为一个开发者,在开发中,只有存在合适的架构,所以关于MVI架构,希望以后有真正的了解再来写一写自己的见解。
简单的说一下
ViewModel:关联层,将Model和View进行绑定,只做和业务逻辑相关的工作,不涉及任何和UI相关的操作,不持有控件的引用,不更新UI。
View:视图层,只做和UI相关的工作,不涉及任何业务逻辑,不涉及操作数据,不处理数据。UI和数据严格的分开。
Model:模型层,保存数据的状态,比如数据存储、网络请求。同时还与View存在一定的耦合,可以通过观察
启用方式
//添加ViewModel的引用
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.2.0'
//启用DataBinding
buildFeatures {
dataBinding true
}
class MyViewModel: VideModel{
}
viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
// 这里需要提到一下在旧版本的APIVIewModelProviders已经被添加了Deprecated标签,也就是已经不推荐使用了
viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
ViewModel的优势在于生命周期和数据的持久化,这就是为什么我们需要在activity/fragment去使用它。其次就是异步回调,不会造成内存泄漏问题,对View层和Model层的完全解耦,这就是ViewModel在整个MVVM架构中的重要性了。
Tip:当ViewModel一定需要context引用,有没有什么办法呢? 其实是有的, ViewModel抽象类有一个实现类AndroidViewModel(Application),它接收的对象是application。
这是一个登录界面使用ViewModel的例子
仔细看会发现,实战当中,其实就是在mainViewModel定义了两个属性,用来缓存account 和 pwd,但是这两个数据却能持久化在这里面。为什么呢?因为当你旋转屏幕的时候,你会发现输入框的值依旧存在。我们知道在做横竖屏切换的时候,activity会被重新创建,此时如果我们数据是放在activity当中,数据就已经被销毁了。
LiveData数据变化感知,也就是说当我们对一个对象多次赋值的时候,可以通过LiveData去操作,监听对象改变,然后处理相应的业务逻辑。
ViewModel.kt
public MutableLiveData<String> account = new MutableLiveData<>();
public MutableLiveData<String> pwd = new MutableLiveData<>();
account.setValue("admin")
pwd.setValue("123456")
这里使用的是MutableLiveData,表示值的内容可变动,而LiveData是不可变的。<>中的是泛型,你可以直接将一个对象放进去,当对象的内容有改动时,通知改变就可以了。
MainActivity.kt
viewModel.account.observe(this, {
tvAccount.text = it
})
viewModel.pwd.observe(this, {
tvPwd.text = it
})
我们可以看到,在viewModel中通过对MutableLiveData对象调用它的setValue,在Activity中,就可以通过MutableLiveData的observe监听到数据的变化,从而处理相应的逻辑。(例子代码中是通过TextView进行显示)
在上述例子中,可以看到我们使用的是setValue()的方式,还有一种方式是postValue(),需要注意一点,setValue()的方式只允许在主线程中使用,而postValue可以在任何线程中使用,并且如果在主线程执行发布之前,多次调用此方法,则只会分派最后一个值。
Android的DataBinding已经内置了,因此只需要在app模块的build.gradle中开启就可以使用了。
dataBinding {
enabled = true
}
Databinding顾名思义就是数据绑定,接下来会通过几个例子讲一下不同的绑定方式。
布局文件,当我们启动了DataBinding,在定义布局的根节点,通过AS的提示快捷按键,就可以显示添加data binding。
activity_main.xml
<?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"
xmlns:tools="http://schemas.android.com/tools">
<!--绑定数据-->
<data>
<variable
name="user"
type="com.example.User" />
</data>
<LinearLayout>
<TextView
android:id="@+id/tv_account"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.account}" />
<TextView
android:id="@+id/tv_pwd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.pwd}" />
</LinearLayout>
</layout>
最好进入Activity,在onCreate方法中,添加绑定
override fun onCreate(Bundle saveInstanceState) {
val dataBinding = DataBindingUtil.setContentView(this, R.layou.activity_main)
user = User("admin", "123456")
dataBinding.setUser(user)
/**
我们可以通过手动赋值,比如外界输入等方式,给user赋值,在界面上就能及时显示修改的数据
user.setAccount("study")
user.setPwd("666")
**/
}
DataBinding的双向绑定其主要的差别就是在布局文件中使用中,用@={viewModel.user.account}将数据直接赋值给数据源
<!--绑定数据-->
<data>
<variable
name="viewModel"
type="com.example.viewmodels.MainViewModel" />
</data>
<TextView
android:id="@+id/tv_account"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.user.account}" />
<TextView
android:id="@+id/tv_pwd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.user.pwd}"/>
<EditText
android:id="@+id/et_account"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:text="@={viewModel.user.account}"
android:hint="账号" />
<EditText
android:id="@+id/et_pwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:text="@={viewModel.user.pwd}"
android:hint="密码"
android:inputType="textPassword" />
这里需要注意几点:
第一是数据源,这里绑定的是viewModel,也就是MainViewModel中的数据都可以拿到。
第二就是响应的地方,通过这种方式去显示ViewModel中对象的变量数据在空间上,也就是两个TextView。
第三个地方,也就是双向绑定的意义,就是UI改变数据源。我们知道当输入框输入数据时,text属性值会改变为输入的数据,而@={viewModel.user.account}就是将输入的数据直接赋值给数据源。这样我们就不需要在Activity中再去处理EditText的内容了,减少了耦合。
而实际开发过程中,我们需要用到双向绑定的地方还是比较少的,相对于单项绑定。
我们知道ViewModel能在Activity和Fragment里使用,因此也能作为媒介使得Activity和Fragment进行交互。那么需要在View里使用呢?
假如我有一个自定义view或者dialog,它包含一堆数据和状态,能否使用ViewModel去管理数据呢?
先来看看ViewModel的源码,为什么说到ViewModel创建不建议通过new,而是ViewModelProvider的方式。
// ViewModelProvider.java
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
// 注释 6, 通过ViewModelStore 拥有者的 getDefaultViewModelProviderFactory方法获取工厂对象
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: ViewModelProvider.NewInstanceFactory.getInstance());
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull ViewModelProvider.Factory factory) {
//注释7, 保存工厂 Factory和 缓存对象,ViewModelStore
mFactory = factory;
mViewModelStore = store;
}
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
String canonicalName = modelClass.getCanonicalName();,
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
//注释 9, 从缓存 mViewModelStore中取 ViewModel的对象
// Key值是 “androidx.lifecycle.ViewModelProvider.DefaultKey” + 类名
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
......
// 是响应类的对象则直接返回
return (T) viewModel;
}
......
......
//注释 10, 缓存中没有 ViewModel对象,用工厂创建,并存入缓存
viewModel = (mFactory).create(modelClass);
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
从源码可以看出,ViewModel对象是通过工厂模式进行创建的,并且保存了Factory和缓存对象,当我们通过ViewModelProvider(this).get()方法获取ViewModel对象时,会优先通过缓存获取。
在看看注释6处的getDefaultViewModelProviderFactory()方法
// ComponentActivity.java
public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
......
if (mDefaultFactory == null) {
mDefaultFactory = new SavedStateViewModelFactory(
//注释 11, 获取了 Application对象,创建SavedStateViewModelFactory对象
getApplication(),
this,
getIntent() != null ? getIntent().getExtras() : null);
}
return mDefaultFactory;
}
// SavedStateViewModelFactory.java
public SavedStateViewModelFactory(@NonNull Application application,
@NonNull SavedStateRegistryOwner owner,
@Nullable Bundle defaultArgs) {
mSavedStateRegistry = owner.getSavedStateRegistry();
mLifecycle = owner.getLifecycle();
mDefaultArgs = defaultArgs;
mApplication = application;
//注释 12, 创建 AndroidViewModelFactory工厂
mFactory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
......
// 注释 13,通过 AndroidViewModelFactory创建 ViewModel对象
return mFactory.create(modelClass);
......
}
public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {
private static ViewModelProvider.AndroidViewModelFactory sInstance;
@NonNull
public static ViewModelProvider.AndroidViewModelFactory getInstance(@NonNull Application application) {
// 注释14, 单例模式创建工厂实例
if (sInstance == null) {
sInstance = new ViewModelProvider.AndroidViewModelFactory(application);
}
return sInstance;
}
private Application mApplication;
// 传入 Application对象
public AndroidViewModelFactory(@NonNull Application application) {
mApplication = application;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
try {
//注释 15, 工厂方法设计模式创建 ViewModel对象,持有 Application的引用
return modelClass.getConstructor(Application.class).newInstance(mApplication);
}
......
}
return super.create(modelClass);
}
}
能看出AndroidViewModelFactory这个工厂,用单例模式创建了该工厂对象,然后使用工厂方法设计模式创建了ViewModel对象,ViewModel创建时传入的时Application参数,这里能够知道ViewModel对象最终持有的,并不是Activity的引用,而是Application的引用,也就是说,为什么ViewModel不容易造成内存泄漏的问题了。
到了这里,为什么不推荐使用new 去创建ViewModel也就迎刃而解了。
自定义view中,如果需要使用ViewModel,虽然官方不建议使用new ViewModel的方式,但是我们还是可以取巧,通过这种方式在view当中去使用它。- -!