MVXArchitecture

MVC

View:XML布局文件。 Model:实体模型(数据的获取、存储、数据状态变化)。 Controller:对应于Activity,处理数据、业务和UI。

从上面这个结构来看,Android本身的设计还是符合MVC架构的,但是Android中纯粹作为View的XML视图功能太弱,我们大量处理View的逻辑只能写在Activity中,这样Activity就充当了View和Controller两个角色,直接导致Activity中的代码大爆炸。相信大多数Android开发者都遇到过一个Acitivty数以千行的代码情况吧!所以,更贴切的说法是,这个MVC结构最终其实只是一个Model-View(Activity:View&Controller)的结构。

MVP

**View: **对应于Activity和XML,负责View的绘制以及与用户的交互。 **Model: **依然是实体模型。 **Presenter: **负责完成View与Model间的交互和业务逻辑。

前面我们说,Activity充当了View和Controller两个角色,MVP就能很好地解决这个问题,其核心理念是通过一个抽象的View接口(不是真正的View层)将Presenter与真正的View层进行解耦。Persenter持有该View接口,对该接口进行操作,而不是直接操作View层。这样就可以把视图操作和业务逻辑解耦,从而让Activity成为真正的View层。

但MVP也存在一些弊端:

  • Presenter(以下简称P)层与View(以下简称V)层是通过接口进行交互的,接口粒度不好控制。粒度太小,就会存在大量接口的情况,使代码太过碎版化;粒度太大,解耦效果不好。同时对于UI的输入和数据的变化,需要手动调用V层或者P层相关的接口,相对来说缺乏自动性、监听性。如果数据的变化能自动响应到UI、UI的输入能自动更新到数据,那该多好!
  • MVP是以UI为驱动的模型,更新UI都需要保证能获取到控件的引用,同时更新UI的时候要考虑当前是否是UI线程,也要考虑Activity的生命周期(是否已经销毁等)。
  • MVP是以UI和事件为驱动的传统模型,数据都是被动地通过UI控件做展示,但是由于数据的时变性,我们更希望数据能转被动为主动,希望数据能更有活性,由数据来驱动UI。
  • V层与P层还是有一定的耦合度。一旦V层某个UI元素更改,那么对应的接口就必须得改,数据如何映射到UI上、事件监听接口这些都需要转变,牵一发而动全身。如果这一层也能解耦就更好了。
  • 复杂的业务同时也可能会导致P层太大,代码臃肿的问题依然不能解决。

MVVM

**View: **对应于Activity和XML,负责View的绘制以及与用户交互。 **Model: **实体模型。 **ViewModel: **负责完成View与Model间的交互,负责业务逻辑。

MVVM的目标和思想与MVP类似,利用数据绑定(Data Binding)、依赖属性(Dependency Property)、命令(Command)、路由事件(Routed Event)等新特性,打造了一个更加灵活高效的架构。

数据驱动

在常规的开发模式中,数据变化需要更新UI的时候,需要先获取UI控件的引用,然后再更新UI。获取用户的输入和操作也需要通过UI控件的引用。在MVVM中,这些都是通过数据驱动来自动完成的,数据变化后会自动更新UI,UI的改变也能自动反馈到数据层,数据成为主导因素。这样MVVM层在业务逻辑处理中只要关心数据,不需要直接和UI打交道,在业务处理过程中简单方便很多。

低耦合度

MVVM模式中,数据是独立于UI的。

数据和业务逻辑处于一个独立的ViewModel中,ViewModel只需要关注数据和业务逻辑,不需要和UI或者控件打交道。UI想怎么处理数据都由UI自己决定,ViewModel不涉及任何和UI相关的事,也不持有UI控件的引用。即便是控件改变了(比如:TextView换成EditText),ViewModel也几乎不需要更改任何代码。它非常完美的解耦了View层和ViewModel,解决了上面我们所说的MVP的痛点。

更新UI

在MVVM中,数据发生变化后,我们在工作线程直接修改(在数据是线程安全的情况下)ViewModel的数据即可,不用再考虑要切到主线程更新UI了,这些事情相关框架都帮我们做了。

团队协作

MVVM的分工是非常明显的,由于View和ViewModel之间是松散耦合的:一个是处理业务和数据、一个是专门的UI处理。所以,完全由两个人分工来做,一个做UI(XML和Activity)一个写ViewModel,效率更高。

可复用性

一个ViewModel可以复用到多个View中。同样的一份数据,可以提供给不同的UI去做展示。对于版本迭代中频繁的UI改动,更新或新增一套View即可。如果想在UI上做A/B Testing,那MVVM是你不二选择。

MVVM:

MVVM架构介绍

MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。唯一的区别是,它采用双向数据绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。

MVVM架构图如下所示:

图片

可以看出MVVM与MVP的主要区别在于,你不用去主动去刷新UI了,只要Model数据变了,会自动反映到UI上。换句话说,MVVM更像是自动化的MVP。

MVVM的双向数据绑定主要通过DataBinding实现,不过相信有很多人跟我一样,是不喜欢用DataBinding的,这样架构就变成了下面这样。

图片

  1. View观察ViewModel的数据变化并自我更新,这其实是单一数据源而不是双向数据绑定,所以其实MVVM的这一大特性我其实并没有用到

  2. View通过调用ViewModel提供的方法来与ViewModel交互

小结

  1. MVC架构的主要问题在于Activity承担了View与Controller两层的职责,同时View层与Model层存在耦合
  2. MVP引入Presenter层解决了MVC架构的两个问题,View只能与Presenter层交互,业务逻辑放在Presenter层
  3. MVP的问题在于随着业务逻辑的增加,View的接口会很庞大,MVVM架构通过双向数据绑定可以解决这个问题
  4. MVVM与MVP的主要区别在于,你不用去主动去刷新UI了,只要Model数据变了,会自动反映到UI上。换句话说,MVVM更像是自动化的MVP。
  5. MVVM的双向数据绑定主要通过DataBinding实现,但有很多人(比如我)不喜欢用DataBinding,而是View通过LiveData等观察ViewModle的数据变化并自我更新,这其实是单一数据源而不是双向数据绑定

MVVM补充

链接:https://juejin.cn/post/6844904176296673287

数据视图互绑 + 长生命周期数据

即使将访问数据的细节剥离出Presenter,它依然不单纯。因为它持有 View 层接口,这就要求Presenter需了解 该把哪个数据传递给哪个接口方法,这就是 数据绑定,它在构建视图时就已经确定(无需等到数据返回),所以这个细节可以从业务层剥离,归并到视图层。

Presenter的实例被 Activity 持有,所以它的生命周期和 Activiy 同步,即业务数据和界面同生命周期。在某些场景下,这是一个缺点,比如横竖屏切换。此时,如果数据的生命周期不依赖界面,就可以免去重新获取数据的成本。这势必 需要一个生命周期更长的对象(ViewModel)持有数据。

生命周期更长的 ViewModel

最终的持有链如下:NonConfigurationInstances 持有 ViewModelStore 持有 ViewModel。

所以 ViewModel 生命周期比 Activity 更长。这样 ViewModel 中存放的业务数据就可以在 Activity 销毁重建时被复用。

上一节的例子中,构建 Presenter 是直接在 Activity 中 new,而构建ViewModel是通过ViewModelProvider.get():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ViewModelProvider {
// ViewModel 实例商店
private final ViewModelStore mViewModelStore;

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
// 从商店获取 ViewModel实例
ViewModel viewModel = mViewModelStore.get(key);

if (modelClass.isInstance(viewModel)) {
return (T) viewModel;
} else {
...
}
// 若商店无 ViewModel 实例 则通过 Factory 构建
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
viewModel = (mFactory).create(modelClass);
}
// 将 ViewModel 实例存入商店
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
}

ViewModel实例通过ViewModelStore获取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ViewModel 实例商店
public class ViewModelStore {
// 存储 ViewModel 实例的 Map
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);
}

...
}

ViewModelStoreViewModel实例存储在HashMap中。

ViewModelStore通过ViewModelStoreOwner获取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ViewModelProvider {
// ViewModel 实例商店
private final ViewModelStore mViewModelStore;

// 构造 ViewModelProvider 时需传入 ViewModelStoreOwner 实例
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
// 通过 ViewModelStoreOwner 获取 ViewModelStore
this(owner.getViewModelStore(), factory);
}

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
}

ViewModelStoreOwner实例又存储在哪?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Activity 基类实现了 ViewModelStoreOwner 接口
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
LifecycleOwner,
ViewModelStoreOwner,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner {

// Activity 持有 ViewModelStore 实例
private ViewModelStore mViewModelStore;

public ViewModelStore getViewModelStore() {
if (mViewModelStore == null) {
// 获取配置无关实例
NonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// 从配置无关实例中恢复 ViewModel商店
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}

// 静态的配置无关实例
static final class NonConfigurationInstances {
// 持有 ViewModel商店实例
ViewModelStore viewModelStore;
...
}
}

Activity 就是ViewModelStoreOwner实例,且持有ViewModelStore实例,该实例还会被保存在一个静态类中。

最终的持有链如下:NonConfigurationInstances 持有 ViewModelStore 持有 ViewModel。

所以 ViewModel 生命周期比 Activity 更长。这样 ViewModel 中存放的业务数据就可以在 Activity 销毁重建时被复用。

数据绑定

在 MVP 模式中,Presenter 持有 View 层接口并主动向界面推数据。

MVVM 模式中,ViewModel 不再持有 View 层接口,也不主动给界面推数据,而是界面被动地观察数据变化。

MVVM 这种更新界面的方式称为 “数据驱动”,即只需更新数据即可,因为界面会主动观察数据的变化并做出响应。

这使得 ViewModel 只需持有数据并根据业务逻辑更新之即可

MVVM中Activity 属于V层,布局构建以及数据绑定都在这层完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class MvvmActivity : AppCompatActivity() {
private var rvNews: RecyclerView? = null
private var newsAdapter = NewsAdapter()

// 构建布局
private val rootView by lazy {
ConstraintLayout {
TextView {
layout_id = "tvTitle"
layout_width = wrap_content
layout_height = wrap_content
textSize = 25f
padding_start = 20
padding_end = 20
center_horizontal = true
text = "News"
top_toTopOf = parent_id
}

rvNews = RecyclerView {
layout_id = "rvNews"
layout_width = match_parent
layout_height = wrap_content
top_toBottomOf = "tvTitle"
margin_top = 10
center_horizontal = true
}
}
}

// 构建 ViewModel 实例
private val newsViewModel by lazy {
// 构造 ViewModelProvider 实例, 通过其 get() 获得 ViewModel 实例
ViewModelProvider(this, NewsFactory(applicationContext)).get(NewsViewModel::class.java) }

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(rootView)
initView()
bindData()
}

// 将数据绑定到视图
private fun bindData() {
newsViewModel.newsLiveData.observe(this, Observer {
newsAdapter.news = it
rvNews?.adapter = newsAdapter
})
}

private fun initView() {
rvNews?.layoutManager = LinearLayoutManager(this)
}
}

其中构建布局 DSL 的详细介绍可以点击这里。它省去了原先V层( Activity + xml )中的 xml。

代码中的数据绑定是通过观察 ViewModel 中的 LiveData 实现的。这不是数据绑定的完全体,所以还需手动地观察 observe 数据变化(只有当引入data-binding包后,才能把视图和控件的绑定都静态化到 xml 中)。但至少它让 ViewModel 无需主动推数据了:

在 MVP 模式中,Presenter 持有 View 层接口并主动向界面推数据。

MVVM 模式中,ViewModel 不再持有 View 层接口,也不主动给界面推数据,而是界面被动地观察数据变化。

MVVM 这种更新界面的方式称为 “数据驱动”,即只需更新数据即可,因为界面会主动观察数据的变化并做出响应。

这使得 ViewModel 只需持有数据并根据业务逻辑更新之即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 数据访问接口在构造函数中注入
class NewsViewModel(var newsRepository: NewsRepository) : ViewModel() {
// 持有业务数据
val newsLiveData by lazy { newsRepository.fetchNewsLiveData() }
}

// 定义构造 ViewModel 方法
class NewsFactory(context: Context) : ViewModelProvider.Factory {
// 构造 数据访问接口实例
private val newsRepository = NewsRepositoryImpl(context)
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
// 将数据接口访问实例注入 ViewModel
return NewsViewModel(newsRepository) as T
}
}

// 然后就可以在 Activity 中这样构造 ViewModel 了
class MvvmActivity : AppCompatActivity() {
// 构建 ViewModel 实例
private val newsViewModel by lazy {
ViewModelProvider(this, NewsFactory(applicationContext)).get(NewsViewModel::class.java) }
}

ViewModel只关心业务逻辑和数据,不关心获取数据的细节,所以它们都被数据访问接口隐藏了。

Author

white crow

Posted on

2021-11-09

Updated on

2024-03-25

Licensed under