kotlin和MVVM的结合使用总结(二)
MVVM 架构详解
核心组件:ViewModel 和 LiveData
在 Android 中,MVVM 架构主要借助 ViewModel 和 LiveData 来实现。ViewModel 负责处理业务逻辑,而 LiveData 则用于实现数据的响应式更新。
ViewModel 的源码分析
ViewModel 的核心逻辑在 ViewModelStore 类中。ViewModelStore 是一个存储 ViewModel 的容器,内部使用 HashMap 来存储不同的 ViewModel 实例。以下是 ViewModelStore 的关键代码:
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);}public final void clear() {for (ViewModel vm : mMap.values()) {vm.onCleared();}mMap.clear();}
}
ViewModel 利用 ViewModelStore 保证在配置变更(如屏幕旋转)时数据不丢失,并且将业务逻辑与 Activity、Fragment 分离,提高了代码的可维护性和可测试性。
LiveData 的源码分析
LiveData 基于观察者模式,通过 observe 方法添加观察者,当数据变化时,会调用 Observer 的 onChanged 方法更新 UI。
与 MVC 架构对比
MVC 架构的问题
在 MVC 架构中,Activity 既充当 View 又充当 Controller。在 Android 中,Activity 继承自 AppCompatActivity,其源码中包含大量视图初始化和事件处理代码,使得 Activity 变得复杂。例如:
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);findViewById(R.id.button).setOnClickListener(v -> {// 处理点击事件});}
}
与 MVVM 相比,MVC 没有明确的职责划分,导致代码耦合度高,难以维护和扩展。
与 MVP 架构对比
MVP 架构的特点
MVP 中 Presenter 与 View 通过接口交互。Presenter 持有 View 接口的引用,在更新视图时调用接口方法。以下是一个简单的示例:
// View 接口
public interface MainView {void showData(String data);
}// Presenter 类
public class MainPresenter {private MainView view;public MainPresenter(MainView view) {this.view = view;}public void loadData() {// 模拟加载数据String data = "Hello, MVP!";view.showData(data);}
}
而 MVVM 采用数据绑定,ViewModel 无需持有 View 的引用,降低了耦合度,使得代码更加灵活和易于维护。
面试扩展:
MVVM 架构与其他架构区别
-
请详细阐述 MVVM、MVC 和 MVP 架构在数据绑定机制上的差异,并说明这种差异如何影响代码的可维护性和扩展性。
回答:- MVVM:在 Android 中借助
LiveData实现数据绑定,以LiveData源码为例,它基于观察者模式,通过observe方法添加观察者,内部维护mVersion和ObserverWrapper的mLastVersion来控制数据分发。数据变化时,LiveData会遍历观察者调用onChanged方法更新 UI,使得 View 和 ViewModel 之间的数据同步自动化,减少手动更新代码,提高可维护性与扩展性。例如在复杂 UI 界面中,数据更新无需在多处手动操作视图。 - MVC:如在 Android 里
Activity常兼具 View 和 Controller 职责,在Activity源码中,大量视图初始化和事件处理代码混杂,在更新视图时需手动在 Controller 部分(如Activity中的业务逻辑代码)编写更新视图的代码,这导致代码耦合度高,可维护性差。例如一个界面有多个视图需更新,代码中会有多处重复的视图更新逻辑,不利于扩展新功能。 - MVP:Presenter 通过接口与 View 交互,更新视图时 Presenter 手动调用 View 接口方法。从代码结构看,Presenter 持有 View 引用,若业务逻辑复杂,Presenter 会变得臃肿,可维护性降低。且当 View 层变化时,Presenter 中调用 View 接口方法的代码也需大量修改,扩展性不佳。比如更换了视图框架,Presenter 中更新视图的方法基本都要调整。
- MVVM:在 Android 中借助
-
从架构组件的生命周期角度,分析 MVVM 与 MVP 架构的不同。
回答:- MVVM:ViewModel 的生命周期由
ViewModelStore和ViewModelProvider管理。ViewModelProvider从ViewModelStore中创建或获取 ViewModel 实例,在配置变更(如屏幕旋转)时,ViewModel 实例不销毁,保证数据存储。而LiveData具有生命周期感知能力,通过LifecycleOwner控制观察者的注册与注销,只有当LifecycleOwner处于活跃状态(如STARTED或RESUMED)时,观察者才会接收数据更新,避免内存泄漏等问题。 - MVP:Presenter 与 View 通过接口交互,Presenter 持有 View 引用。但 Presenter 本身没有很好的生命周期管理机制,在配置变更时,若不妥善处理,Presenter 可能会持有旧的 View 引用,导致内存泄漏。例如在屏幕旋转时,若没有正确处理 Presenter 与 View 的关系,可能会出现空指针异常等问题。
- MVVM:ViewModel 的生命周期由
LiveData 数据倒灌的源码级别分析
数据倒灌的原因
LiveData 内部维护了一个 mVersion 和 START_VERSION,mVersion 表示 LiveData 数据的版本号,每次数据更新时 mVersion 会递增。ObserverWrapper 是 Observer 的包装类,其中的 mLastVersion 记录了观察者最后一次接收到数据的版本号。
// LiveData 关键代码
private static abstract class ObserverWrapper {final Observer<? super T> mObserver;boolean mActive;int mLastVersion = START_VERSION;ObserverWrapper(Observer<? super T> observer) {mObserver = observer;}// ...
}
当新的观察者注册时,LiveData 会检查 mLastVersion 和 mVersion,如果 mLastVersion 小于 mVersion,则会将数据发送给观察者,从而导致数据倒灌。
解决方法原理
以 SingleLiveEvent 为例,它通过一个标志位 mPending 来控制数据是否已被消费。
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;import java.util.concurrent.atomic.AtomicBoolean;public class SingleLiveEvent<T> extends MutableLiveData<T> {private final AtomicBoolean mPending = new AtomicBoolean(false);@Overridepublic void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {super.observe(owner, t -> {if (mPending.compareAndSet(true, false)) {observer.onChanged(t);}});}@Overridepublic void setValue(T value) {mPending.set(true);super.setValue(value);}@Overridepublic void postValue(T value) {mPending.set(true);super.postValue(value);}
}
当数据更新时,mPending 设为 true,观察者接收到数据后将其设为 false,确保数据只被消费一次。
面试扩展:
- 请描述 LiveData 数据倒灌产生的原因,并给出至少两种基于源码层面的解决方案。
回答:- 原因:从
LiveData源码看,它内部维护mVersion代表数据版本号,每次数据更新mVersion递增,ObserverWrapper中的mLastVersion记录观察者最后接收数据版本号。新观察者注册时,若mLastVersion小于mVersion,就会接收数据,导致数据倒灌。 - 解决方案:
- SingleLiveEvent:它通过
AtomicBoolean类型的mPending标志位控制数据消费。数据更新时,mPending设为true,观察者接收数据后设为false,保证数据只被消费一次。从其源码实现可知,在observe方法中判断mPending状态决定是否通知观察者。 - 自定义 LiveData 包装类:在自定义包装类中添加版本号或标记位。如在包装类中维护一个
AtomicInteger类型的版本号,每次数据更新时版本号递增,在observe方法中,观察者接收数据后记录当前版本号,下次数据更新时对比版本号,若相同则不通知,从而避免数据倒灌。
- SingleLiveEvent:它通过
- 原因:从
ViewModel 对 UI 数据管理的源码级别分析
数据存储与生命周期管理
ViewModel 的生命周期由 ViewModelStore 和 ViewModelProvider 管理。ViewModelProvider 负责创建和获取 ViewModel 实例,它会先从 ViewModelStore 中查找是否存在该 ViewModel 实例,如果存在则直接返回,不存在则创建新的实例。
// ViewModelProvider 关键代码
public class ViewModelProvider {private final Factory mFactory;private final ViewModelStore mViewModelStore;public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {mFactory = factory;mViewModelStore = store;}@NonNull@MainThreadpublic <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);}@NonNull@MainThreadpublic <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {ViewModel viewModel = mViewModelStore.get(key);if (modelClass.isInstance(viewModel)) {//noinspection uncheckedreturn (T) viewModel;} else {//noinspection StatementWithEmptyBodyif (viewModel != null) {// TODO: log a warning.}}viewModel = mFactory.create(modelClass);mViewModelStore.put(key, viewModel);//noinspection uncheckedreturn (T) viewModel;}
}
这样,在配置变更时,ViewModel 实例不会被销毁,保证了数据的存储。
数据更新与通知
ViewModel 通常使用 LiveData 来存储和通知 UI 数据的变化。当 ViewModel 中的数据更新时,调用 LiveData 的 setValue 或 postValue 方法,LiveData 会遍历所有观察者并调用其 onChanged 方法。
// LiveData 关键代码
private void considerNotify(ObserverWrapper observer) {if (!observer.mActive) {return;}if (!observer.shouldBeActive()) {observer.activeStateChanged(false);return;}if (observer.mLastVersion >= mVersion) {return;}observer.mLastVersion = mVersion;//noinspection uncheckedobserver.mObserver.onChanged((T) mData);
}
通过这种方式,ViewModel 实现了对 UI 数据的管理和更新通知。
面试扩展:
- 从源码角度分析 ViewModel 如何确保在配置变更时 UI 数据不丢失,以及如何与 LiveData 协作更新 UI。
回答:- 配置变更时数据不丢失:
ViewModel借助ViewModelStore和ViewModelProvider实现。ViewModelStore内部用HashMap存储ViewModel实例,ViewModelProvider在创建或获取ViewModel实例时,先从ViewModelStore查找,若存在则返回,不存在才创建新实例。例如在屏幕旋转等配置变更时,ViewModel实例得以保留,确保 UI 数据不丢失。 - 与 LiveData 协作更新 UI:
ViewModel通常使用LiveData存储和通知 UI 数据变化。当ViewModel中数据更新,调用LiveData的setValue或postValue方法,LiveData会遍历观察者调用considerNotify方法,在considerNotify方法中判断观察者状态和版本号等条件后,调用观察者的onChanged方法通知 UI 更新。
- 配置变更时数据不丢失:
相关文章:
kotlin和MVVM的结合使用总结(二)
MVVM 架构详解 核心组件:ViewModel 和 LiveData 在 Android 中,MVVM 架构主要借助 ViewModel 和 LiveData 来实现。ViewModel 负责处理业务逻辑,而 LiveData 则用于实现数据的响应式更新。 ViewModel 的源码分析 ViewModel 的核心逻辑在 …...
Git 入门知识详解
文章目录 一、Git 是什么1、Git 简介2、Git 的诞生3、集中式 vs 分布式3.1 集中式版本控制系统3.2 分布式版本控制系统 二、GitHub 与 Git 安装1、GitHub2、Git 安装 一、Git 是什么 1、Git 简介 Git 是目前世界上最先进的分布式版本控制系统。版本控制系统能帮助我们更好地管…...
React.memo 和 useMemo
现象 React 中,通常父组件的某个state发生改变,会引起父组件的重新渲染(和其他state的重新计算),从而会导致子组件的重新渲染(和其他非相关属性的重新计算) 问题一:如何避免因为某个…...
EDI 如何与 ERP,CRM,WMS等系统集成
在数字化浪潮下,与制造供应链相关产业正加速向智能化供应链转型。传统人工处理订单、库存和物流的方式已难以满足下单客户对响应速度和数据准确性的严苛要求。EDI技术作为企业间数据交换的核心枢纽,其与ERP、CRM、WMS等业务系统的深度集成,成…...
面试踩过的坑
1、 “”和equals 的区别 “”是运算符,如果是基本数据类型,则比较存储的值;如果是引用数据类型,则比较所指向对象的地址值。equals是Object的方法,比较的是所指向的对象的地址值,一般情况下,重…...
论文阅读:2024 ACL ArtPrompt: ASCII Art-based Jailbreak Attacks against Aligned LLMs
总目录 大模型安全相关研究:https://blog.csdn.net/WhiffeYF/article/details/142132328 Artprompt: Ascii art-based jailbreak attacks against aligned llms https://www.doubao.com/chat/3846685176618754 https://arxiv.org/pdf/2402.11753 https://github…...
多物理场耦合低温等离子体装置求解器PASSKEy2
文章目录 PASSKEy2简介PASSKEY2计算流程PASSKEy2 中求解的物理方程电路模型等离子体模型燃烧模型 PASSKEy2的使用 PASSKEy2简介 PASSKEy2 是在 PASSKEy1 的基础上重新编写的等离子体数值模拟程序。 相较于 PASSKEy1, PASSKEy2 在具备解决低温等离子体模拟问题的能力…...
视频噪点多,如何去除画面噪点?
你是否遇到过这样的困扰?辛辛苦苦拍摄的视频,导出后却满屏 “雪花”,夜景变 “噪点盛宴”,低光环境秒变 “马赛克现场”? 无论是日常拍摄的vlog、珍贵的家庭录像,还是专业制作的影视作品,噪点问…...
09前端项目----分页功能
分页功能 分页器的优点实现分页功能自定义分页器先实现静态分页器调试分页器动态数据/交互 Element UI组件 分页器的优点 电商平台同时展示的数据很多,所以采用分页功能实现分页功能 Element UI已经有封装好的组件,但是也要掌握原理,以及自定…...
第十二届蓝桥杯 2021 C/C++组 直线
目录 题目: 题目描述: 题目链接: 思路: 核心思路: 两点确定一条直线: 思路详解: 代码: 第一种方式代码详解: 第二种方式代码详解: 题目:…...
《Piper》皮克斯技术解析:RIS系统与云渲染如何创造奥斯卡级动画短片
本文由专业专栏作家 Mike Seymour 撰写,内容包含非常有价值的行业资讯。 译者注 《Piper》是皮克斯动画工作室的一部技术突破性的短片,讲述了一只小鸟在海滩上寻找食物并面对自然挑战的故事。它不仅凭借其精美的视觉效果和细腻的情感表达赢得了2017年奥…...
Java在excel中导出动态曲线图DEMO
1、环境 JDK8 POI 5.2.3 Springboot2.7 2、DEMO pom <dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>5.2.3</version></dependency><dependency><groupId>commons…...
第19章:Multi-Agent多智能体系统介绍
第19章:Multi-Agent多智能体系统介绍 欢迎来到多智能体系统 (Multi-Agent System, MAS) 的世界!在之前的章节中,我们深入探讨了单个 AI Agent 的构建,特别是结合了记忆、上下文和规划能力的 MCP 框架。然而,现实世界中的许多复杂问题往往需要多个智能体协同工作才能有效解…...
Kotlin Multiplatform--02:项目结构进阶
Kotlin Multiplatform--02:项目结构进阶 引言正文 引言 在上一章中,我们对 Kotlin Multiplatform 项目有了基本的了解,已经可以进行开发了。但我们只是使用了系统默认的项目结构。本章介绍了如何进行更复杂的项目结构管理。 正文 在上一章中&…...
Spring Cloud Gateway配置双向SSL认证(完整指南)
本文将详细介绍如何为Spring Cloud Gateway配置双向SSL认证,包括证书生成、配置和使用。 目录结构 /my-gateway-project ├── /certs │ ├── ca.crt # 根证书 │ ├── ca.key # 根私钥 │ ├── gateway.crt # 网关证书 │ ├── …...
Windows同步技术-使用命名对象
在 Windows 系统下使用命名对象(如互斥体、事件、信号量、文件映射等内核对象)时,需注意以下关键要点: 命名规则 唯一性:名称需全局唯一,避免与其他应用或系统对象冲突,建议使用 GUID 或应用专…...
代码随想录算法训练营第五十八天 | 1.拓扑排序精讲 2.dijkstra(朴素版)精讲 卡码网117.网站构建 卡码网47.参加科学大会
1.拓扑排序精讲 题目链接:117. 软件构建 文章讲解:代码随想录 思路: 把有向无环图进行线性排序的算法都可以叫做拓扑排序。 实现拓扑排序的算法有两种:卡恩算法(BFS)和DFS,以下BFS的实现思…...
linux ptrace 图文详解(七) gdb、strace跟踪系统调用
目录 一、gdb/strace 跟踪程序系统调用 二、实现原理 三、代码实现 四、总结 (代码:linux 6.3.1,架构:arm64) One look is worth a thousand words. —— Tess Flanders 相关链接: linux ptrace 图…...
【前端】ES6 引入的异步编程解决方案Promise 详解
Promise 详解 1. 基本概念 定义:Promise 是 ES6 引入的异步编程解决方案,表示一个异步操作的最终完成(或失败)及其结果值。核心作用:替代回调函数,解决“回调地狱”问题,提供更清晰的异步流程控…...
常见正则表达式整理与Java使用正则表达式的例子
一、常见正则表达式整理 1. 基础验证类 邮箱地址 ^[a-zA-Z0-9._%-][a-zA-Z0-9.-]\\.[a-zA-Z]{2,}$ (匹配如 userexample.com)手机号 ^1[3-9]\\\\d{9}$ (匹配国内11位手机号,如 13812345678)中文字符 ^[\u4e00-\u9fa5…...
const(C++)
打印出来的结果是 a是12 *p是200 const修饰指针 const修饰引用...
python21-循环小作业
课程:B站大学 记录python学习,直到学会基本的爬虫,使用python搭建接口自动化测试就算学会了,在进阶webui自动化,app自动化 循环语句小作业 for-in作业斐波那契 for 固定数值计算素数字符统计数字序列range 函数 水仙花…...
小白电路设计-设计11-恒功率充电电路设计
介绍 作为电子信息工程的我,电路学习是一定要学习的,可惜目前作为EMC测试工程师,无法兼顾太多,索性不如直接将所学的知识进行运用,并且也可以作为契机,进行我本人的个人提升。祝大家与我一起进行提升。1.本…...
传感器模块有助于加速嵌入式视觉开发
传感器模块是一种小型成像解决方案,用于轻松将定制的视觉技术集成到机器和设备中,使其具备“视觉”功能。机器人、无人机、物联网、消费电子设备和监控应用的开发人员在设计中使用传感器模块,可以节省开发时间和资源。FRAMOS 推出了一个创新的可互换传感器模块和适配器生态系…...
Spring AI 快速入门:从环境搭建到核心组件集成
Spring AI 快速入门:从环境搭建到核心组件集成 一、前言:Java开发者的AI开发捷径 对于Java生态的开发者来说,将人工智能技术融入企业级应用往往面临技术栈割裂、依赖管理复杂、多模型适配困难等挑战。Spring AI的出现彻底改变了这一局面——…...
http://noi.openjudge.cn/——2.5基本算法之搜索——200:Solitaire
文章目录 题目宽搜代码总结 题目 总时间限制: 5000ms 单个测试点时间限制: 1000ms 内存限制: 65536kB 描述 Solitaire is a game played on a chessboard 8x8. The rows and columns of the chessboard are numbered from 1 to 8, from the top to the bottom and from left t…...
架构师面试(三十六):广播消息
题目 在像 IM、短视频、游戏等实时在线类的业务系统中,一般会有【广播消息】业务,这类业务具有瞬时高流量的特点。 在对【广播消息】业务实现时通常需要同时写 “系统消息库” 和更新用户的 “联系人库” 的操作,用户的联系人表中会有未读数…...
如何开启远程桌面连接外网访问?异地远程控制内网主机
实现远程桌面连接外网访问,能够突破地域限制,随时随地访问远程计算机,满足远程办公、技术支持等多种需求。下面为你详细介绍开启方法。 一、联网条件 确保本地计算机和远程计算机都有稳定的网络连接,有联网能上网。 二、开启远程…...
基于 Python(selenium) 的百度新闻定向爬虫:根据输入的关键词在百度新闻上进行搜索,并爬取新闻详情页的内容
该项目能够根据输入的关键词在百度新闻上进行搜索,并爬取新闻详情页的内容。 一、项目准备 1. 开发环境配置 操作系统:支持 Windows、macOS、Linux 等主流操作系统,本文以 Windows 为例进行说明。Python 版本:建议使用 Python 3.8 及以上版本,以确保代码的兼容性和性能。…...
TortoiseGit使用图解
前言 记录GitTortoiseGit使用,记录下开发中常用命令,健忘时用到方知好。 TortoiseGit使用 图解 commit-提交代码 pull-拉取远程分支最新代码 push-将本地分支代码推送到远程分支 show log-查看分支提交记录 show log - 切换分支查看 show log - 远程分…...
