“终于懂了“系列:Jetpack AAC完整解析(四)MVVM - Android架构探索!

本文详细比较了MVP和MVVM架构模式在Android开发中的应用,介绍了它们的职责划分、实现思路,重点讲解了JetpackMVVM的组件交互和实际应用示例,以及其在数据驱动和解耦方面的优势。
摘要由CSDN通过智能技术生成
  • 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,视图模型,负责业务逻辑。

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

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

View 产生事件,使用 ViewModel进行逻辑处理后,通知Model更新数据,Model把更新的数据给ViewModel,ViewModel自动通知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发生重建时 无需重新向后台请求数据,节省了开销,让视图重建时更快展示数据。

首先,请查看下图,该图显示了所有模块应如何彼此交互: Jetpack MVVM 架构

各模块对应MVVM架构:

  • View层:Activity/Fragment
  • ViewModel层:Jetpack ViewModel + Jetpack LivaData
  • Model层:Repository仓库,包含 本地持久性数据 和 服务端数据

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

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

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

另外,图中所有的箭头都是单向的,例如View层指向了ViewModel层,表示View层会持有ViewModel层的引用,但是反过来ViewModel层却不能持有View层的引用。除此之外,引用也不能跨层持有,比如View层不能持有仓库层的引用,谨记每一层的组件都只能与它相邻层的组件进行交互。

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

3.2 实施

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

3.2.1 构建界面

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

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

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

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

public LiveData<List> getUserListLiveData() {
return userListLiveData;
}
public LiveData getLoadingLiveData() {
return loadingLiveData;
}

}

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

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

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

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

//UserListActivity.java

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

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

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

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

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

3.2.2 获取数据

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

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

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

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

public class UserRepository {

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

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

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

3.2.3 连接 ViewModel 与存储区

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

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

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

/**

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

loadingLiveData.setValue(true);

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

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

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

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

//UserListActivity.java
public class UserListActivity extends AppCompatActivity {
private UserListViewModel mUserListViewModel;
private ProgressBar mProgressBar;
private RecyclerView mRvUserList;
private UserAdapter mUserAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_list);

initView();
initViewModel();
getData();
observeLivaData();
}
private void initView() {…}

private void initViewModel() {
ViewModelProvider viewModelProvider = new ViewModelProvider(this);
mUserListViewModel = viewModelProvider.get(UserListViewModel.class);
}

/**

  • 获取数据,调用ViewModel的方法获取
    */
    private void getData() {
    mUserListViewModel.getUserInfo();
    }

private void observeLivaData() {…}

3.2.4 缓存数据

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

//UserRepository.java

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

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

到这里,Jetpack MVVM 就介绍完了。

最后

答应大伙的备战金三银四,大厂面试真题来啦!

这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

《960全网最全Android开发笔记》

《379页Android开发面试宝典》

包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

腾讯、字节跳动、阿里、百度等BAT大厂 2020-2021面试真题解析

资料收集不易,如果大家喜欢这篇文章,或者对你有帮助不妨多多点赞转发关注哦。文章会持续更新的。绝对干货!!!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

[外链图片转存中…(img-1IVpjVyi-1714807527214)]

腾讯、字节跳动、阿里、百度等BAT大厂 2020-2021面试真题解析

[外链图片转存中…(img-wklYUXB9-1714807527215)]

资料收集不易,如果大家喜欢这篇文章,或者对你有帮助不妨多多点赞转发关注哦。文章会持续更新的。绝对干货!!!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 8
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值