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当中去使用它。- -!

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,下面是一个简单的示例: 首先,在build.gradle文件中添加以下依赖项: ```groovy // ViewModel and LiveData implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' // Data Binding implementation 'androidx.databinding:databinding-runtime:4.0.1' ``` 接下来,创建一个名为MainActivity的Activity,并在其布局文件中添加两个Fragment的占位符: activity_main.xml: ```xml <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <FrameLayout android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent"/> </layout> ``` MainActivity.java: ```java public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); // 加载第一个Fragment getSupportFragmentManager().beginTransaction() .replace(R.id.container, new FirstFragment()) .commit(); } } ``` 接下来,创建一个名为FirstFragment的Fragment,并在其布局文件中使用DataBinding绑定数据: first_fragment.xml: ```xml <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.text}" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Next" android:onClick="@{viewModel::onNextClicked}" /> </LinearLayout> </layout> ``` FirstFragment.java: ```java public class FirstFragment extends Fragment { private FirstViewModel viewModel; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { // 使用DataBinding绑定布局文件 FirstFragmentBinding binding = DataBindingUtil.inflate(inflater, R.layout.first_fragment, container, false); // 创建ViewModel实例 viewModel = ViewModelProviders.of(this).get(FirstViewModel.class); // 将ViewModel与布局文件中的变量绑定 binding.setViewModel(viewModel); // 设置LifecycleOwner,以便LiveData知道何时更新UI binding.setLifecycleOwner(this); return binding.getRoot(); } } ``` 下面是FirstViewModel.java: ```java public class FirstViewModel extends ViewModel { private MutableLiveData<String> textLiveData = new MutableLiveData<>(); public FirstViewModel() { // 初始化LiveData的默认值 textLiveData.setValue("Hello, World!"); } public LiveData<String> getText() { return textLiveData; } public void onNextClicked() { // 更新LiveData的值 textLiveData.setValue("Next Clicked!"); } } ``` 最后,创建另一个名为SecondFragment的Fragment,并在MainActivity中添加一个方法来加载它: second_fragment.xml: ```xml <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.text}" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Previous" android:onClick="@{viewModel::onPreviousClicked}" /> </LinearLayout> </layout> ``` SecondFragment.java: ```java public class SecondFragment extends Fragment { private SecondViewModel viewModel; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { // 使用DataBinding绑定布局文件 SecondFragmentBinding binding = DataBindingUtil.inflate(inflater, R.layout.second_fragment, container, false); // 创建ViewModel实例 viewModel = ViewModelProviders.of(this).get(SecondViewModel.class); // 将ViewModel与布局文件中的变量绑定 binding.setViewModel(viewModel); // 设置LifecycleOwner,以便LiveData知道何时更新UI binding.setLifecycleOwner(this); return binding.getRoot(); } } ``` 下面是SecondViewModel.java: ```java public class SecondViewModel extends ViewModel { private MutableLiveData<String> textLiveData = new MutableLiveData<>(); public SecondViewModel() { // 初始化LiveData的默认值 textLiveData.setValue("Goodbye, World!"); } public LiveData<String> getText() { return textLiveData; } public void onPreviousClicked() { // 更新LiveData的值 textLiveData.setValue("Previous Clicked!"); } } ``` 最后,在MainActivity中添加一个方法来加载SecondFragment: ```java private void loadSecondFragment() { getSupportFragmentManager().beginTransaction() .replace(R.id.container, new SecondFragment()) .commit(); } ``` 现在,你的MVVM Android项目就完成了!你可以使用LiveDataViewModel来管理数据,并使用DataBinding将数据绑定到UI上。Lifecycle组件可确保UI在活动和片段之间正确地进行管理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值