Android Jatpack--ViewModel

news2024/11/24 20:46:15

1.ViewModel

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

ViewModel出现的背景:

①职责分离

Android开发中,在页面较为简单的情况下,通常会将UI交互、数据获取等相关的业务逻辑全部写在页面中。但是在页面功能复杂的情况下,这样做就是不合适的。因为这样不符合“单一功能原则”。页面只应该负责处理用户与UI控件的交互,应该将UI与数据相关的业务逻辑隔离开。

为了能够更好地将职能划分,Android提供了ViewModel类,专门用于存放应用程序页面所需的数据。

②数据保留不丢失

在Android中如果Activity、Fragment销毁或者重建,存储在其中的数据会丢失。对于简单的数据,Activity可以使用onSaveInstanceState()方法来从onCreate()中恢复数据,但这个方法只适合可以序列化再反序列化的少量数据,而不适合较大的数据。

③防止异步操作时内存泄露

UI界面经常需要异步操作,比如网络请求等,当界面销毁时往往需要手动维护异步取消的动作,这样显得特别繁琐,并且把所有代码都写在界面中,会变得特别臃肿。

于是就需要将视图数据与界面分离,让层次清晰且高效。ViewModel作为视图数据和界面的桥梁,用来存储与UI相关的数据,它通过lifecycle感知的方式存储和管理UI相关数据。它可以维护自己的生命周期,不需要手动操作,这无疑大大降低开发难度。

 

ViewModel的生命周期:

be68ea840e704c3c90bde8fe1c56db09.png

这张图是在没有任何设置的情况下,旋转屏幕时Activity的生命周期变化和ViewModel的生命周期。可以看到Activity重建的时候,ViewModel中的数据是不会被清理的。即ViewModel类使得数据在配置更改(如屏幕旋转)时保活。

从这张图可以看出:

①ViewModel的生命周期比创建它的Activity、Fragment的生命周期都要长,即ViewModel中的数据会一直存活在Activity/Fragment中。因此ViewModel不能持有Context的对象,否则会出现内存泄露。

也就是说ViewModel一直保留在内存中,直到它的作用域永久消失:在activity的情况下,当它finishes时;而在fragment的情况下,当它被detached时。

②Activity在生命周期中可能会触发多次onCreate(),而ViewModel只会在第一次onCreate()时创建,然后直到最后Activity销毁。

 

ViewModel的应用:

屏幕旋转后,使用户操作数据仍然存在。

 

2.ViewModel的使用

①写一个类继承自ViewModel

class MyViewModel : ViewModel() {

    private var number:Int = 0

    fun getNumber(): Int {

        return number

    }

    fun setNumber() {

        number++

    }

    override fun onCleared() {

        super.onCleared()

        //viewModel销毁时调用,可以做一些释放资源的操作,防止内存泄露

    }

}

②使用自定义的ViewModel

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)

        val myViewModel = ViewModelProvider( this).get(MyViewModel::class.java)

        btn_add.setOnClickListener {

            myViewModel.setNumber()

            tv_text.setText("" + myViewModel.getNumber())

        }

    }

}

点击Button可以使TextView上的数字一直增加,而且旋转屏幕时数据也会保持,不会丢失(如果不使用ViewModel,旋转屏幕时TextView上的数据会变为0)。

ViewModel是一个抽象类,只有一个onCleared()方法。当ViewModel不再被需要,即与之相关的Activity/Fragment被销毁时,该方法会被系统调用。因此可以在该方法中执行一些资源释放的相关操作,防止造成内存泄露。注意: 当屏幕旋转而导致的Activity重建,并不会调用该方法。

注意:

①如果Activity重新创建,它接收的ViewModel实例与第一个Activity创建的实例相同。

②当Activity完成(不能简单理解为destroy)时,框架会调用ViewModel的onCleared()方法,以便它可以清理资源。

③ViewModel的生命周期比视图的生命周期长,所以ViewModel绝不能持有视图、Lifecycle或Activity的上下文等引用,否则就会造成内存泄漏。

 

3.源码分析

①ViewModel的实例缓存在哪里

ViewModel的创建方法是

val myViewModel = ViewModelProvider( this).get(MyViewModel::class.java)

创建ViewModelProvider对象并传入this参数,然后通过ViewModelProvider的get方法,传入MyViewModel的class类型,就得到了 myViewModel实例。

看一下ViewModelProvider的构造方法:

public ViewModelProvider( ViewModelStoreOwner owner) {

    // 获取owner对象的ViewModelStore对象

    this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory() : NewInstanceFactory.getInstance());

}

ViewModelProvider构造方法的参数类型是 ViewModelStoreOwner。而使用时传入的是this,这是因为Activity实现了ViewModelStoreOwner接口。

public class ComponentActivity extends androidx.core.app.ComponentActivity implements ViewModelStoreOwner ... {

    private ViewModelStore mViewModelStore;

    // 重写了ViewModelStoreOwner接口的唯一的方法getViewModelStore()

    @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.");

        }

        ensureViewModelStore();

        return mViewModelStore;

    }

}

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

public ViewModelProvider(ViewModelStore store, Factory factory) {

    mFactory = factory;

    // 缓存ViewModelStore对象

    mViewModelStore = store;

}

接着看ViewModelProvider#get方法做了什么。

@MainThread

public <T extends ViewModel> T get( 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);

}

获取ViewModel的CanonicalName, 然后调用了另一个get方法。

@MainThread

public <T extends ViewModel> T get(String key, Class<T> modelClass) {

     //从mViewModelStore缓存中尝试获取

    ViewModel viewModel = mViewModelStore.get(key);

    // 命中缓存

    if (modelClass.isInstance(viewModel)) {

        if (mFactory instanceof OnRequeryFactory) {

            ((OnRequeryFactory) mFactory).onRequery(viewModel);

        }

        // 返回缓存的 ViewModel 对象

        return (T) viewModel;

    }

    // 使用工厂模式创建 ViewModel 实例

    if (mFactory instanceof KeyedFactory) {

        viewModel = ((KeyedFactory) mFactory).create(key, modelClass);

    } else {

        viewModel = mFactory.create( modelClass);

    }

    // 将创建的ViewModel实例放进mViewModelStore缓存中

    mViewModelStore.put(key, viewModel);

    // 返回新创建的 ViewModel 实例

    return (T) viewModel;

}

通过ViewModelProvider的构造方法可以知道mViewModelStore其实是Activity里的mViewModelStore对象,它在 ComponentActivity中被声明。 看到了put方法,不难猜它内部用了Map结构。

public class ViewModelStore {

   //内部有一个 HashMap

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {

        ViewModel oldViewModel = mMap.put(key, viewModel);

        if (oldViewModel != null) {

            oldViewModel.onCleared();

        }

    }

 // 通过key获取ViewModel对象

    final ViewModel get(String key) {

        return mMap.get(key);

    }

    Set<String> keys() {

        return new HashSet<>(mMap.keySet());

    }

    public final void clear() {

        for (ViewModel vm : mMap.values()) {

            vm.clear();

        }

        mMap.clear();

    }

}

到这里,正常情况下ViewModel的创建流程看完了,简单总结:ViewModel对象存在于ComponentActivity的mViewModelStore对象中。

②为什么Activity旋转屏幕后ViewModel可以恢复数据

在ViewModelProvider的构造方法中,获取ViewModelStore对象时,实际调用了 MainActivity#getViewModelStore(),而 getViewModelStore()实现在MainActivity的父类 ComponentActivity中。

 ComponentActivity.java:

@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.");

    }

    ensureViewModelStore();

    return mViewModelStore;

}

在返回mViewModelStore对象之前调用了 ensureViewModelStore()方法。

void ensureViewModelStore() {

    if (mViewModelStore == null) {

        NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();

       if (nc != null) {

            // Restore the ViewModelStore from NonConfigurationInstances

            mViewModelStore = nc.viewModelStore;

        }

        if (mViewModelStore == null) {

            mViewModelStore = new ViewModelStore();

        }

    }

}

当mViewModelStore == null调用了getLastNonConfigurationInstance()获取 NonConfigurationInstances对象nc,当nc != null时将mViewModelStore赋值为 nc.viewModelStore,最终viewModelStore == null时,才会创建ViewModelStore实例。

不难发现,之前创建的viewModelStore对象被缓存在NonConfigurationInstances中。

 // 它是 ComponentActivity 的静态内部类

static final class NonConfigurationInstances {

    Object custom;

    // 果然在这儿

    ViewModelStore viewModelStore;

}

NonConfigurationInstances对象是通过 getLastNonConfigurationInstance()来获取的。

public Object getLastNonConfigurationInstance() {

    return mLastNonConfigurationInstances != null ?mLastNonConfigurationInstances.activity : null;

}

注意:

①onRetainNonConfigurationInstance方法和getLastNonConfigurationInstance是成对出现的,跟onSaveInstanceState机制类似,只不过它是仅用作处理配置更改的优化。

②返回的是onRetainNonConfigurationInstance 返回的对象。

看看onRetainNonConfigurationInstance 方法

//保留所有适当的非配置状态

@Override

public final Object onRetainNonConfigurationInstance() {

    Object custom = onRetainCustomNonConfigurationInstance();

    ViewModelStore viewModelStore = mViewModelStore;

    // 若 viewModelStore 为空,则尝试从 getLastNonConfigurationInstance() 中获取

    if (viewModelStore == null) {

        NonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance();

        if (nc != null) {

            viewModelStore = nc.viewModelStore;

        }

    }

   //依然为空,说明没有需要缓存的,则返回null

    if (viewModelStore == null && custom == null) {

        return null;

    } 

    // 创建 NonConfigurationInstances 对象,并赋值viewModelStore

    NonConfigurationInstances nci = new NonConfigurationInstances();

    nci.custom = custom;

    nci.viewModelStore = viewModelStore;

    return nci;

}

到这儿大概明白了,Activity在因配置更改而销毁重建过程中会先调用onRetainNonConfigurationInstance保存viewModelStore实例。 在重建后可以通过getLastNonConfigurationInstance方法获取之前的viewModelStore实例。因此Activity旋转屏幕后ViewModel可以恢复数据。

③什么时候ViewModel#onCleared()会被调用

public abstract class ViewModel {

    protected void onCleared() {

    }

    @MainThread

    final void clear() {

        mCleared = true;

        if (mBagOfTags != null) {

            synchronized (mBagOfTags) {

                for (Object value : mBagOfTags.values()) {

                    closeWithRuntimeException( value);

                }

            }

        }

        onCleared();

    }

}

onCleared()方法被clear()调用了。 刚才看 ViewModelStore源码时好像是调用了clear() ,回顾一下:

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {

        ViewModel oldViewModel = mMap.put(key, viewModel);

        if (oldViewModel != null) {

            oldViewModel.onCleared();

        }

    }

    final ViewModel get(String key) {

        return mMap.get(key);

    }

    Set<String> keys() {

        return new HashSet<>(mMap.keySet());

    }

    public final void clear() {

        for (ViewModel vm : mMap.values()) {

            vm.clear();

        }

        mMap.clear();

    }

}

在ViewModelStore的clear()中,遍历mMap并调用ViewModel对象的clear(),再看ViewModelStore的clear()什么时候被调用的:

// ComponentActivity的构造方法

public ComponentActivity() {

    ... 

    getLifecycle().addObserver(new LifecycleEventObserver() {

        @Override

        public void onStateChanged( LifecycleOwner source,Lifecycle.Event event) {

            if (event == Lifecycle.Event.ON_DESTROY) {

                mContextAwareHelper.clearAvailabl eContext();

                if (!isChangingConfigurations()) {

                    getViewModelStore().clear();

                }

            }

        }

    });

    ...

}

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

 // Activity#isChangingConfigurations()

public boolean isChangingConfigurations() {

    return mChangingConfigurations;

}

isChangingConfigurations用来检测当前的Activity是否因为Configuration的改变被销毁了, 配置改变返回true,非配置改变返回false。

总结:在activity销毁时,判断如果是非配置改变导致的销毁,getViewModelStore().clear()才会被调用。

 

4.ViewModel使用注意

①Fragment间共享数据

因为ViewModel只会在Activity存活时会创建一次,因此在同一个Activity中可以在多个Fragment中共享ViewModel中数据。

public class FragmentA extends Fragment{

    ViewModelProviders.of(getActivity()).get( MyViewModel.class).getDatas().observe(this, new Observer<User>() {

        @Override

        public void onChanged(User user) {

            //获取Activity中数据变化

       }

    });

}

public class FragmentB extends Fragment{

    ViewModelProviders.of(getActivity()).get( MyViewModel.class).getDatas().observe(this, new Observer<User>() {

        @Override

        public void onChanged(User user) {

        }

    });

}

//在Activity中更新数据

ViewModelProviders.of(this).get(MyViewModel.class). updateUser();

②使用ViewModel的时候,要注意ViewModel不能够持有View、Lifecycle、Acitivity引用,而且不能够包含任何包含前面内容的类。因为ViewModel的生命周期比它们长,这样很有可能会造成内存泄漏。

③ViewModel中使用Context

如果ViewModel需要Applicaiton的Context(为了获取系统服务),可以使用AndroidViewModel。

普通的ViewModel生命周期都很短,随着Activity 销毁而销毁。如果要创建一个长生命周期的ViewModel可以使用AndroidViewModel。

AndroidViewModel 持有了一个Application,所以它的生命周期会很长。具体使用如下:

class MyViewModel(application: Application) : AndroidViewModel(application) {

    override fun onCleared() {

        super.onCleared()

        //viewModel销毁时调用,可以做一些释放资源的操作

    }

}

可以在AndroidViewModel中存储一些全局数据。

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/585072.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【社区分享 - 低代码篇】浪潮低代码引擎UBML和低代码平台inBuilder

【社区分享 - 低代码篇】浪潮低代码引擎UBML和低代码平台inBuilder 文章目录 【社区分享 - 低代码篇】浪潮低代码引擎UBML和低代码平台inBuilder1、inBuilder低代码平台介绍2、inBuilder低代码平台操作指导 1、inBuilder低代码平台介绍 1&#xff09;首先介绍了低代码平台相关背…

分布式应用之zookeeper集群+消息队列Kafka

1.Zookeeper集群的相关知识 1.1 zookeeper的概念 ZooKeeper是一个分布式的&#xff0c;开放源码的分布式应用程序协调服务&#xff0c;是Google的Chubby一个开源的实现&#xff0c;是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件&#xff0c;提供的功能…

00后卷王自述,我真的很卷吗?

前段时间我去面试了一个软件测试公司&#xff0c;成功拿到了offer&#xff0c;薪资也从10k涨到了18k&#xff0c;对于工作都还没两年的我来说&#xff0c;还是比较满意的&#xff0c;毕竟有些工作了3到4年的可能还没有我的高。 在公司一段时间后大家都说我是卷王&#xff0c;其…

CCP4i2之蛋白结构自动构建:Autobuild protein

在解析x射线蛋白晶体结构过程中&#xff0c;最常用的方法就是分子置换&#xff0c;即进行molecular replacement&#xff08;MR&#xff09;时&#xff0c;输入合适的同源蛋白作为model&#xff0c;以及目标蛋白的mtz和sequence&#xff0c;来完成相位解析的过程。 相位求解成…

TDengine 基于SpringBoot 框架和Druid 连接池的TDengine Demo示例

一、前文 TDengine 入门教程——导读 二、JDBC Demo下载 TDengine 官方提供的示例程序源码位于TDengine / TDengine 的 TDengine/examples/JDBC下: JDBCDemo&#xff1a;JDBC 最简单的demo。connectionPools&#xff1a;HikariCP, Druid, dbcp, c3p0 等连接池中使用 taos-jdb…

【刷题之路Ⅱ】迷宫问题升级版——找最短路径

【刷题之路Ⅱ】迷宫问题升级版——找最短路径 一、题目描述二、解题1、方法1——暴力递归更新栈1.1、思路分析1.2、先将栈实现以下1.3、代码实现 一、题目描述 原题连接&#xff1a; 地下迷宫 题目描述&#xff1a; 小青蛙有一天不小心落入了一个地下迷宫,小青蛙希望用自己仅…

C语言深度解析--操作符

目录 操作符 1.算数操作符 2.移位操作符 左移操作符<<&#xff1a; 右移操作符>>&#xff1a; 3.位操作符 按位与&&#xff1a; 按位或 | &#xff1a; 按位异或 ^ &#xff1a; 4.赋值操作符 5.单目操作符 6.关系操作符 7.逻辑操作符 8.条件操作…

这所985复试竟可直接加50分,若复试有科研经历!

本期为大家整理热门院校-“大连理工大学”的择校分析&#xff0c;这个择校分析专题会为大家结合&#xff1a;初试复试占比、复试录取规则&#xff08;是否公平&#xff09;、往年录取录取名单、招生人数、分数线、专业课难度等进行分析。希望能够帮到大家! –所有数据来源于研…

vue:实现简单的拖拽功能

背景 平常做业务很容易遇到拖拽功能&#xff0c;没做之前总觉得会很复杂&#xff0c;今天来看一下到底是怎么实现的。 拖拽API 这是 HTML5 新增的 API&#xff0c;当给元素设置 draggable"true" 的时候&#xff0c;这个元素就可以拖拽了。 <div draggable&quo…

JavaScript全解析——Express框架介绍与入门

本文为千锋资深前端教学老师带来的【JavaScript全解析】系列&#xff0c;文章内含丰富的代码案例及配图&#xff0c;从0到1讲解JavaScript相关知识点&#xff0c;致力于教会每一个人学会JS&#xff01; 文末有本文重点总结&#xff0c;可以收藏慢慢看~ 更多技术类内容&#xf…

Linux 安装nodejs、npm、yarn、nrm(超实用)

前言&#xff1a;初衷想要本地通过dockerfile文件直接把项目打包到linux服务器&#xff0c;不用再本地加载再上传等&#xff0c;后续再贴上配置文件 一、什么是nodejs 来自官网的介绍&#xff0c;Node.js 是一个开源的跨平台 JavaScript 运行时环境。它几乎是任何类型项目的流…

AI加持,Fabric让Power BI生态更强大

在Microsoft Build 2023上微软正式推出了Microsoft Faric预览版&#xff0c;它将Power BI、Azure Synapse、Azure Data Factory的优点整合到了一个统一的SasS服务中。数据工程师、数仓工程师、数据科学家、数据分析师和业务用户可以在Fabric中无缝协作&#xff08;微软这是要卷…

MinIO:基于Go实现的高性能、兼容S3协议的对象存储

High Performance Object Storage for AI 译文&#xff1a;MinIO是一个基于Go实现的高性能、兼容S3协议的对象存储 文档 项目地址&#xff1a;https://github.com/minio/minio官网地址&#xff1a;https://min.io/文档地址&#xff1a;https://docs.min.io/Software Developme…

PyTorch-优化器以及网络模型的修改

目的&#xff1a;优化器可以将神经网络中的参数根据损失函数和反向传播来进行优化&#xff0c;以得到最佳的参数值&#xff0c;让模型预测的更准确。 1. SGD import torch import torchvision from torch import nn from torch.nn import Sequential, Conv2d, MaxPool2d, Flat…

Spring Security 笔记

在Spring Security 5.7.0-M2&#xff0c;我们弃用了 WebSecurityConfigurerAdapter &#xff0c;因为我们鼓励用户转向使用基于组件的安全配置。 为了帮助大家熟悉这种新的配置风格&#xff0c;我们编制了一份常见用例表和推荐的新写法。 配置HttpSecurity Configuration pu…

重磅发布!面向装备制造业服务化转型白皮书

《面向装备制造业服务化转型白皮书》 关于白皮书 《面向装备制造业服务化转型白皮书》通过调研160余家装备制造企业的服务化路径及模式&#xff0c;研讨支持企业开展服务型制造的系统化方案&#xff0c;希望为装备制造业服务化转型&#xff0c;探索切实有效的路径以供参考。 …

Web 自动化测试案例——关闭某视频网站弹出广告以及打开登录框输入内容

文章目录 &#x1f4cb;前言&#x1f3af;自动化测试&#x1f9e9;环境的搭建 &#x1f3af;案例介绍&#x1f4dd;最后 &#x1f4cb;前言 人生苦短&#xff0c;我用Python。许久没写博客了&#xff0c;今天又是久违的参与话题的讨论&#xff0c;话题的内容是&#xff1a;如何…

4.文件系统

组成 Linux&#xff1a;一切皆文件 索引节点&#xff08;I-node&#xff09; I-node&#xff08;Index Node&#xff09;&#xff1a;文件系统的内部数据结构&#xff0c;用于管理文件的元数据和数据块。 文件的元数据&#xff1a;包括文件的权限、拥有者、大小、时间戳、索引…

VM增加磁盘并挂载到根目录

1、虚拟机增加磁盘 首先要关闭虚拟机&#xff0c;否则增加按钮不可见。 9 vm添加磁盘完毕。 2、登录虚拟机挂盘 1、lsblk查看硬盘挂载情况&#xff0c;sdb为新挂载的磁盘。 [rootlocalhost ~]# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda …

通过python封装接口采集1688店铺所有商品数据接口,1688店铺所有商品接口,1688API接口

采集1688店铺所有商品数据需要进行以下步骤&#xff1a; 获取店铺ID 要获取店铺ID&#xff0c;您可以通过访问店铺首页来获取&#xff0c;例如&#xff1a;https://1688455341.1688.com/ 店铺ID就是链接中的“1688455341”。 获取店铺所有商品列表页 通过向1688店铺的搜索…