Jetpack系列 ViewModel

前言

大家如果想了解ViewModel的理论可以先去官方去了解下这里不做概述,直接干货


一、ViewModel 是什么?

ViewModel是生命周期的方式存储和管理界面相关的数据类。ViewModel 让数据可在发生屏幕旋转等配置更改后继续留存。如今在MVVM架构中担任VM的重要角色

二、使用步骤

1.新建View Model实例

support库 :
android.arch.lifecycle包下

androidx 库:
androidx.lifecycle包下
实现都是一样的

class MyViewModels() : ViewModel() {
    private val _liveData by lazy {   //懒加载
        MutableLiveData<String>()
    }
    fun getLiveData() = _liveData.apply {
        _liveData.value = "Hello , ViewModel"
    }
    
}

通过源码可以知道ViewModel是一个抽象类所以我们只有继承它就可以实现自定义的ViewModel代码如下:

public abstract class ViewModel {

}

一般View Model都是配合LiveData使用进行对Activity、Fragment的数据管理使在UI界面得到共享数据自动实现数据观察者模式


import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProviders

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
//        val vm = ViewModelProviders.of(this,MyViewModel.MyViewModelFactory(this)).get(MyViewModel::class.java)
//        vm.info()


        val vm = ViewModelProviders.of(this,).get(MyViewModels::class.java)     //获取实例
        vm.getLiveData().observe(this){
            //观察数据
        }
    }
}

这样就最简单的完成了ViewModel对Activity的数据管理了
ViewModelProviders是我引入的一个依赖 如下:

implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0'

2.原理分析

2.1 那么ViewModel 是怎么实现对数据的管理的呢?

先从Activity入手,来到androidx.activity.ComponentActivity可以看到实现了一个ViewModelStoreOwner接口

接口很简单代码如下:


package androidx.lifecycle;

import androidx.annotation.NonNull;

/**
 * A scope that owns {@link ViewModelStore}.
 * <p>
 * A responsibility of an implementation of this interface is to retain owned ViewModelStore
 * during the configuration changes and call {@link ViewModelStore#clear()}, when this scope is
 * going to be destroyed.
 */
@SuppressWarnings("WeakerAccess")
public interface ViewModelStoreOwner {
    /**
     * Returns owned {@link ViewModelStore}
     *
     * @return a {@code ViewModelStore}
     */
    @NonNull
    ViewModelStore getViewModelStore();
}

来到接口的实现方法:

  @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }

从代码得知通过getLastNonConfigurationInstance获取实列,通过新建对象获取实列,我们进一步来到getLastNonConfigurationInstance进行分析源码给的注释:

 @Nullable
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }

    /**
     * Called by the system, as part of destroying an
     * activity due to a configuration change, when it is known that a new
     * instance will immediately be created for the new configuration.  You
     * can return any object you like here, including the activity instance
     * itself, which can later be retrieved by calling
     * {@link #getLastNonConfigurationInstance()} in the new activity
     * instance.

我们知道这个方法是系统调用的那么我们又回到ComponentActivity类这里面一个重新方法onRetainNonConfigurationInstance()代码如下:

 @Override
    @Nullable
    public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();

        ViewModelStore viewModelStore = mViewModelStore;
        if (viewModelStore == null) {
            // No one called getViewModelStore(), so see if there was an existing
            // ViewModelStore from our last NonConfigurationInstance
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                viewModelStore = nc.viewModelStore;
            }
        }

        if (viewModelStore == null && custom == null) {
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }

这里就是系统重写方法的回调也就是对Activity之前的viewModelStore配置对象进行获取这样就实现了Activity的数据保存(屏幕翻转)

再来到Fragment进行源码查看来到androidx.fragment.app.Fragment这里也实现了ViewModelStoreOwner接口那么和上面一样来到实现的方法如下:

  @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        if (mFragmentManager == null) {
            throw new IllegalStateException("Can't access ViewModels from detached fragment");
        }
        return mFragmentManager.getViewModelStore(this);
    }

从中可以得知对象是从FragmentManagerImpl类中获取的代码如下:

   @NonNull
    ViewModelStore getViewModelStore(@NonNull Fragment f) {
        return mNonConfig.getViewModelStore(f);
    }

mNonConfig这个是FragmentManagerViewModel类的实列那么进一步溯源:


    public void attachController(@NonNull FragmentHostCallback host,
            @NonNull FragmentContainer container, @Nullable final Fragment parent) {
        if (mHost != null) throw new IllegalStateException("Already attached");
        mHost = host;
        mContainer = container;
        mParent = parent;
        if (mParent != null) {
            // Since the callback depends on us being the primary navigation fragment,
            // update our callback now that we have a parent so that we have the correct
            // state by default
            updateOnBackPressedCallbackEnabled();
        }
        // Set up the OnBackPressedCallback
        if (host instanceof OnBackPressedDispatcherOwner) {
            OnBackPressedDispatcherOwner dispatcherOwner = ((OnBackPressedDispatcherOwner) host);
            mOnBackPressedDispatcher = dispatcherOwner.getOnBackPressedDispatcher();
            LifecycleOwner owner = parent != null ? parent : dispatcherOwner;
            mOnBackPressedDispatcher.addCallback(owner, mOnBackPressedCallback);
        }

        // Get the FragmentManagerViewModel
        if (parent != null) {
            mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
        } else if (host instanceof ViewModelStoreOwner) {
            ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
            mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
        } else {
            mNonConfig = new FragmentManagerViewModel(false);
        }
    }

从这里就可以得知mNonConfig 的实列获取
从中看出有三种方法进行实例 :
parent :这个是从父FragmentManager中得到NonConfig
host : 这个就是管理Fragment的Activity实例从Activity中得到NonConfig
最后一种就是直接新建实例

2.2 分析ViewModel实例

首先不能直接使用new 因为这样每次新建的话就违背了数据保存的原则
实例如下:

 val vm = ViewModelProviders.of(this).get(MyViewModels::class.java)     //获取实例

为什么是这样呢?那么我们进一步分析
进入到ViewModel类可以看到有一个Map域的声明、还有一个clear函数
在这里插入图片描述
可以看到clear函数就是对Map里面的值进行清空。嗯,若有所思
先放着我们回到获取View Model实例的地方发现是通过ViewModelProviders调用了一个of函数点进去
在这里插入图片描述
在这里插入图片描述

这个可以理解方法重写嘛,不管调用哪个最终都会回到两个参数的方法
第一个参数为FragmentActivity至于为什么是FragmentActivity,因为它继承的ComponentActivity实现了ViewModelStoreOwner接口上面也有讲到
第二个参数Factory ,这里的Factory是ViewModelProvider里面的Factory ,这个参数后面会讲到
继续分析
第一行代码:检查Activity如果在Application的oncreate前调用呢 就直接抛出异常
第二行代码:判断是否有传入Factory ,如果没有就使用默认的AndroidViewModelFactory
返回:通过使用工厂模式,ViewModel的创建交由工厂完成,通过获取View对象ViewModelStore传入中ViewModelProvider。
getViewModelStore函数就是我们上面分析的ViewModelStoreOwner接口里面的实现方法
在这里插入图片描述
那么我们点击去ViewModelStore这个类
在这里插入图片描述
看到Map域里面存储的是ViewModel因为一个Activity中可能有多个ViewModel,所以需要一个Map来维护关系表,key为ViewModel的名字,value为ViewModel对象 通过put方法进行存储,是不是感觉和ViewModel类的实现特别像
再回到之前的代码
在这里插入图片描述

        val vm = ViewModelProviders.of(this,).get(MyViewModels::class.java)     //获取实例

通过of函数返回了一个ViewModelProvider追后调用了get函数点进去分析如图
在这里插入图片描述
方法体首先对传入的参数进行规范判断如图
在这里插入图片描述
最后通过get重载函数以Default_Key作为前缀加上ViewModel 的完整类名为key,获取ViewModel 对象。
在这里插入图片描述

从ViewModelStore中的储存单元Map中获取ViewModel
先看ViewModelStore中是否存在,如果存了就直接返回
如果不存在,好像什么也没做如图:
在这里插入图片描述
在这里插入图片描述

这段代码就是判断我们传入的factort字段我们之前没有传入factort字段所以它为默认的AndroidViewModelFactory
在这里插入图片描述
然后这里将factort进行了一个比较那我们先来了解下AndroidViewModelFactory
这个类进行溯源可以知道实现了ViewModelProvider.Factory接口
getInstance()函数获取该类的实例
create()创建ViewModel
完成分析 回到get函数继续我们的分析
在这里插入图片描述

  • 之后对mFactory 和KeyedFactory进行了一个比较
  • 前面分析知道这两个类都分别直接或间接的实现了ViewModelProvider.Factory接口
  • 如果是KeyedFactory类型则调用KeyedFactory类里面带有key的create()函数 这个key就是DEFAULT_KEY + “:” + canonicalName(以Default_Key作为前缀加上ViewModel 的完整类名为key)
  • 这个key是用来在原有ViewModelStore的储存单元中找到进行覆盖一个新的ViewModel
  • 也就是为什么在原有的Viewmodel中添加一个带参数的构造器然后获取实例时没有传入Factory参数会报错的原因,在下面我会给大家演示
    在这里插入图片描述

如果不是KeyedFactory类型通过调用ViewModelProvider.Factory接口create()函数进行反射创建一个ViewModel

然后将所创建的ViewModel添加到ViewModelStore储存单元中

最后返回ViewModel完成对ViewModel的获取实例


3.ViewModel的使用

当我们需要在ViewModel类中通过构造器传递数值时,我们需要通过一个ViewModelProvider.Factory接口去实现它的create函数进行重载源码如下:


public class ViewModelProvider {
    /**
     * Implementations of {@code Factory} interface are responsible to instantiate ViewModels.
     */
    public interface Factory {
        /**
         * Creates a new instance of the given {@code Class}.
         * <p>
         *
         * @param modelClass a {@code Class} whose instance is requested
         * @param <T>        The type parameter for the ViewModel.
         * @return a newly created ViewModel
         */
        @NonNull
        <T extends ViewModel> T create(@NonNull Class<T> modelClass);
    }}

那么我们的代码就可以这样写:

class MyViewModel(private val context: Context) : ViewModel() {


    fun info(){
        println("上下文对象: $context")
    }

     class MyViewModelFactory(private val contexts:Context) : ViewModelProvider.Factory{
        override fun <T : ViewModel?> create(modelClass: Class<T>): T = MyViewModel(contexts)  as T
    }

}

通过这段代码我们重载了Factory并进行了覆盖原有create函数
那么我们继续运行后发现报错:
在这里插入图片描述
报错不能实例化,原因:我们之前在ViewModel类中重载了ViewModelProvider.Factory中的create函数使之覆盖了原来的AndroidViewModelFactory源码如下:

@NonNull
    @MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity,
            @Nullable Factory factory) {
        Application application = checkApplication(activity);
        if (factory == null) {
            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        return new ViewModelProvider(activity.getViewModelStore(), factory);
    }

所以这里我们必须要传入一个ViewModelProvider.Factory对象

 val vm = ViewModelProviders.of(this,MyViewModel.MyViewModelFactory(this)).get(MyViewModel::class.java)
        vm.info()

最后运行完成成功打印上下文地址

3.1 AndroidViewModel

那么如果我们要在ViewModel中使用上下文对象呢?
我上面写的实例是不对的,VIewModel不能持有View的上下文对象
因为ViewModel在Activity因配置变化导致重建时会被保留,从生命周期的角度来说,ViewModel的生命周期可能会长于Activity的生命周期。

这说明我们在使用ViewModel时一定要注意,不能让其引用Activity或View,否则可能导致内存泄漏。

源码为我们提供了一个子类AndroidViewModel 这个类就可以自带上下文对象Application

public class AndroidViewModel extends ViewModel {
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    public <T extends Application> T getApplication() {
        return (T) mApplication;
    }
}

总结

ViewModel的分析就到这里了,大家多看看开源项目就知道ViewModel怎么用了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值