(原创)Jetpack系列(二):ViewModel+LiveData

前言

Jetpack上一篇讲完了lifecycle
(原创)Jetpack系列:lifecycle
现在我们来讲ViewModel和LiveData
他们一般是配合来使用的
ViewModel用来存储数据
LiveData来监听数据的变化
这样做的好处是把数据的操作放在ViewModel里
Activity页面只负责业务逻辑即可

ViewModel还可以防止瞬态数据丢失
例如横竖屏的时候数据不会丢失

ViewModel的创建方法

关于ViewModel的创建,有以下几种方式:
1:最原始的办法:
先创建ViewModelProvider

var viewModelProvider =
            ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory(application))

然后创建ViewModel

var myViewModel = viewModelProvider.get(MyViewModel::class.java)

2:在fragment中有以下两种方式,创建属于当前fragment的ViewModel:

@MainThread
public inline fun <reified VM : ViewModel> Fragment.viewModels(
    noinline ownerProducer: () -> ViewModelStoreOwner = { this },
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> = createViewModelLazy(VM::class, { ownerProducer().viewModelStore }, factoryProducer)
@MainThread
public inline fun <reified VM : ViewModel> Fragment.activityViewModels(
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> = createViewModelLazy(
    VM::class, { requireActivity().viewModelStore },
    factoryProducer ?: { requireActivity().defaultViewModelProviderFactory }
)

使用示例:

val viewModel: HomeFragViewModel by viewModels()
val activityViewModel: HomeFragViewModel by activityViewModels()

HomeFragViewModel是你自己ViewModel的类的名字
可以看到这两个方法,一个是和fragment绑定
一个是和fragment所在的Activity绑定
所以一个Activity下的多个fragment就可以通过第二种方式实现数据通信了
内部实现其实是基于这个方法:

@MainThread
public fun <VM : ViewModel> Fragment.createViewModelLazy(
    viewModelClass: KClass<VM>,
    storeProducer: () -> ViewModelStore,
    factoryProducer: (() -> Factory)? = null
)

点进去看,归根结底也是用ViewModelProvider(store, factory).get(viewModelClass.java)的方式去做的

3:Activity里面创建Viewmodel可以这样:

@MainThread
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
    noinline factoryProducer: (() -> Factory)? = null
)

点进去看,走的也是ViewModelLazy类里面的ViewModelProvider(store, factory).get(viewModelClass.java)
综上,这两张方式其实只是提供的一个快捷创建方式,
帮我们快速创建和绑定Activity/fragment而已
然后我们再来看下最基础的创建方式:

ViewModelProvider(<你的ActivityFragment 实例>,<创建的factory>).get(<你的ViewModel>::class.java)

ViewModelProvider说明:
ViewModelProvider其实提供了三个构造方法给我们去创建ViewModelProvider市里
然后调用他的get方法通过反射去创建我们的Viewmodel

    public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }
    public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
        this(owner.getViewModelStore(), factory);
    }
    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }

可以看到,owner传入后,其实就是调用它的getViewModelStore方法
第一个和第二个构造方法,最终走的都是第三个构造方法
ViewModelStore就是用来缓存我们的Viewmodel的对象的,在界面销毁时做一个缓存
下次再从缓存里面读。
owner的生命周期结束后,缓存也会消失
而Factory其实是一个接口,我们可以传入不同的实现:

    public interface Factory {
        @NonNull
        <T extends ViewModel> T create(@NonNull Class<T> modelClass);
    }

ViewModelProvider的get()方法,内部其实就是调用我们传入的Factory的create
去反射创建我们的ViewModel示例。
最后做个总结:
1:ViewModelProvider用来创建ViewModel
2:Viewmodel是和owner的生命周期绑定的
3:真正创建ViewModel的是实现了Factory接口的类的create方法
4:Activity/Fragment都封装了一些自己的快捷创建Viewmodel的方法
如viewModels和activityViewModels方法
注意:第二种和第三种方法,其实是对原始方法的封装
使用这两种方法,需要导入:

implementation "androidx.activity:activity-ktx:1.4.0"

自定义ViewModelProvider.Factory

当我们要根据自己的实际需要来创建自己的ViewModel时
比如要求Viewmodel是单例,或者是全局,或者构造方法有参数
就可以自定义Factory,实现create方法即可
如下,我们自定义了一个可以构造方法可以传入参数的ViewNodel:

class UserViewModelFactory(
    private val type: String
) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return modelClass.getConstructor(String::class.java).newInstance(type)
    }
}

系统也提供了很多的Factory实现,大家可以去自行了解

实战小例子

下面我们通过一个小例子来掌握这两个东西。
首先我们创建一个自己的ViewModel类
去继承AndroidViewModel
里面有一个变量num
代码如下

/**
 * viewModel类
 *
 */
class MyViewModel(application: Application) : AndroidViewModel(application) {
    var num:Int=0
}

然后在Activity里写一个btn按钮和一个textview
textview的文字内容使用的就是自定义Viewmodel里的num值
btn按钮点击一下,textview的num属性值就一
代码如下:

class MainActivity : AppCompatActivity() {


    lateinit var viewModelProvider: ViewModelProvider
    lateinit var myViewModel: MyViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        viewModelProvider =
            ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory(application))
        myViewModel = viewModelProvider.get(myViewModel::class.java)

        mybtn.setOnClickListener {
            mytv1.text = "${++myViewModel.num}"
        }

        mytv1.text = "${myViewModel.num}"

    }
}

上面的ViewModel看起来很简单,无非是数据放在了MyViewModel里
但这不是关键,关键是引入liveData后

现在把代码修改如下:
1、新建一个MyLiveDataViewModel

class MyLiveDataViewModel : ViewModel() {
    private var currentSecond: MutableLiveData<Int>? = null
    fun getCurrSecond(): MutableLiveData<Int> {
        if (currentSecond == null) {
            currentSecond = MutableLiveData()
            currentSecond!!.value = 0
        }
        return currentSecond!!
    }
}

可以看到这里currentSecond返回的是一个MutableLiveData的整型数值
然后在我们的Activity里这样修改

class MainActivity : AppCompatActivity() {


    lateinit var viewModelProvider: ViewModelProvider
    lateinit var myViewModel: MyViewModel
    lateinit var myLiveDataViewModel: MyLiveDataViewModel


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        viewModelProvider =
            ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory(application))
        myViewModel = viewModelProvider.get(MyViewModel::class.java)

        mybtn.setOnClickListener {
            mytv1.text = "${++myViewModel.num}"
        }

        mytv1.text = "${myViewModel.num}"

        myLiveDataViewModel = viewModelProvider.get(MyLiveDataViewModel::class.java)

        myLiveDataViewModel.getCurrSecond().observe(this, Observer {
            mytv2.text = "${it}"
        })
        startTimer()


    }

    private fun startTimer() {
        Timer().schedule(timerTask {
            //非ui线程,用postValue
            myLiveDataViewModel.getCurrSecond().postValue(myLiveDataViewModel.getCurrSecond().value?.plus(1))
        },1000,1000)
    }
}

这里开起了一个子线程,每一秒钟让MyLiveDataViewModel里的数值加1
利用MutableLiveData的observe方法对数值的变化进行监听
发现数据有改变就去刷新UI
其实就是模拟请求网络数据的过程

这样一来我们就发现了
ViewModel+LiveData
可以实现数据也业务的解耦
把数据的变化放在ViewModel里面
用LiveData对这个变化进行监听
然后进行UI刷新即可
当然
不同的Activity或者Fragment
可以共用一个viewmodel的值
数据有变化,两个Activity或者Fragment界面的值也会一起变化的
LiveData里面其实也做了lifecycle的监听
在页面不显示的时候,是不会去执行UI刷新的
最后,xml代码也放上来吧

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <TextView
        android:id="@+id/mytv1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="100dp"
        android:text="Hello World!"
        android:visibility="visible"
        tools:visibility="visible" />

    <Button
        android:id="@+id/mybtn"
        android:layout_width="124dp"
        android:layout_height="91dp"
        android:layout_below="@+id/mytv1"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="100dp"
        android:text="加1" />


    <TextView
        android:id="@+id/mytv2"
        android:layout_width="124dp"
        android:layout_height="91dp"
        android:gravity="center"
        android:layout_below="@+id/mybtn"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="100dp"
        android:text="0" />
</RelativeLayout>

补充:AndroidViewModel和AndroidViewModelFactory

上面例子用的ViewModel继承的是AndroidViewModel
用的是AndroidViewModelFactory来创建AndroidViewModel
注意,AndroidViewModelFactory传入的Context一定要是application里的Context
否则会导致内存泄漏。
因为使用ViewModel的时候,ViewModel不能够持有View、Lifecycle、Acitivity引用,
而且不能够包含任何包含前面内容的类。因为这样很有可能会造成内存泄漏。
那如果需要使用Context对象改怎么办。
这时候我们可以给ViewModel一个Application。Application是一个Context,而且一个应用也只会有Application。
那ViewModel怎么添加Application呢?其实Google还有一个AndroidViewModel。
这是一个包含Application的ViewModel。
下面是一个AndroidViewModel的源码:

public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;
 
    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }
 
    /**
     * Return the application.
     */
    @SuppressWarnings("TypeParameterUnusedInFormals")
    @NonNull
    public <T extends Application> T getApplication() {
        //noinspection unchecked
        return (T) mApplication;
    }
}

可以看到只是在ViewModel的基础上添加了Application。
其实看AndroidViewModelFactory源码就得知
AndroidViewModelFactory传入的Application,最终使用反射,调用了AndroidViewModel的构造方法
把Application传给了AndroidViewModel。如下:

 		@NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
                //noinspection TryWithIdenticalCatches
                try {
                    return modelClass.getConstructor(Application.class).newInstance(mApplication);
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (InstantiationException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (InvocationTargetException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                }
            }
            return super.create(modelClass);
        }

所以我们可以总结:
1:如果我们的ViewModel需要context,可以继承AndroidViewModel,否则继承普通的ViewModel就好
2:继承了AndroidViewModel,就要用AndroidViewModelFactory,
并且传入application里的Context来创建我们自己的的AndroidViewModel。

至此,ViewModel+LiveData就讲到这里了

你可能感兴趣的:(jetpack系列博客,android,kotlin,开发语言)