Android架构--MVVM

news2024/11/25 21:15:34

一、开发架构 是什么?


二、Android开发中的架构

具体到Android开发中,开发架构就是描述 视图层逻辑层数据层 三者之间的关系和实施:

视图层:用户界面,即界面的展示、以及交互事件的响应。

逻辑层:为了实现系统功能而进行的必要逻辑。

数据层:数据的获取和存储,含本地、server。

2.1 MVC

MVC,Model-View-Controller,职责分类如下:

Model,模型层,即数据模型,用于获取和存储数据。

View层,视图层,即 Activity/Fragment

Controller,控制层,负责业务逻辑。

循环流程:

View层 接收到用户操作事件,通知到 Controller 进行对应的逻辑处理,然后通知 Model去获取/更新数据,Model 再把新的数据 通知到 View 更新界面。这就是一个完整 MVC 的数据流向。

MVC

 

 MVC 的问题点 如下:

1.因为xml布局能力很弱,View的很多操作是在Activity/Fragment中的,而业务逻辑同样也是写在Activity/Fragment中。Activity/Fragment 责任不明,同时负责View、Controller,就会导致其中代码量大,不满足单一职责。

2.Model耦合View,View 的修改会导致 Controller 和 Model 都进行改动,不满足最少知识原则。

2.2 MVP

MVP,Model-View-Presenter,职责分类如下:

Model,模型层,即数据模型,用于获取和存储数据。

View,视图层,即Activity/Fragment

Presenter,控制层,负责业务逻辑。

MVP解决了MVC的问题:

1.View责任明确,逻辑不再写在Activity中,而是在Presenter中;

2.Model不再持有View。

循环流程:

View层 接收到用户操作事件,通知到Presenter,Presenter进行逻辑处理,然后通知Model更新数据,Model 把更新的数据给到Presenter,Presenter再通知到 View 更新界面。

MVP

 

MVP的实现思路:

UI逻辑抽象成IView接口,由具体的Activity实现类来完成。且调用Presenter进行逻辑操作。

业务逻辑抽象成IPresenter接口,由具体的Presenter实现类来完成。逻辑操作完成后调用IView接口方法刷新UI。

MVP 本质是面向接口编程,实现了依赖倒置原则。MVP解决了View层责任不明的问题,但并没有解决代码耦合的问题,View和Presenter之间相互持有。

MVP 有缺点 如下:

1.会引入大量的IView、IPresenter接口,增加实现的复杂度。

2.View和Presenter相互持有,形成耦合。 项目没有解耦。

2.3 MVVM

MVVM,Model-View-ViewModel,职责分类如下:

 Model,模型层,即数据模型,用于获取和存储数据。

View,视图,即Activity/Fragment

ViewModel,视图模型,负责业务逻辑。(不等同于Jetpack 的ViewModel组件)

注意,MVVM这里的ViewModel就是一个名称,可以理解为MVP中的Presenter。不等同于上一篇中的 ViewModel组件 ,Jetpack ViewModel组件是 对 MVVM的ViewModel 的具体实施方案。

循环流程:

MVVM 的本质是 数据驱动,把解耦做的更彻底,viewModel不持有view 。

View 产生事件,使用 ViewModel进行逻辑处理后,通知Model更新数据,Model把更新的数据给ViewModel,ViewModel自动通知View更新界面而不是主动调用View的方法

数据驱动:数据的更改 驱动 View 自动刷新

MVVM

MVVM在Android开发中是如何实现的呢?

到这里你会发现,所谓的架构模式本质上理解很简单。比如MVP,甚至你都可以忽略这个名字,理解成 在更高的层面上 面向接口编程,实现了 依赖倒置 原则,就是这么简单。

三、MVVM 的实现 - Jetpack MVVM

前面提到,架构模式选择适合自己项目的即可。话虽如此,但Google官方推荐的架构模式 是适合大多数情况,是非常值得我们学习和实践的。好了,下面我们就来详细介绍 Jetpack MVVM 架构。

3.1 Jetpack MVVM 理解

Jetpack MVVM 是 MVVM 模式在 Android 开发中的一个具体实现,是 Android中 Google 官方提供并推荐的 MVVM实现方式。

不仅通过数据驱动完成彻底解耦,还兼顾了 Android 页面开发中其他不可预期的错误,例如Lifecycle 能在妥善处理 页面生命周期 避免view空指针问题,ViewModel使得UI发生重建时 无需重新向后台请求数据,节省了开销,让视图重建时更快展示数据。

 

 视图讲解:

 图中所有的箭头都是单向的,例如View层指向了ViewModel层,表示View层会持有ViewModel层的引用,但是反过来ViewModel层却不能持有View层的引用

除此之外,引用也不能跨层持有,比如View层不能持有仓库层的引用,谨记每一层的组件都只能与它相邻层的组件进行交互。

这种设计打造了一致且愉快的用户体验。无论用户上次使用应用是在几分钟前还是几天之前,现在回到应用时都会立即看到应用在本地保留的数据。如果此数据已过期,则应用的Repository将开始在后台更新数据。

Jetpack MVVM 架构

Jetpack MVVM 架构

View层:Activity/Fragment。

包含了我们平时写的Activity/Fragment/布局文件等与界面相关的东西。

ViewModel层:Jetpack ViewModel + Jetpack LivaData。

用于持有和UI元素相关的数据,以保证这些数据在屏幕旋转时不会丢失,并且还要提供接口给View层调用以及和仓库层进行通信。

Model层:Repository仓库,包含 本地持久性数据 和 服务端数据

仓库层 要做的主要工作是判断调用方请求的数据应该是从本地数据源中获取还是从网络数据源中获取,并将获取到的数据返回给调用方。本地数据源可以使用数据库、SharedPreferences等持久化技术来实现,而网络数据源则通常使用Retrofit访问服务器提供的Webservice接口来实现。

3.2 实施

我们来举个完整的例子 - 在页面中显示用户信息列表,来说明 Jetpack MVVM 的具体实施。

3.2.1 构建界面

首先创建一个列表页面 UserListActivity,并且知道页面所需要的数据是,用户信息列表。

那么 用户信息列表 如何获取呢?根据上面的架构图,就是ViewModel了,所以我们创建 UserListViewModel 继承自 ViewModel,并且把 用户信息列表 以 LiveData呈现。

public class UserListViewModel extends ViewModel {
    //用户信息
    private MutableLiveData<List<User>> userListLiveData;
    //进条度的显示
    private MutableLiveData<Boolean> loadingLiveData;

    public UserListViewModel() {
        userListLiveData = new MutableLiveData<>();
        loadingLiveData = new MutableLiveData<>();
    }
    
    public LiveData<List<User>> getUserListLiveData() {
        return userListLiveData;
    }
    public LiveData<Boolean> getLoadingLiveData() {
        return loadingLiveData;
    }
    ...
}

LiveData 是一种可观察的数据存储器。应用中的其他组件可以使用此存储器监控对象的更改,而无需在它们之间创建明确且严格的依赖路径。LiveData 组件还遵循应用组件(如 Activity、Fragment 和 Service)的生命周期状态,并包括清理逻辑以防止对象泄漏和过多的内存消耗。

将 UserListViewModel 中的字段类型更改为 MutableLiveData<List>。现在,更新数据时,系统会通知 UserListActivity。此外,由于此 LiveData 字段具有生命周期感知能力,因此当不再需要引用时,会自动清理它们。

另外,注意到暴露的获取LiveData的方法 返回的是LiveData类型,即不可变的,而不是MutableLiveData,好处是避免数据在外部被更改。

 

现在,我们修改 UserListActivity 以观察数据并更新界面:

每次更新用户列表信息数据时,系统都会调用 onChanged() 回调并刷新界面,而不需要 ViewModel主动调用View层方法刷新,这就是 数据驱动 了 —— 数据的更改 驱动 View 自动刷新。

因为LiveData具有生命周期感知能力,这意味着,除非 Activity 处于活跃状态,否则它不会调用 onChanged() 回调。当调用 Activity 的 onDestroy() 方法时,LiveData 还会自动移除观察者。

另外,我们也没有添加任何逻辑来处理配置更改(例如,用户旋转设备的屏幕)。UserListViewModel 会在配置更改后自动恢复,所以一旦创建新的 Activity,它就会接收相同的 ViewModel 实例,并且会立即使用当前的数据调用回调。鉴于 ViewModel 对象应该比它们更新的相应 View 对象存在的时间更长,因此 ViewModel 实现中不得包含对 View 对象的直接引用,包括Context。

class UserListActivity  extends AppCompatActivity{

val mUserListViewModel= ViewModelProvider(this).get(UserListViewModel ::class.java)

    //观察ViewModel的数据,且此数据 是 View 直接需要的,不需要再做逻辑处理
    private void observeLivaData() {
        mUserListViewModel.getUserListLiveData().observe(this, new Observer<List<User>>() {
            @Override
            public void onChanged(List<User> users) {
                if (users == null) {
                    Toast.makeText(UserListActivity.this, "获取user失败!", Toast.LENGTH_SHORT).show();
                    return;
                }
                //刷新列表
                mUserAdapter.setNewInstance(users);
            }
        });

        mUserListViewModel.getLoadingLiveData().observe(this, new Observer<Boolean>() {
            @Override
            public void onChanged(Boolean aBoolean) {
            	//显示/隐藏加载进度条
                mProgressBar.setVisibility(aBoolean? View.VISIBLE:View.GONE);
            }
        });
    }


}

3.2.2 获取数据

现在,我们已使用 LiveData 将 UserListViewModel 连接到UserListActivity,那么如何获取用户个人信息列表数据呢?

实现 ViewModel 的第一个想法可能是 使用Retrofit/Okhttp调用接口 来获取数据,然后将该数据设置给 LiveData 对象。这种设计行得通,但如果采用这种设计,随着应用的扩大,应用会变得越来越难以维护。这样会使 UserListViewModel 类承担太多的责任,这就违背了单一职责原则。

 ViewModel 会将数据获取过程委派给一个新的模块,即Repository仓库

Repository模块会处理数据操作。它们会提供一个干净的 API,以便应用内其余部分也可以轻松获取该数据。数据更新时,它们知道从何处获取数据以及进行哪些 API 调用。您可以将Repository视为不同数据源(如持久性模型、网络服务和缓存)之间的媒介。

虽然Repository模块看起来不必要,但它起着一项重要的作用:它会从应用的其余部分中提取数据源。现在,UserListViewModel 是不知道数据来源的,因此我们可以为ViewModel提供从几个不同的数据源获取数据。

public class UserRepository {

    private static UserRepository mUserRepository;
    public static UserRepository getUserRepository(){
        if (mUserRepository == null) {
            mUserRepository = new UserRepository();
        }
        return mUserRepository;
    }

    //(假装)从服务端获取
    public void getUsersFromServer(Callback<List<User>> callback){
        new AsyncTask<Void, Void, List<User>>() {
            @Override
            protected void onPostExecute(List<User> users) {
                callback.onSuccess(users);
                //存本地数据库
                saveUsersToLocal(users);
            }
            @Override
            protected List<User> doInBackground(Void... voids) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //假装从服务端获取的
                List<User> users = new ArrayList<>();
                for (int i = 0; i < 20; i++) {
                    User user = new User("user"+i, i);
                    users.add(user);
                }
                return users;
            }
        }.execute();
    }
    

 3.2.3 连接 ViewModel 与存储区

我们在UserListViewModel 提供一个方法,可以让用户Activity获取用户信息。此方法就是调用Repository来执行,并且把数据设置到LiveData。

public class UserListViewModel extends ViewModel {
    //用户信息
    private MutableLiveData<List<User>> userListLiveData;
    //进条度的显示
    private MutableLiveData<Boolean> loadingLiveData;

    public UserListViewModel() {
        userListLiveData = new MutableLiveData<>();
        loadingLiveData = new MutableLiveData<>();
    }

//返回LiveData类型
    public LiveData<List<User>> getUserListLiveData() {
        return userListLiveData;
    }
    public LiveData<Boolean> getLoadingLiveData() {
        return loadingLiveData;
    }

  
    /**
     * 获取用户列表信息
     * 假装网络请求 2s后 返回用户信息
     */
    public void getUserInfo() {

        loadingLiveData.setValue(true);

        UserRepository.getUserRepository().getUsersFromServer(new Callback<List<User>>() {
            @Override
            public void onSuccess(List<User> users) {
                loadingLiveData.setValue(false);
                userListLiveData.setValue(users);
            }

            @Override
            public void onFailed(String msg) {
                loadingLiveData.setValue(false);
                userListLiveData.setValue(null);
            }
        });
    }

   
}

在Activity中,就要获取UserListViewModel实例,获取用户信息:

//UserListActivity.java
public class UserListActivity extends AppCompatActivity {
    private UserListViewModel mUserListViewModel;

     private  LiveData<List<User>>  userListLiveData
    private ProgressBar mProgressBar;
    private RecyclerView mRvUserList;
    private UserAdapter mUserAdapter;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_list);

      
        mUserListViewModel = new ViewModelProvider(this).get(UserListViewModel.class);
       //获取数据,调用ViewModel的方法获取

        mUserListViewModel.getUserInfo();
    userListLiveData=mUserListViewModel.getUserListLiveData()
     //观察数据的更新变化     

      userListLiveData.observe(this){userList:List<User>->

          mUserAdapter = new  UserAdapter(userList)

     }

}

3.2.4 缓存网络数据到本地

现在UserRepository 有个问题是,它从后端获取数据后,不会缓存该数据。因此,如果用户在离开页面后再返回,则应用必须重新获取数据,即使数据未发生更改也是如此。这就浪费了宝贵的网络资源,迫使用户等待新的查询完成。所以,我们向 UserRepository 添加了一个新的数据源,本地缓存。缓存实现 可以是 数据库、SharedPreferences等持久化技术。

class UserRepository {

    //从本地数据库获取
    public void getUsersFromLocal(){
        // TODO: 2021/1/24 从本地数据库获取
    }

    //存入本地数据库 (从服务端获取数据后可以调用)
    private void saveUsersToLocal(List<User> users){
        // TODO: 2021/1/24 存入本地数据库
    }

}

3.3 注意点

 1.在应用的各个模块之间设定明确定义的职责界限

 2.ViewModel 不能持有 View层引用,包括Context也不能持有。

3.将一个数据源指定为单一可信来源。 每当需要访问数据时,都应一律源于此单一可信来源。 例如 UserRepository会将网络服务响应保存在数据库中。这样一来,对数据库的更改将触发对活跃 LiveData 对象的回调。数据库会充当单一可信来源

4.保留尽可能多的相关数据和最新数据。 这样,即使用户的设备处于离线模式,他们也可以使用您应用的功能。请注意,并非所有用户都能享受到稳定的高速连接。

5.显示页面状态。 例如例子中的加载进度条,就是观察 ViewModel中的MutableLiveData loadingLiveData 进行操作的。

鸣谢:

https://juejin.cn/post/6921321173661777933/

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

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

相关文章

IL2CPP和Mono的区别

Mono 是一种开源的跨平台 .NET 框架实现&#xff0c;能够执行 C# 代码。Unity 使用 Mono 来处理 C# 脚本&#xff0c;并通过 JIT&#xff08;Just-In-Time&#xff09;即时编译器将托管代码转换为本地机器代码&#xff0c;随后在目标平台上执行 IL2CPP 代表 Intermediate Lang…

《业务三板斧:定目标、抓过程、拿结果》读书笔记3

关于目标&#xff0c;关键是共识目标&#xff1a; 为什么不是共识目标&#xff0c;而是共信目标&#xff1f; 共识目标是指管理者通过沟通&#xff0c;让所有团队成员就目标以及实现目 标的方法达成一致。当个人与组织、个人与个人之间出现“路径选择 差异”的时候&#xff0c;…

算法专题四: 前缀和

目录 1. 前缀和2. 二维前缀和3. 寻找数组的中心下标4. 除自身以外数组的乘积5. 和为k的子数组6. 和可被K整除的子数组7. 连续数组8. 矩阵区域和 博客主页:酷酷学!!! 感谢关注~ 1. 前缀和 算法思路: 根据题意, 创建一个前缀和数组, dp[i] dp[i -1] arr[i], 再使用前缀和数组,…

Go编译为可执行文件

在window下打包成其他系统可运行的文件 1.在window下打包成window下可执行文件 在项目main.go同级目录下&#xff0c;逐条执行以下命令 set CGO_ENABLED0 set GOOSwindows set GOARCHamd64 go build -o main-windows.exe main.go 2.在window下打包成linux 在项目main.go同级目…

Android Codec2 CCodec(十六)C2AllocatorGralloc

这一篇文章我们一起来瞧瞧2D&#xff08;Graphic&#xff09; buffer分配器C2AllocatorGralloc是如何工作的。 1、GraphicBuffer 在Android系统中&#xff0c;GraphicBufferAllocator和GraphicBufferMapper是与图形缓冲区&#xff08;Graphic Buffers&#xff09;管理相关的重…

Python爬取b站视频:验证cookie是否有效

具体代码 import requestsheaders {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0,Referer: https://www.bilibili.com/,Origin: https://www.bilibili.com } def readCooki…

Nginx02-安装

零、文章目录 Nginx02-安装 1、Nginx官网 Nginx官网地址&#xff1a;http://nginx.org/ 2、Nginx下载 &#xff08;1&#xff09;Nginx下载 下载页地址&#xff1a;http://nginx.org/en/download.html &#xff08;2&#xff09;更老版本下载 下载页地址&#xff1a;http…

四、链表————相关算法探讨(持续更新中)

链表中相关算法探讨 前言一、移除链表元素1.1 思路分析1.2 解法探讨1.2.1 直接删除1.2.2 创建虚拟头节点来删除1.2.3 递归版删除 二、反转列表2.1 思路分析2.2 做法2.2.1 创建新链表方式2.2.2 双指针法2.2.3 递归法 三、两两交换链表中的节点3.1 思路分析3.2 解法探讨3.2.1 不使…

重磅 | 清华大学刘知远老师领衔的大模型公开课2024年第二季来了!助教阵容强大,零基础大模型从入门到精通,看这个就够了!

本文由readlecture.cn转录总结。ReadLecture专注于音、视频转录与总结&#xff0c;2小时视频&#xff0c;5分钟阅读&#xff0c;加速内容学习与传播。 更多讲座、采访干货内容&#xff0c;欢迎关注公众号“ReadLecture”获取&#xff01;公众号后台直接回复&#xff0c;可与公众…

RK3568笔记六十四:SG90驱动测试

若该文为原创文章,转载请注明原文出处。 前面有测试过PWM驱动,现在使用两种方式来产生PWM驱动SG90,实现舵机旋转任意角度 方法一:使用硬件PWM 方法二:使用高精度定时器,GPIO模拟PWM. 一、PWM子系统框架 二、SG90控制方法 舵机的控制需要MCU产生一个周期为20ms的脉冲信号…

python实现DES算法

DES算法 一、算法介绍1.1 背景1.2 原理1.3 基本功能函数1.3.1 初始置换函数 I P IP IP1.3.2 f f f 轮函数1.3.3 逆初始置换函数 I P − 1 IP^{-1} IP−1 1.4 子密钥的生成 二、代码实现2.1 子密钥生成实现2.2 DES加解密实现2.3 完整代码 三、演示效果 一、算法介绍 1.1 背景…

SpringBoot框架在旅游管理中的应用与实践

第三章 系统分析 3.1可行性分析 对所有的系统来说&#xff0c;都有可能会受到时间和空间上的制约。所以&#xff0c;我们在设计每一个项目的时候&#xff0c;必须对该系统实行可行性分析&#xff0c;这样不但能够降低项目的危害&#xff0c;还能改降低人力、物力和财力的损耗。…

C++(十七) 多态

一、 多态概念 多态&#xff08;polymorphism&#xff09;&#xff0c;通俗来说&#xff0c;就是多种形态。多态分为编译时多态&#xff08;静态多态&#xff09;和运行时多态&#xff08;动态多态&#xff09;。这里我们重点讲运行时多态&#xff0c;同时简单介绍编译时多态。…

swagger2.9.2 和 springboot3.3.4版本冲突问腿

swagger2.9.2 和 springboot3.3.4版本冲突问腿 问题描述&#xff1a;当我们使用 swagger 2.9.2版本的时候&#xff0c;如果恰好我们使用的 springboot 版本是3.x版本&#xff0c;会出现启动报错的问题 解决办法&#xff1a;直接使用swagger 3.x 版本和 springboot 3.x 版本 …

window 安装永洪BI Desktop版本教程

本教程基于永洪BI Desktop 10.2 一、下载软件包 &#xff08;下载需要注册&#xff0c;以便接收License邮件激活码&#xff09;&#xff0c;地址如下&#xff1a;桌面智能数据分析工具_vividime Desktop数据分析软件-永洪科技vividime Desktop是一款轻量级桌面智能数据分析工具…

【探测器】线阵相机中的 TDI 技术

【探测器】线阵相机中的 TDI 技术 1.背景2.TDI相机3.场景应用 1.背景 TDI 即Time Delay Integration时间延迟积分。 TDI相机是线阵相机的一种特殊类型&#xff0c;带有独特的时间延迟积分&#xff08;TDI&#xff09;技术。 换句话说&#xff0c;TDI相机是线阵相机的一个高级版…

HCIP-HarmonyOS Application Developer 习题(七)

&#xff08;判断&#xff09;1、HarmonyOs跨端迁移和多端协同&#xff0c;是使用不同的FA/PA&#xff0c;在不同设备间运行来实现完整的业务。 答案&#xff1a;错误 分析&#xff1a; &#xff08;判断&#xff09;2、HarmonyOs的方舟开发框架包含基于TS扩展的类Web开发范式…

【RPC】—Thrift协议 VS Protobuf

Thrift协议 & VS Protobuf ⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记仓库&#x1f449;https://github.com/A-BigTree/tree-learning-notes 个人主页&#x1f449;https://www.abigtree.top ⭐⭐⭐⭐⭐⭐ 文章目录 Thrift协议 & VS Pro…

云原生(四十九) | WordPress源码部署

文章目录 WordPress源码部署 一、WordPress部署步骤 二、创建项目目录 三、上传源码到WordPress 四、配置安全组 五、配置WordPress 六、访问WordPress WordPress源码部署 一、WordPress部署步骤 第一步&#xff1a;创建项目目录 第二步&#xff1a;上传源码到项目目…

ARM(5)内存管理单元MMU

一、虚拟地址和物理地址 首先&#xff0c;计算机系统的内存被组成一个由M个连续的字节大小组成的数组。每字节都会有一个唯一的物理地址。CPU访问内存最简单的方式就是使用物理地址。如下图&#xff1a; 图 1 物理地址,物理寻址 而现在都是采用的都是虚拟寻址的方法。CPU生成一…