Android ViewModel实现和原理

ViewModel实现和原理

  • 前言
  • 1. 使用
    • 1.1 gradle准备
    • 1.2 模拟场景
    • 1.3. LiveData和ViewModel
    • 1.4 更新数据
  • 2. 原理与源码解读
    • 2.1 添加观察者
    • 2.2 setValue
    • 2.3 post
  • 参考资料

前言

ViewModel的主要基于观察者的设计模式,他主要分为两个部分:

  1. 提供者Provider:在我们这里就是数据的提供者,当实体的数据改变的时候,会自动通知所有观察者,观察者收到通知后就可以做对应的数据。
  2. 观察者Observer:观察者注册提供程序,当提供者每次发送通知的时候,观察者就会做对应的处理。

其最终实现出来的效果就是,在代码中,一旦我们注册的实体对象里面的数据改变之后,对应的UI就会自动的进行更新,这样数据更新的代码我们只需要在观察者里面写一套,不需要反复写多套了。

提供者和观察者是一对多的关系,也就是一个提供者可以被很多个观察者注册

1. 使用

1.1 gradle准备

在用上ViewModel之前,需要在项目的build.gradle中加上如下内容,开启DataBinding。

为了写ui方便我把ViewBinding也加上了,他不是必须的,但是我的demo代码里面会有ViewBinding相关内容。

android {
	buildFeatures {
		dataBinding = true
        viewBinding = true
    }
}

1.2 模拟场景

我们模拟一个简单的场景,页面里面就三个TextView,两个按钮,我们的目标就是用ViewModel来完成点了按钮之后他的Text就动态修改的功能。

实体是我随便设置的

class Student(var name: String = "",
              var age: Int = 0,
              var id: String = "")

页面长这样,三个TextView和两个按钮
在这里插入图片描述

1.3. LiveData和ViewModel

任何一个ViewModel的动态更新都需要围绕LiveData和ViewModel这两个类进行

LiveData:将实体动态化,可以被具有生命周期的对象观察到,只有这个目标对象的生命周期处于活动中,才会收到通知。
ViewModel:通信类,LiveData通过ViewModel来下发通知。

所以我们的代码最后写成这样:
ViewModel作为容器类,里面的成员变量是一个Student的LiveData类(一般都用MutableLiveData,如果有特殊需求也可以自己写)。
如果实际开发的时候有多个这样需要通信的实体,都丢到自定义的ViewModel类里面

class StudentViewModel : ViewModel() {
    private var student = MutableLiveData<Student>()

    fun getStudent(): MutableLiveData<Student> {
        return student
    }
}

注册通知的方式也很简单,先通过ViewModelProvider生成一个ViewModel的实体,然后将对应的实体进行观察者的注册即可。

lass ViewModelActivity : AppCompatActivity() {

    private lateinit var binding: ActivityViewModelBinding
    private lateinit var viewModel: StudentViewModel

    @SuppressLint("SetTextI18n")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityViewModelBinding.inflate(layoutInflater)
        setContentView(binding.root)

		// 创建ViewModel实体的固定写法
        viewModel = ViewModelProvider(this).get(StudentViewModel::class.java)
        // 注册Student对象,这样Student这个对象一旦改变,就会自动调用这里面的方法
        viewModel.getStudent().observe(this) {
            binding.vmName.text = "姓名:${it.name}"
            binding.vmAge.text = "年龄:${it.age}"
            binding.vmId.text = "id:${it.id}"
        }
    }
}

1.4 更新数据

更新数据有两种方式:
setValue:整个对象改变之后,他会自动的通知更新。
post:只改变目标对象里面的一两个成员变量时,通过Post进行更新。

class ViewModelActivity : AppCompatActivity() {

    private lateinit var binding: ActivityViewModelBinding
    private lateinit var viewModel: StudentViewModel

    @SuppressLint("SetTextI18n")
    override fun onCreate(savedInstanceState: Bundle?) {
    	//上面有的代码就不贴了

        binding.vmBtn1.setOnClickListener {
            val student = Student(name = "张三", age = 16, id = "001")
            viewModel.setStudent(student)
        }

        binding.vmBtn2.setOnClickListener {
            viewModel.setName("李四")
        }
    }
}
class StudentViewModel : ViewModel() {
    private var student = MutableLiveData<Student>()

    //通过调用setValue进行更新,代码写成这样是因为kotlin语法省略
    //注意,setValue这个方法必须要在主线程进行
    fun setStudent(student: Student) {
        this.student.value = student
    }

	// 只更新局部成员变量,通过postValue进行更新
    fun setName(name: String) {
        this.student.value?.name = name
        student.postValue(student.value)
    }

    fun getStudent(): MutableLiveData<Student> {
        return student
    }
}

2. 原理与源码解读

这里先把ViewModel在代码层面上执行的原理先讲了,然后我们再通过源码看一下他具体是怎么实现的:

  1. 添加观察者就是在LiveData里面弄了一个HashMap,key是观察者的实体,value是观察者和生命周期对象的绑定类。
    等于是说我们实际上创建的这个观察者,即监听LiveData这个可以变动的实体,也监听了页面本身的生命周期。
  2. setValue就是当key更新了之后,就遍历这个HashMap的keySet,将生命周期状态为运行中的key,调用他们的回调。
  3. post就是通过handler来进行下方通知,所以post可以在子线程跑,其他的部分和setValue一样。

从他的这个原理我们也可以看到观察者模式这种设计模式的一般代码思路:

  1. 添加观察者就是找个集合,List,Set,HashMap等,把观察者对象装进去,观察者对象在注册的时候一般都会传入一个回调CallBack。
  2. 下发通知就是触发某个通知方法之后,遍历集合,然后挨个调用他们注册时传入的回调。

2.1 添加观察者

为了方便我们看源码,我先把当时我们调用observe的这行代码还原成Java的样子

	viewModel.getStudent().observe((LifecycleOwner)this, (Observer)(new Observer() {
         public void onChanged(Object var1) {
            this.onChanged((Student)var1);
         }

         public final void onChanged(Student it) {
            TextView var10000 = ViewModelActivity.access$getBinding$p(ViewModelActivity.this).vmName;
            Intrinsics.checkNotNullExpressionValue(var10000, "binding.vmName");
            var10000.setText((CharSequence)("姓名:" + it.getName()));
            var10000 = ViewModelActivity.access$getBinding$p(ViewModelActivity.this).vmAge;
            Intrinsics.checkNotNullExpressionValue(var10000, "binding.vmAge");
            var10000.setText((CharSequence)("年龄:" + it.getAge()));
            var10000 = ViewModelActivity.access$getBinding$p(ViewModelActivity.this).vmId;
            Intrinsics.checkNotNullExpressionValue(var10000, "binding.vmId");
            var10000.setText((CharSequence)("id:" + it.getId()));
         }
      }));

我们实际上是new了一个Observer对象,然后将这个对象作为入参传了进来。这个东西看着其实和我们的onClickListener之类的很像,其实就是个回调的监听。

public abstract class LiveData<T> {

	// 其实就是一个map,我们不用去关心他里面的具体代码原理,知道他是一个map,有和hashmap同款的功能即可
	private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers =
            new SafeIterableMap<>();

	public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
		// 主线程判断,不是主线程就抛出异常
        assertMainThread("observe");
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // 生命周期已经死亡的就无视
            return;
        }
		// 创建了另一个观察者类,这个观察者类是负责观察页面生命周期的
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        // key为生命周期实体,value为观察者和页面生命周期的观察者类
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        if (existing != null && !existing.isAttachedTo(owner)) {
        	// 如果这个监听已经被注册过了,就会抛出异常
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        if (existing != null) {
            return;
        }
        // 将observer本身对页面的生命周期做监听
        owner.getLifecycle().addObserver(wrapper);
    }
}

mObservers就是我们所说的那个HashMap,key是观察者,也就是我们的new的那个observer类,value则是一个LifecycleBoundObserver对象,他是负责监听页面的生命周期的。

我们就通过这种方式即监听了LiveData本身,又监听了页面的生命周期。

2.2 setValue

接下来我们看看他是怎么做到动态更新的。从setValue这个方法开始,为了方便看,这里省略一些和原理无关的逻辑代码。

public abstract class LiveData<T> {
	@MainThread
    protected void setValue(T value) {
    	// setValue方法也必须在主线程执行
        assertMainThread("setValue");
        dispatchingValue(null);
    }

	void dispatchingValue(@Nullable ObserverWrapper initiator) {
        do {
            if (initiator != null) {
              //省略代码,我们入参是null 
            } else {
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    // 重点看这行,这里就是遍历所有的监听执行
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        
    }

	private void considerNotify(ObserverWrapper observer) {
		// 页面生命周期判断,不符合就return了
		if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        // 数据的版本号判断,版本号不符合就return了
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        // 调用onChanged,onChanged就是我们的回调方法
        observer.mObserver.onChanged((T) mData);
	}
}

这样一看逻辑就很清晰了,每次我们调用setValue,他就会跑一个for循环,把所有的observer都做一个检测,符合要求的就调用最后的onChanged,不符合的就不调用。

2.3 post

我们最后在过一下post这条线的更新逻辑,这边本质上逻辑也是一样的

public abstract class LiveData<T> {

	volatile Object mPendingData = NOT_SET;
	
	private final Runnable mPostValueRunnable = new Runnable() {
        @SuppressWarnings("unchecked")
        @Override
        public void run() {
            Object newValue;
            synchronized (mDataLock) {
                newValue = mPendingData;
                mPendingData = NOT_SET;
            }
            // 本质上还是调用setValue,回到2.2了
            setValue((T) newValue);
        }
    };

	protected void postValue(T value) {
        boolean postTask;
        synchronized (mDataLock) {
            postTask = mPendingData == NOT_SET;
            mPendingData = value;
        }
        if (!postTask) {
            return;
        }
        // 无视掉同步锁的那些代码,本质就是跑了这一行,这个方法从名字上就知道是把一个东西post到主线程执行
        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
    }
}

代码一眼看到头,通过一个线程池将一个runnable推到主线程去处理,最后还是调用的setValue。

最后看一眼他这个线程池是怎么推到主线程的。

public class ArchTaskExecutor extends TaskExecutor {

	// TaskExecutor是个抽象类,实现是DefaultTaskExecutor
	private TaskExecutor mDelegate;

	private ArchTaskExecutor() {
        mDefaultTaskExecutor = new DefaultTaskExecutor();
        mDelegate = mDefaultTaskExecutor; 
    }
	
	@Override
    public void postToMainThread(@NonNull Runnable runnable) {
        mDelegate.postToMainThread(runnable);
    }
}

public class DefaultTaskExecutor extends TaskExecutor {
	
	private volatile Handler mMainHandler;
	
	public void postToMainThread(@NonNull Runnable runnable) {
        if (mMainHandler == null) {
            synchronized (mLock) {
                if (mMainHandler == null) {
                    mMainHandler = createAsync(Looper.getMainLooper());
                }
            }
        }
        //noinspection ConstantConditions
        mMainHandler.post(runnable);
    }
}

参考资料

https://developer.android.google.cn/topic/libraries/architecture/viewmodel/viewmodel-factories?hl=zh-cn

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RK3588/算能/Nvidia智能盒子:[AI智慧油站」,以安全为基,赋能精准经营

2021年9月&#xff0c;山东省应急管理厅印发了关于《全省危险化学品安全生产信息化建设与应用工作方案&#xff08;2021-2022 年&#xff09;》的通知&#xff0c;要求全省范围内加快推进危险化学品安全生产信息化、智能化建设与应用工作&#xff0c;建设完善全省危险化学品安全…

游戏服务器研究一:bigworld 开源代码的编译与运行

1. 前言 bigworld 已经开源了它的代码&#xff0c;而我对于大世界的 scale 很感兴趣&#xff0c;所以就尝试把代码跑起来研究。但是&#xff0c;整个过程比我原先预想的复杂得多。 虽然能找到一些官方的帮助文档&#xff0c;但这些文档要么过旧&#xff0c;要么过于详尽&…

0元体验苹果macOS系统,最简单的虚拟机部署macOS教程

前言 最近发现小伙伴热衷于在VMware上安装体验macOS系统&#xff0c;所以就有了今天的帖子。 正文开始 首先&#xff0c;鉴于小伙伴们热衷macOS&#xff0c;所以小白搜罗了一圈macOS系统&#xff0c;并开启了分享通道。 本次更新的系统版本是&#xff1a; macOS 10.13.6 ma…

B端业务需求分析的3大注意事项

通过深入分析业务需求&#xff0c;可以准确理解B端用户的具体需求&#xff0c;帮助项目团队设计出真正解决企业问题、提高工作效率的产品或服务。这减少了后期变更&#xff0c;节约了时间和资源。如果没有深入分析业务需求&#xff0c;产品或服务功能可能与实际业务需求脱节&am…

街道网格宣传稿件投稿我知道了好方法

作为街道信息宣传员,我的日常是将街道的每一项重要活动、每一份温暖故事编织成文字,传递给公众。这份工作既充满挑战又极具意义,但在最初,我却在这份看似简单的任务上屡屡受阻。那时,我的投稿方式单一且传统——依赖电子邮件,将稿件发送至各大媒体的投稿箱。我本以为,只要内容足…

【讲解下Pip换源】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

【Redis】java客户端(SpringData和jedis)

https://www.oz6.cn/articles/58 https://www.bilibili.com/video/BV1cr4y1671t/?p16 redis官网客户端介绍&#xff1a;https://redis.io/docs/latest/develop/connect/clients/ jedis maven引入依赖 <dependencies><!--引入Jedis依赖--><dependency><…

Jmeter 性能测试步骤是什么?

性能测试是软件开发过程中非常重要的一环。它可以帮助我们评估软件系统在不同负载下的性能表现&#xff0c;找出系统中的性能瓶颈&#xff0c;并提供改进方案。而JMeter作为一款功能强大且广泛使用的性能测试工具&#xff0c;可以帮助我们实现这一目标。 下面&#xff0c;我将…

算法课程笔记——线段树维护矩阵

算法课程笔记——线段树维护矩阵 2

Elasticsearch:简化数据流的数据生命周期管理

作者&#xff1a;来自 Elastic Andrei Dan 今天&#xff0c;我们将探索 Elasticsearch 针对数据流的新数据管理系统&#xff1a;数据流生命周期&#xff0c;从版本 8.14 开始提供。凭借其简单而强大的执行模型&#xff0c;数据流生命周期可让n 你专注于数据生命周期的业务相关方…

总结一下 C# 如何自定义特性 Attribute 并进行应用

前言 Attribute&#xff08;特性&#xff09;是一种用于为程序元素&#xff08;如类、方法、属性等&#xff09;提供元数据信息的方法。 特性是一种声明式的信息&#xff0c;附加到程序元素上&#xff0c;提供额外的数据用于描述和控制这些元素的行为。 在编译和运行时&…

项目准备和启动

1.什么是项目建议书&#xff1f; 2.项目建议书的内容 3.可行性分析方法 4.项目组织结构&#xff08;职能型 项目型 矩阵型&#xff09; 5.项目管理层决策层执行层之间的关系 6.软件项目的可行性分析包括哪几个方面&#xff1f;影响决策的关键因素又是什么&#xff1f; 软件项目…

Microsoft Visual C++ Redistributable 【安装包】【高速下载】

方法1、可以从官方下载&#xff0c;如下图 方法2 已经下载好并且已经整理好了2008--2022的所有版本点击下方链接即可高速下载 如果是win7-win8-win10-win11直接可以下载2015--2022版本&#xff0c;xp需要下载2015之前的 点击链接Microsoft Visual C Redistributable官方版本…

如何在不同的操作系统中查看路由器的IP地址?这里有详细步骤

如果你曾经需要访问路由器的设置页面来进行一些配置更改,你知道你需要路由器的IP地址才能访问。如果你忘记了这个IP地址是什么,下面是如何在几乎所有平台上找到它的。 为什么路由器的IP很有用 在网络世界中,默认网关是一个IP地址,当流量被发送到当前网络之外的目的地时,…

代理四川公司疑难商标办理商标异议复审办理

申请商标注册或者办理其他商标事宜&#xff0c;可以自行办理&#xff0c;也可以委托依法设立的商标代理机构办理。外国人或者外国企业在中国申请商标注册和办理其他商标事宜的&#xff0c;应当委托依法设立的商标代理机构办理&#xff0c;按照被代理人的委托办理商标注册申请或…

立创开源学习篇(一)

1.机壳地 外面包围的一圈是机壳地&#xff0c;和金属外壳相连与电路板的GND不相连&#xff1a;&#xff08;大疆很多产品有此设计&#xff09; 屏蔽和接地&#xff1a;通过在电路板周围打孔&#xff0c;并连接到机壳地&#xff0c;可以形成有效的电磁屏蔽层&#xff08;形成金…

主机与VMware虚拟机共享文件夹:解决虚拟机找不到共享文件夹问题,挂载文件权限问题

最近在倒腾创龙T113&#xff0c;跟着教程走遇到设置了共享文件夹&#xff0c;但是虚拟机找不到的问题。 原因&#xff1a;权限问题 解决方法&#xff1a; ①在虚拟机关机状态下&#xff0c;进入选项卡设置“共享文件” ②启动虚拟机&#xff0c;打开命令行 到系统根目录&#…

openwrt如何安装python

首先配置opkg源。 # 备份初始conf mv /etc/opkg.conf /etc/opkg.conf.bak # 新建配置 vim /etc/opkg.conf # 添加如下内容&#xff1a; dest root / dest ram /tmp lists_dir ext /var/opkg-lists option overlay_root /overlay # notice dest usb /mnt/sdb1/opkg arch all 100…

一个简单的信号发射电路的构建

在基本的信号发射电路中&#xff0c;线圈&#xff08;电感器&#xff09;和电阻的组合可以产生振荡信号&#xff0c;而天线&#xff08;通常通过线圈&#xff09;用于发射信号。 LC振荡电路&#xff1a; **线圈&#xff08;L1&#xff09;和电容器&#xff08;C&#xff09;**串…

图神经网络学习笔记

文章目录 一、图神经网络应用领域分析二、图基本模块定义三、邻接矩阵的定义四、GNN中常见任务五、消息传递计算方法六、多层GCN的作用七、GCN基本模型概述八、图卷积的基本计算方法九、邻接的矩阵的变换十、GCN变换原理解读 本笔记参考自b站up主小巴只爱学习的图神经网络教程 …