Android之MVVM架构之ViewModel + LiveData + DataBinding

Posted mr_zengkun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android之MVVM架构之ViewModel + LiveData + DataBinding相关的知识,希望对你有一定的参考价值。

前言

很早前写过一篇MVVM架构的文章,当时写的很粗糙,一直想抽空补全一下,自己对MVVM的理解,写一篇让新手都能够容易掌握的文章。
众所周知,Google已经开始倾向MVI架构,但是作为一个开发者,在开发中,只有存在合适的架构,所以关于MVI架构,希望以后有真正的了解再来写一写自己的见解。

基本使用

刚学习MVVM,这张架构图是非常重要的

  1. 简单的说一下
    ViewModel:关联层,将Model和View进行绑定,只做和业务逻辑相关的工作,不涉及任何和UI相关的操作,不持有控件的引用,不更新UI。
    View:视图层,只做和UI相关的工作,不涉及任何业务逻辑,不涉及操作数据,不处理数据。UI和数据严格的分开。
    Model:模型层,保存数据的状态,比如数据存储、网络请求。同时还与View存在一定的耦合,可以通过观察

  2. 启用方式

//添加ViewModel的引用
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.2.0'



//启用DataBinding
buildFeatures 
   dataBinding true

  1. 自定义的VM类只要继承引用的第三方库中的ViewModel抽象类即可
class MyViewModel: VideModel

  1. 然后我们就可以在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官方给的生命周期图

  1. 生命周期长于Activity。不难发现,ViewModel的生命周期,贯穿整个被绑定的Activity的生命周期,也就是说,既然Activity因为系统配置变更销毁重建,比如旋转屏幕等,ViewModel对象依旧会被保留,并关联到新的Activity。只有当Activity正常销毁时,ViewModel对象才会调用onCleared()进行清除。
  2. 开头说过,众多开发模式当中,他们的区别大同小异,都是为了实现解耦。而我们知道,在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公开了两个方法用来更新存储的值setValuepostValue,在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

Android mvvm框架之ViewModel

Android基础——框架模式MVVM之DataBinding的实践

Android 面试之—— ViewModel 总结篇

Android mvvm框架之ViewModel