Android MVVM+Clean架构简介

本文主要介绍Android开发中MVVM Clean架构。

一、ViewModel

ViewModel 类是一种业务逻辑或屏幕级状态容器。它用于将状态公开给界面,以及封装相关的业务逻辑。 它的主要优点是,它可以缓存状态,并可在配置更改后持久保留相应状态。这意味着在 activity 之间导航时或进行配置更改后(例如旋转屏幕时),界面将无需重新提取数据。

在发生配置改变时 Activity 和 Fragment 会被销毁重建,它们内部的临时性数据(不是通过 Intent 传入的数据)就会丢失. 如果把这些临时数据放到 ViewModel 中, 则可以避免数据的丢失。当然也可以利用 onSaveInstanceState 来保留临时数据,但是如果临时数据的量较大,onSaveInstanceState 由于涉及了跨进程通信,较大的数据量会造成 marshalling 和 unmashlling 消耗较大。而利用 ViewModel 其实是没有跨进程通信的消耗。但是它没有 onSaveInstanceState 提供的 Activity 被回收之后的数据恢复功能:在 Activity 位于后台时系统会在内存不足时将其回收,当 Activity 再次回到前台时,系统会把 onSaveInstanceState 中保存的数据通过 onRestoreInstanceState 和 onCreate 里的 savedInstanceState 参数传递给 Activity。

二、LiveData

LiveData是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 activity、fragment 或 service)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。

使用 LiveData 具有以下优势:

·确保界面符合数据状态

LiveData 遵循观察者模式。当底层数据发生变化时,LiveData 会通知 Observer 对象。您可以整合代码以在这些 Observer 对象中更新界面。这样一来,您无需在每次应用数据发生变化时更新界面,因为观察者会替您完成更新。

·不会发生内存泄漏

观察者会绑定到 Lifecycle 对象,并在其关联的生命周期遭到销毁后进行自我清理。

·不会因 Activity 停止而导致崩溃

如果观察者的生命周期处于非活跃状态(如返回堆栈中的 activity),它便不会接收任何 LiveData 事件。

不再需要手动处理生命周期

界面组件只是观察相关数据,不会停止或恢复观察。LiveData 将自动管理所有这些操作,因为它在观察时可以感知相关的生命周期状态变化。

·数据始终保持最新状态

如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。

·适当的配置更改

如果由于配置更改(如设备旋转)而重新创建了 Activity 或 Fragment,它会立即接收最新的可用数据。

三、Lifecycle

生命周期感知型组件可执行操作来响应另一个组件(如 Activity 和 Fragment)的生命周期状态的变化。

class MyActivity extends AppCompatActivity {private MyLocationListener myLocationListener;public void onCreate(...) {myLocationListener = new MyLocationListener(this, location -> {// update UI});}@Overridepublic void onStart() {super.onStart();Util.checkUserStatus(result -> {// what if this callback is invoked AFTER activity is stopped?if (result) {myLocationListener.start();}});}@Overridepublic void onStop() {super.onStop();myLocationListener.stop();}
}

在我们需要执行长时间运行的操作(如 onStart() 中的某种配置检查)时尤其如此。这可能会导致出现一种竞态条件,在这种条件下,onStop() 方法会在 onStart() 之前结束,这使得组件留存的时间比所需的时间要长。

public class MyObserver implements DefaultLifecycleObserver {@Overridepublic void onResume(LifecycleOwner owner) {connect()}@Overridepublic void onPause(LifecycleOwner owner) {disconnect()}
}
myLifecycleOwner.getLifecycle().addObserver(new MyObserver());

四、DataBinding

使用声明性格式将布局中的界面组件绑定到应用中的数据源。

@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);dataBinding = DataBindingUtil.setContentView(this, layout());dataBinding.setVariable(getBindingVariable(), getViewModel());dataBinding.setLifecycleOwner(this);liveDataObserve();}

布局中修改如下:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"><data><import type="com.example.mvvm.ui.main.MainViewModel" /><variablename="viewModel"type="MainViewModel" /></data><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><com.google.android.material.button.MaterialButtonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="50sp"android:onClick="@{()->viewModel.getUsers()}"android:text="json获取"/><com.google.android.material.button.MaterialButtonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="50sp"android:onClick="@{()->viewModel.saveUsers()}"android:text="数据库保存"/><com.google.android.material.button.MaterialButtonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="50sp"android:onClick="@{()->viewModel.getUserDetail()}"android:text="数据库获取"/><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/rv_user"android:layout_width="match_parent"android:layout_height="match_parent"/></LinearLayout>
</layout>

BindingAdapter :绑定适配器,是 Jetpack DataBinding 中用来扩展布局 xml 属性行为的注解,允许你针对布局 xml 中的一个或多个属性进行绑定行为扩展,这个属性可以是自定义属性,也可以是原生属性。

public class ImageHelper {@BindingAdapter({"imageUrl"})public static void loadImage(ImageView imageView,String url){Glide.with(imageView.getContext()).load(url).error(R.mipmap.ff).placeholder(R.mipmap.ff).into(imageView);}

对应布局中的view则是:

    <ImageViewandroid:layout_width="90dp"android:layout_height="90dp"android:scaleType="centerCrop"app:imageUrl="@{url}"/>

五、什么是Clean Architecture

Clean Code 的作者 Robert C. Martin (Uncle Bob),写了一本书 Clean Architecture(《代码简洁之道》),从而提出了这个架构 (Clean Architecture)。

代码分为三个独立的层:

  1. Presentation Layer
  2. Domain Layer
  3. Data Layer

Presentation Layer

这部分主要包括我们的Activity,Fragment,ViewModel。Activity与Domain层通讯执行操作,不直接和Data层通讯。用户所有的操作(如点击事件等)在Presentation层通过ViewModel向下传递。

@Injectpublic MainViewModel(GetUserListCase getUserListCase, GetUserDetailCase getUserDetailCase, SaveUserListCase saveUserListCase) {this.getUserListCase = getUserListCase;this.getUserDetailCase = getUserDetailCase;this.saveUserListCase = saveUserListCase;}public void getUsers() {this.getUserListCase.execute(new UserListObserver(), GetUserListCase.Params.forFileName("users.json"));}public void saveUsers() {this.saveUserListCase.execute(new SaveUserListObserver(), SaveUserListCase.Params.forFileName("users.json"));}public void getUserDetail() {this.getUserDetailCase.execute(new UserDetailsObserver(), GetUserDetailCase.Params.forUser(1));}

Domain Layer

这一层主要包含我们所有的Use Case。我们定义一个抽象类,所有的case都要继承这个UseCase,以规范UseCase的使用。基类UseCase会对入参和出参进行规范,入参需要实现IRequestValues接口,出参要实现IResponseValue接口。

public class GetUserListCase extends UseCase<GetUserListCase.Params,GetUserListCase.UsersEntity > {private UserRepository userRepository;@Injectpublic GetUserListCase(UserRepository userRepository) {super();this.userRepository = userRepository;}@Overrideprotected Observable<UsersEntity> buildUseCaseObservable(GetUserListCase.Params params) {return userRepository.getUsers(params.fileName);}public static final class UsersEntity implements IResponseValue {private List<UserViewEntity> users;public List<UserViewEntity> getUsers() {return users;}public void setUsers(List<UserViewEntity> users) {this.users = users;}}public static final class Params implements IRequestValues {private final String fileName;private Params(String fileName) {this.fileName = fileName;}
}

UseCase是ViewModel和Repository之间的媒介,当我们需要新增功能时,只需要新增一个UseCase。UseCase面向接口,调用需要使用的Repository接口类中的对应方法。UseCase中通过Hitl依赖注入,调用对应的Repository接口类的实现类。

单个UseCase只应该具备单个的功能,如:获取用户的列表,并且对应User的Repository中单个方法(获取用户的列表)如果需要获取单个用户的详情等场景,需要增加UseCase(如GetUserDetailCase),并调用Repository对应的方法。

Data Layer

这里具有Domain层可以使用的所有存储库。此层向外部类公开数据源 API。

public class UserDataRepository implements IUserRepository {private final Context context;@InjectGson gson;@InjectUserDataMapper userDataMapper;@InjectAppDbHelper appDbHelper;@InjectUserDataRepository(@ApplicationContext Context context) {this.context = context;}@Overridepublic Observable<List<UserViewEntity>> getUsers(String fileName) {return LocalJsonResolutionUtils.getJson(context, fileName).map(it ->userDataMapper.transformList(gson.fromJson(it, new TypeToken<List<UserModel>>() {}.getType())));}@Overridepublic Observable<UserViewEntity> getUserById(int userId) {return appDbHelper.getUserDetail(userId).map(this.userDataMapper::transform);}@Overridepublic Observable<Void> saveUserList(String fileName) {return LocalJsonResolutionUtils.getJson(context, fileName).map(it -> {appDbHelper.saveUserList(gson.fromJson(it, new               TypeToken<List<UserModel>>() {}.getType()));return null;});}
}

UserDataRepository可以区分从网络或者本地获取数据,当获取的数据发生改变时,只需要修改此处的代码,并进行相应的转换即可,而不需要改变上层代码。

UserDataRepositoryIUserRepository接口的具体实现,后续根据业务需要,可以在IUserRepository中新增接口。在业务变动较大的情况下,可以新增IUserRepository的接口实现类,然后通过Hilt对外提供注入,这样只需要修改data层,上层的调用可以基本保持不变。

六、总结Clean架构

优点:

  • 代码比使用普通 MVVM 更容易测试。
  • 代码进一步解耦(最大的优势)。
  • 包结构更易于导航。
  • 该项目甚至更容易维护。
  • 可以更快地添加新功能。

缺点:

  • 它添加了很多额外的类,因此对于低复杂度的项目来说并不理想。

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

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

相关文章

音频筑基:窄带、宽带、超宽带、全带一次说透

音频筑基&#xff1a;窄带、宽带、超宽带、全带一次说透 窄带、宽带、超宽带、全带定义参考资料 音频信号中&#xff0c;经常遇到窄带、宽带等说法&#xff0c;本文进行一个小结归类。 窄带、宽带、超宽带、全带定义 窄带、宽带到全带&#xff0c;总体来说是&#xff0c;指对音…

Android JNI中设置全局的jbyteArray

在JNI的代码文件中声明一个全局变量&#xff0c;用来存储jbyteArray对象。你可以在JNI的头文件中定义该变量。 static jbyteArray globalByteArray;在JNI的某个函数中&#xff0c;将传递的jbyteArray对象赋值给全局变量。 JNIEXPORT void JNICALL Java_com_example_MyClass_set…

【遥感专题系列】影像信息提取之——基于专家知识的决策树分类

可以将多源数据用于影像分类当中&#xff0c;这就是专家知识的决策树分类器&#xff0c;本专题以ENVI中Decision Tree为例来叙述这一分类器。 本专题包括以下内容&#xff1a; 专家知识分类器概述知识&#xff08;规则&#xff09;定义ENVI中Decision Tree的使用 概述 基于知…

数据结构与算法——队列

概述 计算机科学中&#xff0c;queue 是以顺序的方式维护的一组数据集合&#xff0c;在一端添加数据&#xff0c;从另一端移除数据。添加的一端称为尾&#xff0c;移除的一端称为头。 功能 插入offer(value : E) : boolean  取值并移除poll() : E  取值peek() : E  判断…

LeetCode 40.组合总和 II

组合总和 II 给定一个候选人编号的集合 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的每个数字在每个组合中只能使用 一次 。 注意&#xff1a;解集不能包含重复的组合。 方法一、回溯 由于题目要求解集…

实体识别与分类方法综述

目录 前言1 实体识别简介2 基于模板和规则的方法3 基于序列标注的方法3.1 常见序列标注模型3.2 模型参数估计和学习问题3.3 常见序列预测模型 4. 基于深度学习的实体识别方法5 基于预训练语言模型的实体识别5.1 BERT、GPT等预训练语言模型5.2 解码策略 6 特殊问题与挑战6.1 标签…

如何提高记忆力?

许多学员经常问我&#xff1a;为什么您的记忆力那么好&#xff1f;有没有什么方法&#xff0c;可以提高记忆力&#xff1f; 今天&#xff0c;我想好好聊聊这个问题。 当然&#xff0c;学习和记忆&#xff0c;是一个巨大的话题。这篇文章只是一个初探。希望能帮你打开一些视野&a…

关于java中static详解

关于java中static详解 我们接触static的时候不是在学习面向对象的时候接触的&#xff0c;是在学习方法的时候就有过接触&#xff0c;我们之前对static的了解只是静态的修饰&#xff0c;本篇文章我们对static这个修饰符做一个详细的理解&#x1f600;。 static 如果在属性上&a…

深入理解Redis:如何设置缓存数据的过期时间及其背后的机制

目录 Redis 给缓存数据设置过期时间 Redis是如何判断数据是否过期的呢&#xff1f; 过期的数据的删除策略 Redis 内存淘汰机制 Redis 给缓存数据设置过期时间 一般情况下&#xff0c;我们设置保存的缓存数据的时候都会设置一个过期时间。为什么呢&#xff1f; 因为内存是有…

算法训练营Day48(动态规划9)

说明 今天就是打家劫舍的一天&#xff0c;这个系列不算难&#xff0c;可以一口气拿下。 198.打家劫舍 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 class Solution:def rob(self, nums: List[int]) -> int:if len(nums) 0: # 如果没有房屋&a…

电流检测电路设计方案汇总

电流检测电路设计方案&#xff08;一&#xff09; 低端检流电路的检流电阻串联到地&#xff08;图1&#xff09;&#xff0c;而高端检流电路的检流电阻是串联到高电压端&#xff08;图2&#xff09;。两种方法各有特点&#xff1a;低端检流方式在地线回路中增加了额外的线绕电…

​ElasticSearch

目录 简介 基本概念 倒排索引 FST 简介 ES是一个基于lucene构建的&#xff0c;分布式的&#xff0c;RESTful的开源全文搜索引擎。支持对各种类型的数据的索引&#xff1b;搜索速度快&#xff0c;可以提供实时的搜索服务&#xff1b;便于水平扩展&#xff0c;每秒可以处理 …

等保2.0 MySQL 5.7 配置修改

MySQL 5.7 等保2.0 配置修改 设置 connection_control 插件修改 root 用户名开启普通日志和二进制日志 设置 connection_control 插件 安装插件 # 登录数据库 mysql -u root -p# windows 下 INSTALL PLUGIN CONNECTION_CONTROL SONAME connection_control.dll INSTALL PLUGIN…

响应式Web开发项目教程(HTML5+CSS3+Bootstrap)第2版 例5-1事件处理

代码 <!doctype html> <html> <head> <meta charset"utf-8"> <title>事件处理</title> </head><body> <input id"btn" type"button" name"btn" value"提交" /> <…

深入理解ZooKeeper分布式锁

第1章&#xff1a;引言 分布式系统&#xff0c;简单来说&#xff0c;就是由多台计算机通过网络相连&#xff0c;共同完成任务的系统。想象一下&#xff0c;咱们平时上网浏览网页、看视频&#xff0c;背后其实都是一大堆服务器在协同工作。这些服务器之间需要协调一致&#xff…

【golang】slice赋值null slice不使用零值 | go slice append 头插 尾插

一、slice 传 null 1、如何禁止零值 众所周知go如果初始化都会自带零值效果 比如一个切片我们在传送过程中&#xff0c;如果被占位且不想传值为零值 我们就需要使用* 1.1、定义一个带零值的slice 定义如下&#xff1a; slice make([]float64, 5)这样会输出&#xff1a; [0,…

小游戏选型(二):第三方社交小游戏厂家对比,即构/声网/融云/云信等

前言&#xff1a; 上一篇文章我们主要介绍社交游戏化趋势&#xff0c;并分析了直播平台面临的买量贵、变现难等问题&#xff0c;探讨了小游戏作为新的运营变现玩法的优势。同时还列举了各大直播平台TOP5的小游戏。今天我们继续介绍小游戏系列内容&#xff0c;本文是该系列的第…

浪花 - 添加队伍业务开发

一、接口设计 1. 请求参数&#xff1a;封装添加队伍参数 TeamAddRequest package com.example.usercenter.model.request;import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.ann…

勤学苦练“prompts“,如沐春风“CodeArts Snap“

前言 CodeArts Snap 上手一段时间了&#xff0c;对编程很有帮助。但是&#xff0c;感觉代码编写的不尽人意。 我因此也感到困惑&#xff0c;想要一份完整的 CodeArts Snap 手册看看。 就在我感觉仿佛"独自彷徨在这条悠长、悠长又寂寥的雨巷"时&#xff0c;我听了大…

【数据库】聊聊explain如何优化sql以及索引最佳实践

在实际的开发中&#xff0c;我们难免会遇到一些SQL优化的场景&#xff0c;虽然之前也看过周阳的课程&#xff0c;但是一直没有进行细心的整理&#xff0c;所以本篇会进行详细列举explain的相关使用&#xff0c;以及常见的索引最佳实践&#xff0c;并通过案例进行讲解。 数据准…