kotlin与MVVM结合使用总结(三)

1. MVVM 架构详细介绍及源码层面理解

整体架构

MVVM(Model - View - ViewModel)架构是为了解决视图和数据模型之间的耦合问题而设计的。它通过引入 ViewModel 作为中间层,实现了视图和数据的分离,提高了代码的可维护性和可测试性。

  • View(视图层):在 Android 中,View 主要关联 Activity、Fragment 以及 XML 布局文件等,负责呈现界面与响应用户交互。像 Activity 里设置布局、初始化视图元素,以及处理用户点击等操作都属于 View 范畴。例如在一个登录界面 Activity 里,布局文件定义了输入框、按钮等 UI 元素的样式与位置,Activity 则处理按钮点击事件,这些都在 View 层完成。
  • Model(数据模型层):负责数据的获取、存储与处理。比如从网络请求用户信息、将数据存储到本地数据库等。在电商应用中,从服务器获取商品列表数据,或是将用户的购物车信息保存到本地数据库,都由 Model 层执行。
  • ViewModel(视图模型层):作为连接 View 与 Model 的纽带,承担关键的界面显示逻辑处理任务。它从 Model 获取数据,并将其转换为适合 View 展示的形式。例如在新闻应用中,Model 获取到原始新闻数据列表,ViewModel 可对数据进行加工,如截取新闻摘要、处理图片链接等,让数据能更好地在 View 中展示。在数据更新时,ViewModel 通过 LiveData 等机制通知 View 进行相应更新。从源码层面看,ViewModel 借助 ViewModelProvider 来创建与管理。ViewModelProvider 内部运用 ViewModelStore 存储 ViewModel 实例,确保配置变更(如屏幕旋转)时,ViewModel 实例不会被销毁,维持数据的稳定性。
观察者模式在 MVVM 中的应用

在 MVVM 架构里,观察者模式发挥着核心作用。ViewModel 持有数据,以 LiveData 为例,View 作为观察者监听 LiveData 数据变化。LiveData 内部维护了观察者列表,当数据变更时,会调用 dispatchingValue 方法遍历观察者列表。在 considerNotify 方法中,判断观察者状态,若活跃则通过 observer.mObserver.onChanged((T) mData) 通知观察者,View 接收到通知后更新界面,实现了 View 与 ViewModel 低耦合通信,这在诸多面试真题里都有涉及,是理解 MVVM 架构的关键。ViewModel 是通过 ViewModelProvider 来创建和管理的。

2. LiveData 实例化方法及源码分析

实例化方法
  • MutableLiveDataMutableLiveData 是 LiveData 的子类,它公开了 setValue() 和 postValue() 方法,允许外部修改其持有的数据。
MutableLiveData<String> liveData = new MutableLiveData<>();

在源码中,MutableLiveData 只是简单地继承了 LiveData 并暴露了修改数据的方法。

public class MutableLiveData<T> extends LiveData<T> {@Overridepublic void postValue(T value) {super.postValue(value);}@Overridepublic void setValue(T value) {super.setValue(value);}
}
  • 使用 Transformations 类进行转换Transformations 类提供了一些静态方法,如 map() 和 switchMap(),可以对 LiveData 进行转换。
LiveData<Integer> source = new MutableLiveData<>();
LiveData<String> transformed = Transformations.map(source, input -> "Transformed: " + input);

map() 方法的源码实现如下:

public static <X, Y> LiveData<Y> map(LiveData<X> source,final Function<X, Y> mapFunction) {final MediatorLiveData<Y> result = new MediatorLiveData<>();result.addSource(source, new Observer<X>() {@Overridepublic void onChanged(@Nullable X x) {result.setValue(mapFunction.apply(x));}});return result;
}

3. LiveData 如何实现生命周期绑定问题

LiveData 生命周期绑定机制在面试中频繁被问到,其实现依赖于 Android 的 Lifecycle 组件。

         当调用 LiveData.observe() 方法时,会创建 LifecycleBoundObserver 对象,它实现了 LifecycleEventObserver 接口来监听 LifecycleOwner(如 Activity、Fragment)的生命周期变化。在 observe() 方法源码中,先检查 LifecycleOwner 状态,若已销毁则直接返回;否则创建 LifecycleBoundObserver 并添加到观察者列表,同时将其注册到 LifecycleOwner 的生命周期观察者中。

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);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;}owner.getLifecycle().addObserver(wrapper);
}

LifecycleBoundObserver 的 onStateChanged 方法会在 LifecycleOwner 生命周期状态变化时被调用。在此方法中,根据生命周期状态决定是否更新观察者。当状态变为 DESTROYED 时,从 LiveData 的观察者列表移除该观察者,防止内存泄漏;当状态为 STARTED 或 RESUMED 时,认为观察者活跃,可接收数据更新。

class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {@NonNullfinal LifecycleOwner mOwner;LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {super(observer);mOwner = owner;}@Overrideboolean shouldBeActive() {return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);}@Overridepublic void onStateChanged(@NonNull LifecycleOwner source,@NonNull Lifecycle.Event event) {if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {removeObserver(mObserver);return;}activeStateChanged(shouldBeActive());}@Overrideboolean isAttachedTo(LifecycleOwner owner) {return mOwner == owner;}@Overridevoid detachObserver() {mOwner.getLifecycle().removeObserver(this);}
}

这种机制确保 LiveData 仅在 LifecycleOwner 处于活跃状态(STARTED 或 RESUMED)时更新观察者,有效避免内存泄漏与空指针异常,是 LiveData 的重要特性。

4. LiveData 粘性事件的深入分析

粘性事件的概念

粘性事件是指当一个观察者注册到 LiveData 时,即使该 LiveData 在观察者注册之前已经有了更新,观察者仍然会接收到这些之前的更新。这是因为 LiveData 会记录最新的值,当有新的观察者注册时,会立即将最新的值发送给它。

源码层面分析

在 LiveData 的 observe() 方法中,当新的观察者注册时,会调用 dispatchingValue() 方法,该方法会检查观察者的状态,并将最新的值发送给它。

private void dispatchingValue(@Nullable ObserverWrapper initiator) {if (mDispatchingValue) {mDispatchInvalidated = true;return;}mDispatchingValue = true;do {mDispatchInvalidated = false;if (initiator != null) {considerNotify(initiator);initiator = null;} else {for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {considerNotify(iterator.next().getValue());if (mDispatchInvalidated) {break;}}}} while (mDispatchInvalidated);mDispatchingValue = false;
}private void considerNotify(ObserverWrapper observer) {if (!observer.mActive) {return;}if (!observer.shouldBeActive()) {observer.activeStateChanged(false);return;}if (observer.mLastVersion >= mVersion) {return;}observer.mLastVersion = mVersion;// 调用观察者的 onChanged 方法,发送最新的值observer.mObserver.onChanged((T) mData);
}

在 considerNotify() 方法中,会比较观察者的 mLastVersion 和 LiveData 的 mVersion,如果 mLastVersion 小于 mVersion,则会调用观察者的 onChanged() 方法,将最新的值发送给它。

解决粘性事件的方法

为了避免粘性事件的影响,可以考虑使用一些第三方库,如 SingleLiveEvent 或自定义 LiveData 实现。以下是一个简单的自定义 LiveData 实现,用于避免粘性事件:

import androidx.lifecycle.LiveData;import java.util.concurrent.atomic.AtomicBoolean;public class NonStickyLiveData<T> extends LiveData<T> {private final AtomicBoolean mPending = new AtomicBoolean(false);@Overridepublic void observeForever(@NonNull Observer<? super T> observer) {super.observeForever(new Observer<T>() {@Overridepublic void onChanged(T t) {if (mPending.compareAndSet(true, false)) {observer.onChanged(t);}}});}@Overridepublic void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {super.observe(owner, new Observer<T>() {@Overridepublic void onChanged(T t) {if (mPending.compareAndSet(true, false)) {observer.onChanged(t);}}});}@Overrideprotected void setValue(T value) {mPending.set(true);super.setValue(value);}@Overrideprotected void postValue(T value) {mPending.set(true);super.postValue(value);}
}

在这个自定义的 LiveData 中,使用 AtomicBoolean 来标记是否有新的值需要发送,只有当 mPending 为 true 时,才会调用观察者的 onChanged() 方法,从而避免了粘性事件的影响。

粘性事件总结

        在 LiveData 机制里,不活跃观察者(对应 LifecycleOwner 处于 STOPPED 或 PAUSED 状态)正常情况下不会接收数据更新事件。只有当观察者再次变为活跃状态时,LiveData 才会将最新数据发送给它。这是因为在 LifecycleBoundObserver 的 shouldBeActive 方法中,依据 LifecycleOwner 的当前生命周期状态判断观察者是否活跃,不活跃则不进行数据分发。

       然而,LiveData 存在粘性事件问题,这在面试中常被提及。粘性事件指新观察者注册时,即便 LiveData 之前已有更新,观察者仍会收到这些之前的更新数据。从源码层面分析,在 LiveData 的 observe() 方法中,新观察者注册后会调用 dispatchingValue() 方法。在 dispatchingValue() 内部的 considerNotify() 方法里,通过比较观察者的 mLastVersion 和 LiveData 的 mVersion 来决定是否通知观察者。若 mLastVersion 小于 mVersion,则调用观察者的 onChanged() 方法发送最新数据,导致粘性事件发生。

为解决粘性事件问题,常见方法如下:

  • 使用 SingleLiveEvent:自定义一个继承自 MutableLiveData 的类,重写相关方法确保事件只被消费一次。例如在一些开源项目中,SingleLiveEvent 类通过设置标志位,在 observe() 方法中判断标志位,仅在首次观察时触发数据更新,后续不再响应之前的粘性数据。
  • 使用 Event 包装类:将数据包装在 Event 类中,通过标记数据是否已被处理来避免重复触发。在观察者获取数据时,先检查标记位,若未处理则处理数据并设置标记位,防止重复处理粘性数据。
  • 使用 MediatorLiveDataMediatorLiveData 可监听其他 LiveData 变化,并在必要时过滤粘性事件。通过添加源 LiveData 的观察者,在数据变化时进行相应处理,如更新自身数据后移除源 LiveData,避免粘性事件传递给新观察者。

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

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

相关文章

A系统使用iframe嵌套B系统时登录跨域问题!

我这边两个项目都是独立的&#xff0c;问题是做了跨域配置之后点击登录接口调用成功但是页面没有跳转进去 显示以下报错 这个错误明确指出了问题的核心原因&#xff1a;由于跨站点Cookie设置未正确声明SameSiteNone&#xff0c;导致浏览器拦截了Cookie。这是现代浏览器&#x…

消息唯一ID算法参考

VUE // src/utils/idGenerator.js/*** 雪花算法风格的 ID 生成器**//*** 前缀 w代表web端,m代表手机端**/ const DEFAULT_PREFIX = w; const DEFAULT_TOTAL_LENGTH = 16; const CHARS

《WebGIS之Vue零基础教程》(5)计算属性与侦听器

1 计算属性 1) 什么是计算属性 :::info 计算属性就是基于现有属性计算后的属性 ::: 2) 计算属性的作用 计算属性用于对原始数据的再次加工 3) 案例 :::warning **需求** 实现如下效果 ::: 使用表达式实现 html Document 请输入一个字符串: 反转后的字符串: {{msg.split(…

洞悉 NGINX ngx_http_access_module基于 IP 的访问控制实战指南

一、模块概述 ngx_http_access_module 是 NGINX 核心模块之一&#xff0c;用于基于客户端 IP 地址或 UNIX 域套接字限制访问。它通过简单的 allow/deny 规则&#xff0c;对请求进行最先匹配原则的过滤。与基于密码&#xff08;auth_basic&#xff09;、子请求&#xff08;auth…

数据中台-数据质量管理系统:从架构到实战

一、数据质量管理系统核心优势解析​ ​ (一)可视化驱动的敏捷数据治理​ 在数据治理的复杂流程中,Kettle 的 Spoon 图形化界面堪称一把利器,为数据工程师们带来了前所未有的便捷体验。想象一下,你不再需要花费大量时间和精力去编写冗长且复杂的 SQL 脚本,只需通过简单…

数据分析之 商品价格分层之添加价格带

在分析货品数据的时候&#xff0c;我们会对商品的价格进行分层汇总&#xff0c;也叫价格带&#xff0c;​​ 一、价格带的定义​​ ​​价格带&#xff08;Price Band&#xff09;​​&#xff1a;将商品按价格区间划分&#xff08;如0-50元、50-100元、100-200元等&#xff…

Maven 依赖范围(Scope)详解

Maven 依赖范围&#xff08;Scope&#xff09;详解 Maven 是一个强大的项目管理工具&#xff0c;广泛用于 Java 开发中构建、管理和部署应用程序。在使用 Maven 构建项目时&#xff0c;我们经常需要引入各种第三方库或框架作为项目的依赖项。通过在 pom.xml 文件中的 <depe…

vue3实现v-directive;vue3实现v-指令;v-directive不触发

文章目录 场景&#xff1a;问题&#xff1a;原因&#xff1a;‌ 场景&#xff1a; 列表的操作列有按钮&#xff0c;通过v-directive指令控制按钮显隐&#xff1b;首次触发了v-directive指令&#xff0c;控制按钮显隐正常&#xff1b;但是再次点击条件查询后&#xff0c;列表数…

数据结构【树和二叉树】

树和二叉树 前言1.树1.1树的概念和结构1.2树的相关术语1.3树的表示方法1.4 树形结构实际运用场景 2.二叉树2.1二叉树的概念和结构2.2二叉树具备以下特点&#xff1a;2.3二叉树分类 3.满二叉树4.完全二叉树5.二叉树性质6.附&#xff1a;树和二叉树图示 前言 欢迎莅临姜行运主页…

css面板视觉高度

css面板视觉高度 touch拖拽 在手机端有时候会存在实现touch上拉或者下拉的样式操作 此功能实现可以参考&#xff1a; https://blog.csdn.net/u012953777/article/details/147465162?spm1011.2415.3001.5331 面板视觉高度 前提需求&#xff1a; 1、展示端分为两部分&…

【Linux系统】详解Linux权限

文章目录 前言一、学习Linux权限的铺垫知识1.Linux的文件分类2.Linux的用户2.1 Linux下用户分类2.2 创建普通用户2.3 切换用户2.4 sudo&#xff08;提升权限的指令&#xff09; 二、Linux权限的概念以及修改方法1.权限的概念2.文件访问权限 和 访问者身份的相关修改&#xff08…

路由器的基础配置全解析:静态动态路由 + 华为 ENSP 命令大全

&#x1f680; 路由器的基础配置全解析&#xff1a;静态&动态路由 华为 ENSP 命令大全 &#x1f310; 路由器的基本概念&#x1f4cd; 静态路由配置&#x1f4e1; 动态路由协议&#xff1a;RIP、OSPF、BGP&#x1f5a5; 华为 ENSP 路由器命令大全&#x1f539; 路由器基本…

详细图解 Path-SAM2: Transfer SAM2 for digital pathology semantic segmentation

✨ 背景动机 数字病理中的语义分割&#xff08;semantic segmentation&#xff09;是非常关键的&#xff0c;比如肿瘤检测、组织分类等。SAM&#xff08;Segment Anything Model&#xff09;推动了通用分割的发展&#xff0c;但在病理图像上表现一般。 病理图像&#xff08;Pa…

初识Redis · 哨兵机制

目录 前言&#xff1a; 引入哨兵 模拟哨兵机制 配置docker环境 基于docker环境搭建哨兵环境 对比三种配置文件 编排主从节点和sentinel 主从节点 sentinel 模拟哨兵 前言&#xff1a; 在前文我们介绍了Redis的主从复制有一个最大的缺点就是&#xff0c;主节点挂了之…

HTTP header Cookie 和 Set-Cookie

RFC 6265: HTTP State Management Mechanismhttps://www.rfc-editor.org/rfc/rfc6265 Set-Cookie 响应头 服务器使用 Set-Cookie 响应头向客户端&#xff08;通常是浏览器&#xff09;发送 Cookie。 基本格式&#xff1a; Set-Cookie: <cookie名称><cookie值>;…

【Unity完整游戏开发案例】从0做一个太空大战游戏

1.实现飞机移动控制 // 这个脚本实现控制飞机前后移动&#xff0c;方向由鼠标控制 //1.WS控制前后移动2.鼠标控制上下左右旋转3.AD控制倾斜 using System.Collections; using System.Collections.Generic; using UnityEngine;public class PlayerController : MonoBehav…

【C++】C++11新特性(一)

文章目录 列表初始化initializer_list左值引用和右值引用 列表初始化 在 C98 中可以使用{}对数组或者结构体元素进行统一的列表初始值设定 struct Point {int _x;int _y; }; int main() {int array1[] { 1, 2, 3, 4, 5 };int array2[5] { 0 };Point p { 1, 2 };return 0; …

小黑享受思考心流: 73. 矩阵置零

小黑代码 class Solution:def setZeroes(self, matrix: List[List[int]]) -> None:"""Do not return anything, modify matrix in-place instead."""items []m len(matrix)n len(matrix[0])for i in range(m):for j in range(n):if not m…

精益数据分析(19/126):走出数据误区,拥抱创业愿景

精益数据分析&#xff08;19/126&#xff09;&#xff1a;走出数据误区&#xff0c;拥抱创业愿景 在创业与数据分析的探索之旅中&#xff0c;我们都渴望获取更多知识&#xff0c;少走弯路。今天&#xff0c;我依然带着和大家共同进步的想法&#xff0c;深入解读《精益数据分析…

循环神经网络RNN---LSTM

一、 RNN介绍 循环神经网络&#xff08;Recurrent Neural Network&#xff0c;简称 RNN&#xff09;是一种专门用于处理序列数据的神经网络&#xff0c;在自然语言处理、语音识别、时间序列预测等领域有广泛应用。 传统神经网络 无法训练出具有顺序的数据。模型搭建时没有考…