Android之MVVM架构之ViewModel + LiveData + DataBinding
Posted mr_zengkun
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android之MVVM架构之ViewModel + LiveData + DataBinding相关的知识,希望对你有一定的参考价值。
前言
很早前写过一篇MVVM架构的文章,当时写的很粗糙,一直想抽空补全一下,自己对MVVM的理解,写一篇让新手都能够容易掌握的文章。
众所周知,Google已经开始倾向MVI架构,但是作为一个开发者,在开发中,只有存在合适的架构,所以关于MVI架构,希望以后有真正的了解再来写一写自己的见解。
基本使用
刚学习MVVM,这张架构图是非常重要的
-
简单的说一下
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
- 自定义的VM类只要继承引用的第三方库中的ViewModel抽象类即可
class MyViewModel: VideModel
- 然后我们就可以在activity/fragment中实例化它
viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
// 这里需要提到一下在旧版本的APIVIewModelProviders已经被添加了Deprecated标签,也就是已经不推荐使用了
viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
ViewModel的优势在于生命周期和数据的持久化,这就是为什么我们需要在activity/fragment去使用它。其次就是异步回调,不会造成内存泄漏问题,对View层和Model层的完全解耦,这就是ViewModel在整个MVVM架构中的重要性了。
ViewModel生命周期
Google官方给的生命周期图
- 生命周期长于Activity。不难发现,ViewModel的生命周期,贯穿整个被绑定的Activity的生命周期,也就是说,既然Activity因为系统配置变更销毁重建,比如旋转屏幕等,ViewModel对象依旧会被保留,并关联到新的Activity。只有当Activity正常销毁时,ViewModel对象才会调用onCleared()进行清除。
- 开头说过,众多开发模式当中,他们的区别大同小异,都是为了实现解耦。而我们知道,在MVP的Presenter中,需要持有View的接口对象,来回调结果给界面。而ViewModel是不需要持有UI层的引用,由于ViewModel贯穿了Activity的整个完整的生命周期,甚至比Activity生命周期要长,这就导致,如果让ViewModel持有Activity的引用时,容易造成内存泄漏等问题。
Tip:当ViewModel一定需要context引用,有没有什么办法呢? 其实是有的, ViewModel抽象类有一个实现类AndroidViewModel(Application),它接收的对象是application。
这是一个登录界面使用ViewModel的例子
仔细看会发现,实战当中,其实就是在mainViewModel定义了两个属性,用来缓存account 和 pwd,但是这两个数据却能持久化在这里面。为什么呢?因为当你旋转屏幕的时候,你会发现输入框的值依旧存在。我们知道在做横竖屏切换的时候,activity会被重新创建,此时如果我们数据是放在activity当中,数据就已经被销毁了。
LiveData
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可以在任何线程中使用,并且如果在主线程执行发布之前,多次调用此方法,则只会分派最后一个值。
DataBinding
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的内容了,减少了耦合。
而实际开发过程中,我们需要用到双向绑定的地方还是比较少的,相对于单项绑定。
如何在dialog或者自定义View中使用
我们知道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当中去使用它。- -!
Android架构组件之ViewModel和LiveData
关于应用架构,Google官方现在主推MVVM架构,官方推出的JetPack库提供了一系类支持MVVM架构,其中最核心的两个类是ViewModel和LiveData。
在MVVM架构中,View通常指Activity和Fragment,主要用来根据数据渲染UI,而Model主要负责数据的获取,这里通常包含获取网络数据和本地缓存数据,而ViewModel作为View和Model的桥梁,主要负责UI数据的处理,官方提供了ViewModel类作为一种实现。LiveData作为一种可观察的数据存储类,可以很好在支持ViewModel处理好的数据通知UI的更新。
ViewModel
主要负责存储和管理和界面相关的数据。很多地方都提到ViewModel感知生命周期,查看源码可以得知并不是这样,它只不过在config变化导致Activity销毁重建期间不会销毁。
ViewModel对于开发者来说,就是Activity或Fragment的一个成员变量,它的初始化代码通常是这样的:
viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
而它的底层实现过程是这样的,FragmentActivity或Fragment有一个成员变量mViewModelStore用来存储ViewModel,ViewModelProviders只是个包装器,它会从当前activity或fragment的ViewModelStore中取ViewModel,如果没有的话就创建一个并存储到ViewModelStore中。至于文档中提到的config变化不会销毁,是因为config变化引起的销毁重建,FragmentActivity会缓存ViewModelStore,如下代码所示,在onRetainNonConfigurationInstance函数中保存了ViewModelStore。但是正常的内存不足页面销毁重建时,ViewModel是会重新创建的,我们可以看到onSaveInstanceState中没有对ViewModelStore做缓存的,并且在onDestroy中进行了销毁。
protected void onCreate(@Nullable Bundle savedInstanceState)
this.mFragments.attachHost((Fragment)null);
super.onCreate(savedInstanceState);
FragmentActivity.NonConfigurationInstances nc = (FragmentActivity.NonConfigurationInstances)this.getLastNonConfigurationInstance();
if (nc != null && nc.viewModelStore != null && this.mViewModelStore == null)
this.mViewModelStore = nc.viewModelStore;
......
@NonNull
public ViewModelStore getViewModelStore()
if (this.getApplication() == null)
throw new IllegalStateException("Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.");
else
if (this.mViewModelStore == null)
FragmentActivity.NonConfigurationInstances nc = (FragmentActivity.NonConfigurationInstances)this.getLastNonConfigurationInstance();
if (nc != null)
this.mViewModelStore = nc.viewModelStore;
if (this.mViewModelStore == null)
this.mViewModelStore = new ViewModelStore();
return this.mViewModelStore;
//config变化时保存数据
public final Object onRetainNonConfigurationInstance()
Object custom = this.onRetainCustomNonConfigurationInstance();
FragmentManagerNonConfig fragments = this.mFragments.retainNestedNonConfig();
if (fragments == null && this.mViewModelStore == null && custom == null)
return null;
else
FragmentActivity.NonConfigurationInstances nci = new FragmentActivity.NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = this.mViewModelStore;
nci.fragments = fragments;
return nci;
LiveData
1、简介
看官方文档我们知道LiveData是一种可观察的数据存储类,它具有感知生命周期的能力。通常ViewModel是靠LiveData通知View层数据变化的,Activity、Fragment和Service监听LiveData,当数据变化时会收到通知做相应的UI更新工作。上面说了LiveData有生命周期感知能力,所以当Activity处于非活跃状态时,是不会收到通知的,当Activity再次处于活跃状态时,会重新收到数据变化的通知。我们可以看下典型的使用的方式:
// void observe(LifecycleOwner owner, Observer<? super T> observer)
mViewModel?.phone?.observe(this, Observer
user_phone.setText(it ?: "")
)
上面的代码是写在Activity或Fragment中的,phone就是ViewModel中的一个LiveData,我们可以看到在监听LiveData时同时传了参数LifecycleOwner,LiveData就是通过这个LifecycleOwner感知生命周期的,当phone的值发生变化并且当前页面(LifecycleOwner)处于活跃状态时,会收到通知更新UI,如果页面不处于活跃状态不会收到通知的。当页面从不活跃状态变为活跃状态时,中间数据发生过变化的话会收到通知。
2、使用
在UI层(Activity、Fragment)向LiveData注册监听者,接收数据变化的通知,代码如上面的图所示;LiveData没有公开的方法更新数据,子类MutableLiveData公开了两个方法用来更新存储的值setValue和postValue,在UI线程可以直接使用setValue,在非UI线程使用postValue;
3、扩展LiveData
前面说过LiveData可以感知声明周期,如果部分业务请求对生命周期敏感,可以扩展LiveData,根据生命周期回调做相应的工作:
class StockLiveData(symbol: String) : LiveData<BigDecimal>()
private val stockManager = StockManager(symbol)
private val listener = price: BigDecimal ->
value = price
override fun onActive()
stockManager.requestPriceUpdates(listener)
override fun onInactive()
stockManager.removeUpdates(listener)
4、LiveData转换 -- Transformations
如果想在LiveData的值返回给观察者前进行处理,可以使用Transformations类的map()和switchMap()方法,这两个方法类似,都是以原始LiveData和一个函数做入参,返回一个新的LiveData,区别是这个函数入参的返回值,一个是返回处理过后的数据,一个是返回新的LiveData,看官方文档有点难理解,但是看源码就比较清晰,他们都是返回了一个新的MediatorLiveData对象,对原始LiveData做了处理,可以看下官方例子和map()源码
// userLiveData值更新会触发userName更新,用户监听userName即可;
// 原始User对象用来生成userName的值
val userLiveData: LiveData<User> = UserLiveData()
val userName: LiveData<String> = Transformations.map(userLiveData)
user -> "$user.name $user.lastName"
//userId值更新会触发user更新,用户监听user即可;
//原始userId用来生成新的LiveData
private fun getUser(id: String): LiveData<User>
...
val userId: LiveData<String> = ...
val user = Transformations.switchMap(userId) id -> getUser(id)
//map()源码
public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source, @NonNull final Function<X, Y> mapFunction)
final MediatorLiveData<Y> result = new MediatorLiveData();
result.addSource(source, new Observer<X>()
public void onChanged(@Nullable X x)
result.setValue(mapFunction.apply(x));
);
return result;
5、LiveData合并 ---- MediatorLiveData
MediatorLiveData是LiveData的一个子类,它可以将多个LiveData合并,有任何一个LiveData变化都可以触发通知MediatorLiveData的观察者。上面的Transformations的map()和switchMap()都是使用的MediatorLiveData实现的。
var data1 = MutableLiveData<String>()
var data2 = MutableLiveData<String>()
var mediator1 = MediatorLiveData<String>()
mediator1.addSource(data1)
mediator1.value = "handled $it"
mediator1.addSource(data2)
mediator1.value = "handled $it"
var mediator2 = MediatorLiveData<String>().apply
this.addSource(data1)
this.value = "handled $it"
this.addSource(data2)
this.value = "handled $it"
以上是关于Android之MVVM架构之ViewModel + LiveData + DataBinding的主要内容,如果未能解决你的问题,请参考以下文章
Android架构组件之ViewModel和LiveData
Android架构组件之ViewModel和LiveData