android Jetpack—ViewModel使用方法和详细原理解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: android Jetpack—ViewModel使用方法和详细原理解析

1、ViewModel 初始化方式


来到 androidx,ViewModel 的创建方式与老版本有了很大的不同,所以这里还是要将 Viewmodel 的初始化讲一下


1.1、安卓工厂初始化

每次都会重新创建 model,并且不受 ViewModelStore 管控,所以无特殊需求禁止使用该种方式


使用 AndroidViewModelFactory 工厂创建

  1. viewmodel 类定义
class AndroidFactoryViewModel(app:Application): AndroidViewModel(app) {
    fun print(){
        logEE("使用安卓默认工厂创建viewmodel")
    }
}
复制代码


  1. 创建 viewmodel 实例代码
val model = ViewModelProvider.AndroidViewModelFactory.getInstance(application)
                .create(AndroidFactoryViewModel::class.java)
            model.print()
复制代码


1.2、简单工厂初始化 ViewModel

每次都会重新创建 model,并且不受 ViewModelStore 管控,所以无特殊需求禁止使用该种方式

NewInstanceFactory

  1. viewmodel 类定义代码
class SimpleFactoryViewModel: ViewModel() {
    fun print(){
        logEE("使用简单工厂创建viewmodel")
    }
}
复制代码


  1. 创建 viewmodel 代码
val model =ViewModelProvider.NewInstanceFactory().create(SimpleFactoryViewModel::class.java)
 model.print()
复制代码


1.3、自定义安卓工厂初始化

多次创建可以复用 model,不会重新创建

默认的工厂只能创建带 Application 的 ViewModel 实例的,我们通过自定义工厂实现自定义构造参数的目的

  1. 定义安卓工厂
class CustomAndroidViewModelFactory(val app: Application, private val data: String) :
    ViewModelProvider.AndroidViewModelFactory(app) {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
            return try {
                modelClass.getConstructor(Application::class.java, String::class.java)
                    .newInstance(app, data)
            } ...
        }
        return super.create(modelClass)
    }
}
复制代码

省略了 catch 中的代码,您可以到源码中查看

在我们的自定义工厂的构造中传入一个自定义的 data 参数,代表我们的自定义构造参数


  1. viewmodel 类定义
    必须是继承 AndroidViewModel 才可以
class CustomAndroidViewModel(private val app: Application, private val data: String) :
    AndroidViewModel(app) {
    fun print() {
        logEE(data)
    }
}
复制代码

我们的 CustomAndroidViewModel 类中也有一个 data:String 参数,这个参数就是对应上一步中自定义工厂中的 data 参数


  1. 实例化 viewmodel
val model = ViewModelProvider(
                viewModelStore,
                CustomAndroidViewModelFactory(application, "自定义安卓工厂创建viewmodel")
            ).get(CustomAndroidViewModel::class.java)
            model.print()
复制代码


1.4、自定义简单工厂初始化

多次获取可以复用 model,不会重新创建

自定义简单工厂也是为了实现 viewmodel 构造传参的目的,废话不多说,直接上代码吧

  1. 定义安卓工厂
class CustomSimpleViewModelFactory(app:Application,private val data:String) : ViewModelProvider.AndroidViewModelFactory(app) {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return try {
            modelClass.getConstructor(String::class.java).newInstance(data)
        } ......
    }
}
复制代码


  1. viewmodel 类定义
class CustomSimpleViewModel(private val data: String) : ViewModel() {
    fun print() {
        logEE(data)
    }
}
复制代码


  1. 实例化 viewmodel
val model = ViewModelProvider(
                viewModelStore,
                CustomSimpleViewModelFactory(application, "自定义简单工厂创建viewmodel")
            ).get(CustomSimpleViewModel::class.java)
            model.print()
复制代码


1.5 使用委托机制创建 viewmodel(推荐)

多次创建可以复用 model,不会重新创建

google 官方给我们提供了 activity-ktx 库,通过它我们可以使用委托机制创建 viewmodel

实现方式如下:

  1. 加入依赖
implementation 'androidx.activity:activity-ktx:1.2.2'
复制代码


  1. 创建 viewmodel 类
class EnTrustModel : ViewModel() {
    fun print(){
        logEE("关注公众号 \"安安安安卓\" 免费学知识")
    }
}
复制代码


  1. 实例化 viewmodel
private val wtModel: EnTrustModel by viewModels<EnTrustModel> {
        ViewModelProvider.NewInstanceFactory()
    }
复制代码


  1. 调用 viewmodel 的方法
wtModel.print()
复制代码


2、 viewmodel 概览


ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。

我们知道 android 中的 Activity 和 Fragment 都是可以对生命周期进行管理的,如果 Activity/Fragment 被系统销毁,例如屏幕旋转就必须重新创建 Activity/Fragment,这个时候就涉及到一个数据恢复的问题。通常我们会使用 onSaveInstanceState 和 onRestoreSaveInstance 两个方法对数据进行保存和恢复,但是这两个方法只能处理数据量较小的情况。

如果是大数据量的情况那么用 viewmodel 就是不错的选择了

同时 viewmodel 还可以帮助我们将业务逻辑从 Activity/Fragment 中分离出来


3、 viewmodel 实现 Fragment 之间数据共享


同一个 activity 中出现多个 Fragment,并且这些 Fragment 之间需要进行通信,这样的需求是很常见的

以往我们可能会采用下列方式实现数据共享:

  1. EventBus
  2. 接口回调
  3. 全局共享数据池

但是当我们学了 viewmodel 后就会发现,viewmodel 可以轻松实现 Fragment 之间的数据共享

我们可以让多个 Fragment 共用同一个 ViewModel 实现数据的共享。


本例中我们要实现下面的功能:

activity 中有两个 fragment,FragmentA 和 FragmentB,我们在 ViewModel 的构造方法中开始倒计时 2000s,在 FragmentA 中观察倒计时的数据并展示在页面上。然后再切换到 FragmentB,如果 FragmentB 中的倒计时的秒数没有重置为 2000,说明我们的数据共享成功了。

上代码:

  1. 创建 ViewModel
class ShareDataModel: ViewModel() {
    val liveData = MutableLiveData<String>()
    var total = 2000L
    init {
        /**
         * 实现倒计时,一秒钟倒计时一次
         */
        val countDownTimer = object :CountDownTimer(1000 * total, 1000) {
            override fun onTick(millisUntilFinished: Long) {
                liveData.postValue("剩余倒计时时间 ${--total}")
            }
            override fun onFinish() {
                logEE("倒计时完成")
            }
        }
        countDownTimer.start()
    }
}
复制代码


  1. 创建 FragmentA
class FragmentA : Fragment() {
    private val model: ShareDataModel by activityViewModels()
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_a, container, false).apply {
            model.liveData.observe(viewLifecycleOwner,
                { value -> findViewById<TextView>(R.id.tv_aresult).text = value })
        }
    }
}
复制代码


  1. 创建 FragmentB
class FragmentB : Fragment() {
    private val model: ShareDataModel by activityViewModels()
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_b, container, false).apply {
            model.liveData.observe(viewLifecycleOwner,
                { value -> findViewById<TextView>(R.id.tv_bresult).text = value })
        }
    }
}
复制代码


  1. 展示结果

image.png

通过 gif 我们发现,即使 replace 后倒计时的数据仍然没有改变,说明我们成功实现了数据共享


4、 关于 viewmodel 的几个知识点


  1. viewmodel 绝对不可以引用视图
  2. 在 activity 的存在期间可能会多次走 onCreate 方法,但是我们的 viewmodel 只有 actvity 结束并销毁的情况下才会被回收
  3. viewmodel 通过 lifecycle 来观察 activity/fragment 的生命周期


5、源码解析


5.1、创建 ViewModel 源码解析

先看一下创建 ViewModel 的代码:

val model = ViewModelProvider(
                viewModelStore,
                CustomSimpleViewModelFactory(application, "自定义简单工厂创建viewmodel")
            ).get(CustomSimpleViewModel::class.java)
复制代码

那么就从 ViewModelProvider 构造方法和 get 方法说起吧,


  1. ViewModelProvider 构造方法

ViewModelProvider 构造方法传入两个参数,ViewModelStore(用来存储管理 ViewModel)和 Factory(创建 ViewModel 的工厂)

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }
复制代码


  1. ViewModelProvider 的 get 方法
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }
复制代码

getCanonicalName 方法可以获取一个能唯一标识 Class的字符串,最终会用作生成我们 ViewModelStore 中存储 ViewModel 的 key。

获取 key 后我们将 key 和 modelClass 做为参数调用重载 get 方法


  1. 重载 get 方法

方法的解读写在代码中了

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);//从ViewModelStore中根据key获取ViewModel,如果有就会返回
        if (modelClass.isInstance(viewModel)) {//判断获取的ViewModel是否是传入的modelClass类型
            if (mFactory instanceof OnRequeryFactory) {//不看这里
                ((OnRequeryFactory) mFactory).onRequery(viewModel);
            }
            return (T) viewModel;//如果是就返回viewmodel
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) mFactory).create(key, modelClass);//一般不会用这个工厂
        } else {
            viewModel = mFactory.create(modelClass);//创建ViewModel
        }
        mViewModelStore.put(key, viewModel);//将ViewModel添加到mViewModelStore中
        return (T) viewModel;
    }
复制代码


5.2、ViewModelStore 类解析

ViewModelStore 中有三个重要方法 get、put、clear,并且维护了一个 HashMap

  1. get
final ViewModel get(String key) {
        return mMap.get(key);
    }
复制代码


很简单就是根据key获取ViewModel
复制代码


  1. put
final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);//oldViewModel指的是被覆盖的ViewModel
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }
复制代码

将一个 ViewModel 添加到 map 中,并且将被覆盖的 ViewModel 数据清理(因为同一个 activity 中只能存在一个同类型的 ViewModel)


  1. clear

下一节会讲

clear 方法的调用时机(重点)

先看一下 clear 方法

public final void clear() {
      for (ViewModel vm : mMap.values()) {
          vm.clear();
      }
      mMap.clear();
  }
复制代码


clear 方法的调用位置在 ComponentActivity 的构造方法中,代码如下:

getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    // Clear out the available context
                    mContextAwareHelper.clearAvailableContext();
                    // And clear the ViewModelStore
                    if (!isChangingConfigurations()) {//当有配置信息存在的时候返回:true(屏幕旋转,后台被杀死),当没有配置信息的时候返回:false(应用被主动杀死)。
                        getViewModelStore().clear();
                    }
                }
              }
          });
复制代码

恍然大悟,原来 ViewModel 通过 LifeCycle 观察 Activity 生命周期的方式来处理数据的清理操作


ViewModelScope如何保证异常情况不被销毁

2021-06-20修订

之前写完后不久后发现ViewModelScope的不变性也是重要的一点,所以补充一下

我们知道我们的ViewModel是在ViewModelStore中存储的,所以当屏幕旋转或者被后台回收的时候其实ViewModelStore也是会被回收的,那么我们如何保证ViewModelStore在重新走onCreate方法的时候的不变性呢,

直接先晒代码:

ViewModelStore声明的位置如下:

静态内部类

static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
    }
复制代码


ComponendActivity中有如下代码:

@Override
    @Nullable
    @SuppressWarnings("deprecation")
    public final Object onRetainNonConfigurationInstance() {
        // Maintain backward compatibility.
        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;
    }
复制代码


onRetainNonConfigurationInstance方法解释:

Retain all appropriate non-config state.  You can NOT override this yourself!  
复制代码

翻译:重新获取合适的配置状态,这个方法不可以被你自己重写


所以我们看完代码就清楚了,ViewModelStore在activity异常状态被销毁的时候会被保存,然后重新创建的时候会走ComponentActivity方法恢复配置。也就是说ViewModelStore被恢复成activity被异常销毁前的状态。当我们再调用ViewModeStore.get方法的时候获取的ViewModel还是原来的ViewModel而不会重新创建。



相关文章
|
15天前
|
安全 算法 网络协议
解析:HTTPS通过SSL/TLS证书加密的原理与逻辑
HTTPS通过SSL/TLS证书加密,结合对称与非对称加密及数字证书验证实现安全通信。首先,服务器发送含公钥的数字证书,客户端验证其合法性后生成随机数并用公钥加密发送给服务器,双方据此生成相同的对称密钥。后续通信使用对称加密确保高效性和安全性。同时,数字证书验证服务器身份,防止中间人攻击;哈希算法和数字签名确保数据完整性,防止篡改。整个流程保障了身份认证、数据加密和完整性保护。
|
8天前
|
机器学习/深度学习 数据可视化 PyTorch
深入解析图神经网络注意力机制:数学原理与可视化实现
本文深入解析了图神经网络(GNNs)中自注意力机制的内部运作原理,通过可视化和数学推导揭示其工作机制。文章采用“位置-转移图”概念框架,并使用NumPy实现代码示例,逐步拆解自注意力层的计算过程。文中详细展示了从节点特征矩阵、邻接矩阵到生成注意力权重的具体步骤,并通过四个类(GAL1至GAL4)模拟了整个计算流程。最终,结合实际PyTorch Geometric库中的代码,对比分析了核心逻辑,为理解GNN自注意力机制提供了清晰的学习路径。
156 7
深入解析图神经网络注意力机制:数学原理与可视化实现
|
9天前
|
机器学习/深度学习 缓存 自然语言处理
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
Tiktokenizer 是一款现代分词工具,旨在高效、智能地将文本转换为机器可处理的离散单元(token)。它不仅超越了传统的空格分割和正则表达式匹配方法,还结合了上下文感知能力,适应复杂语言结构。Tiktokenizer 的核心特性包括自适应 token 分割、高效编码能力和出色的可扩展性,使其适用于从聊天机器人到大规模文本分析等多种应用场景。通过模块化设计,Tiktokenizer 确保了代码的可重用性和维护性,并在分词精度、处理效率和灵活性方面表现出色。此外,它支持多语言处理、表情符号识别和领域特定文本处理,能够应对各种复杂的文本输入需求。
46 6
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
|
1月前
|
机器学习/深度学习 算法 数据挖掘
解析静态代理IP改善游戏体验的原理
静态代理IP通过提高网络稳定性和降低延迟,优化游戏体验。具体表现在加快游戏网络速度、实时玩家数据分析、优化游戏设计、简化更新流程、维护网络稳定性、提高连接可靠性、支持地区特性及提升访问速度等方面,确保更流畅、高效的游戏体验。
76 22
解析静态代理IP改善游戏体验的原理
|
1月前
|
编解码 缓存 Prometheus
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
本期内容为「ximagine」频道《显示器测试流程》的规范及标准,我们主要使用Calman、DisplayCAL、i1Profiler等软件及CA410、Spyder X、i1Pro 2等设备,是我们目前制作内容数据的重要来源,我们深知所做的仍是比较表面的活儿,和工程师、科研人员相比有着不小的差距,测试并不复杂,但是相当繁琐,收集整理测试无不花费大量时间精力,内容不完善或者有错误的地方,希望大佬指出我们好改进!
97 16
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
|
19天前
|
Java 数据库 开发者
详细介绍SpringBoot启动流程及配置类解析原理
通过对 Spring Boot 启动流程及配置类解析原理的深入分析,我们可以看到 Spring Boot 在启动时的灵活性和可扩展性。理解这些机制不仅有助于开发者更好地使用 Spring Boot 进行应用开发,还能够在面对问题时,迅速定位和解决问题。希望本文能为您在 Spring Boot 开发过程中提供有效的指导和帮助。
68 12
|
16天前
|
开发框架 监控 JavaScript
解锁鸿蒙装饰器:应用、原理与优势全解析
ArkTS提供了多维度的状态管理机制。在UI开发框架中,与UI相关联的数据可以在组件内使用,也可以在不同组件层级间传递,比如父子组件之间、爷孙组件之间,还可以在应用全局范围内传递或跨设备传递。
35 2
|
27天前
|
JavaScript 搜索推荐 Android开发
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
65 8
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
|
1月前
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
178 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
1月前
|
Dart 前端开发 Android开发
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
55 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex

热门文章

最新文章

  • 1
    Android历史版本与APK文件结构
  • 2
    【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
  • 3
    【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
  • 4
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
  • 5
    【03】微信支付商户申请下户到配置完整流程-微信开放平台创建APP应用-填写上传基础资料-生成安卓证书-获取Apk签名-申请+配置完整流程-优雅草卓伊凡
  • 6
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
  • 7
    Cellebrite UFED 4PC 7.71 (Windows) - Android 和 iOS 移动设备取证软件
  • 8
    escrcpy:【技术党必看】Android开发,Escrcpy 让你无线投屏新体验!图形界面掌控 Android,30-120fps 超流畅!🔥
  • 9
    即时通讯安全篇(一):正确地理解和使用Android端加密算法
  • 10
    Android实战经验之Kotlin中快速实现MVI架构
  • 推荐镜像

    更多