ViewModel原理

一、ViewModel的优势

1.避免横竖屏销毁时手动存数据,而且由于不是序列化,所以任何数据都可以存储

2.Act Frag 主要处理显示的逻辑,VM可以把数据操作的逻辑处理,实现数据与视图控制的逻辑分离

3.避免由于耗时操作(获取网络数据),导致内存泄漏,VM可以避免内存泄漏

由于耗时操作都不在Activity中处理,且VM没有持有Activity,所以Act可以正常退出,

观察当前 activity 生命周期,当 Lifecycle.Event == Lifecycle.Event.ON_DESTROY,并且 isChangingConfigurations() 返回 false 时才会调用 ViewModelStore#clear 。

所以Act正常退出,VM会把还在进行的耗时操作取消,所以整体不会有内存泄漏

二、简单使用

通过ViewModelDelegate的封装,

是的View和Service中都可以直接新建ViewModel

View Fragment中的是拿Activity的context,所以是跟Activity共享同一个

package com.seewo.eclass.mvvm.extention

import android.app.Dialog
import android.view.ContextThemeWrapper
import android.view.View
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import com.seewo.eclass.mvvm.base.CompatService
import com.seewo.eclass.mvvm.viewmodel.ViewModelFactory
import kotlin.reflect.KClass
import kotlin.reflect.KProperty

class ViewModelDelegate<T : ViewModel>(private val clazz: KClass<T>) {

    private var viewModel: T? = null

    operator fun setValue(thisRef: Dialog, property: KProperty<*>, model: T) {}
    operator fun setValue(thisRef: View, property: KProperty<*>, model: T) {}
    operator fun setValue(thisRef: CompatService, property: KProperty<*>, model: T) {}


    operator fun getValue(thisRef: Dialog, property: KProperty<*>) = buildViewModel(
        activity = if (thisRef.context is ContextThemeWrapper) {
            (thisRef.context as ContextThemeWrapper).baseContext as FragmentActivity?
        } else (thisRef.context as FragmentActivity)
    )

    operator fun getValue(thisRef: View, property: KProperty<*>) = buildViewModel(activity = thisRef.context as FragmentActivity)

    operator fun getValue(thisRef: CompatService, property: KProperty<*>) = buildViewModel(compatService = thisRef)

    private fun buildViewModel(activity: FragmentActivity? = null, compatService: CompatService? = null): T {
        if (viewModel != null) return viewModel!!

        activity?.let {
            viewModel = ViewModelProviders.of(it, ViewModelFactory.instance).get(clazz.java)
        } ?: compatService?.let {
            viewModel = ViewModelProvider(it, ViewModelFactory.instance).get(clazz.java)
        } ?: throw IllegalStateException("Activity or CompatService is null! ")

        return viewModel!!
    }
}


fun <T : ViewModel> Dialog.viewModelDelegate(clazz: KClass<T>) =
    ViewModelDelegate(clazz)
fun <T : ViewModel> View.viewModelDelegate(clazz: KClass<T>) =
    ViewModelDelegate(clazz)

fun <T : ViewModel> CompatService.viewModelDelegate(clazz: KClass<T>) =
    ViewModelDelegate(clazz)
fun <T : ViewModel> CompatService.createViewModel(clazz: Class<T>) = ViewModelProvider(this, ViewModelFactory.instance).get(clazz)

三、源码

要点:Jetpack 面试: ViewModel 必知的几个问题

new ViewModelProvider(activity.getViewModelStore(), factory);

第一个参数会调用activity的getViewModelStore()方法(这个方法会返回ViewModelStore,这个类是拿来存储ViewModel的,下面会说到),这里的activity是androidx.fragment.app.FragmentActivity,看一下这个

ViewModel存在哪里?

1.每个Activity都有一个对应的ViewModelStore,这个维护了一个HashMap,以是用来存储

public class ViewModelStore {
    private final HashMap<String, ViewModel> mMap = new HashMap<>();
    //省略了部分东西
    }

2.创建VM时,用到ViewModelProvider

3.ViewModelProvider 构造方法里调用了 this(ViewModelStore, Factory),将 ComponentActivity#getViewModelStore 返回的 ViewModelStore 实例传了进去,并缓存到 ViewModelProvider 中:

4.ViewModelProvider通过get获取ViewModel时 ,以要创造的ViewModel的名字为key,从(Activity的)mViewModelStore中去拿,如果有缓存直接使用,

如果没有就直接创建并存到Activity的mViewModelStore中

还有很重要的一点,viewModelStore 对象被缓存在 NonConfigurationInstances

onRetainNonConfigurationInstance 方法和 getLastNonConfigurationInstance 是成对

简单总结:ViewModel 对象存在了 ComponentActivity 的 mViewModelStore 对象中

  • 所以不同Activity创建的ViewModel默认是不同实例,但是同一个Activity中的View或者Fragment的context获取到的ViewModel默认是同一个。
  • 横竖屏时,mViewModelStore被作为NonConfigurationInstances对象,通过onRetainNonConfigurationInstance()保存起来,再次用到ViewModel会通过这个Act中被保存起来的mViewModelStore中的去找,所以可以做到声明周期比Activity长

Activity真正销毁时,Lifecycle.Event == Lifecycle.Event.ON_DESTROY

观察当前 activity 生命周期,当 Lifecycle.Event == Lifecycle.Event.ON_DESTROY,并且 isChangingConfigurations() 返回 false 时才会调用 ViewModelStore#clear 。

要点

1.在Activity中声明,要怎么做到声明的对象生命周期比Activity还长

实现:每次切换横竖屏时保存ViewModel

每次横竖屏切换的时候,

FragmentActivity

在onRetainNonConfigurationInstance()中保存一个NonConfigurationInstances对象

NonConfigurationInstances:这个会保存mViewModelStore和所有Fragment

在横竖屏切换的时候又会调用getLastNonConfigurationInstance获取上面保存的NonConfigurationInstances对象

所以在横竖屏切换的时候ViewModel的实例还在,就避免了数据丢失

2.同一个lifecycle下的所有ViewModel对象都是同一个

通过ViewModelStore里的HashMap保存

3.ViewModel是一个抽象类,它的子类都是通过反射用创建

四、onSaveInstanceState和onRetainNonConfigurationInstance的区别

     

更多细节可以看:聊聊onSaveInstanceState和onRetainNonConfigurationInstance的区别

onSaveInstanceState

「onSaveInstanceState」方法是当「Activity」调用了「onStop」后,会调用到「ActivityThread」的「callActivityOnSaveInstanceState()「方法,把」Activity」需要保存的数据放入「Bundle」对象中,并且随后通过IPC进程间通信机制,调用「ActivityManagerService的activityStopped」方法,将「Bundle」对象保存到AMS端的「ActivityRecord」中。

跨进程(IPC)通过Bundle存数据到AMS (SystemService进程),app被杀也能恢复数据

通过ActivityRecord.icicle恢复数据

那么存储的key是什么?恢复的时候怎么对应到指定的Activity

onRetainNonConfigurationInstance

 该方法是在重建「Activity」时调用「performDestoryActivity」时会保存数据。

「onRetainNonConfigurationInstance」方法返回的「Object」会赋值给「ActivityClientRecord」的「lastNonConfigurationInstances」。

总结:

  1. 颗粒度不一样。「onSaveInstanceState()「是保存到」Bundle」中,只能保存「Bundle」能接受的数据类型,比如一些基本类型的数据。而「onRetainNonConfigurationInstance()」 可以保存任何类型的数据,数据类型是「Object」
  2. 「onSaveInstanceState()「数据最终存储到」ActivityManagerService」的「ActivityRecord」中了,也就是存到「系统进程」中去了。而「onRetainNonConfigurationInstance()」 数据是存储到「ActivityClientRecord」中,也就是存到「应用本身的进程」中
  3. 「onSaveInstanceState」存到系统进程中,所以App被杀之后还是能恢复的。而「onRetainNonConfigurationInstance」存到本身进程中,App被杀是没法恢复的。
  4. onSaveInstanceState() 仅适合可以序列化再反序列化的少量数据(IPC 对 Bundle 有 1M 的限制),而不适合数量可能较大的数据

五、注意点

VM里面不要持有Act或者Fragment的实例,因为他的生命周期比他们长,那样会造成内存泄漏

如果需要用到context的话需要使用Application的

android已经为我们提供了使用Application的Context的ViewModel

AndroidViewModel

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值