开发8年Android大牛:构建Android MVVM应用程序其实只需这几步?

  • ViewModel与ViewModel的协作

  • ViewModel与View的协作

图 2 中ViewModel 和View 是通过绑定的方式连接在一起的,绑定的一种是数据绑定,一种是命令绑定。数据的绑定 DataBinding 已经提供好了,简单的定义一些ObservableField就能把数据和控件绑定在一起了(如TextView的text属性),但是DataBinding框架提供的不够全面,比如说如何让一个URL绑定到一个ImageView让这个ImageView能自动去加载url指定的图片,如何把数据源和布局模板绑定到一个ListView,让ListView可以不需要去写Adapter和ViewHolder 相关的东西,而只是通过简单的绑定的方式把ViewModel的数据源绑定到Xml的控件里面就能快速的展示列表呢?这些就需要我们做一些工作和简单的封装。关于事件绑定也是一样,MVVM Light Toolkit 做了简单的封装,对于每个事件我们用一个ReplyCommand去处理就行了,ReplyCommand会把可能你需要的数据带给你,这使得我们处理事件的时候也只关心处理数据就行了。

图 1 中ViewModel的模块中我们可以看出ViewModel类下面一般包含下面5个部分:

  • Context (上下文)

  • Model (数据模型Bean)

  • Data Field (数据绑定)

  • Command (命令绑定)

  • Child ViewModel (子ViewModel)

我们先来看下示例代码,然后在一一讲解5个部分是干嘛用的:

//context

private Activity context;

//model(数据模型Bean)

private NewsService.News news;

private TopNewsService.News topNews;

//数据绑定(data field)

public final ObservableField imageUrl = new ObservableField<>();

public final ObservableField html = new ObservableField<>();

public final ObservableField title = new ObservableField<>();

// 一个变量包含了所有关于View Style 相关的字段

public final ViewStyle viewStyle = new ViewStyle();

//命令绑定(command)

public final ReplyCommand onRefreshCommand = new ReplyCommand<>(() -> {

})

public final ReplyCommand onLoadMoreCommand = new ReplyCommand<>(§ -> {

});

//Child ViewModel

public final ObservableList itemViewModel = new ObservableArrayList<>();

/** * ViewStyle 关于控件的一些属性和业务数据无关的Style 可以做一个包裹,这样代码比较美观,

ViewModel 页面也不会有太多的字段。 **/

public static class ViewStyle {

public final ObservableBoolean isRefreshing = new ObservableBoolean(true);

public final ObservableBoolean progressRefreshing = new ObservableBoolean(true);

}

  • Context
Context 是干嘛用的呢,为什么每个ViewModel都最好需要持了一个Context的引用呢?ViewModel 不做和UI相关的事,不操作控件,也不更新UI,那为什么要有Context呢?原因主要有以下两点,当然也有其他用处,调用工具类、帮助类可能需要context参数等:
  • 通过图1中,我们发现ViewModel 通过传参给Model 然后得到一个Observable,其实这就是网络请求部分,做网络请求我们必须把Retrofit Service返回的Observable绑定到Context的生命周期上,防止在请求回来时Activity已经销毁等异常,其实这个Context的目的就是把网络请求绑定到当前页面的生命周期中。

  • 在图1中,我们可以看到两个ViewModel 之间的联系是通过Messenger来做,这个Messenger 是需要用到Context,这个我们后续会讲解。

  • Model
Model 是什么呢,其实就是数据原型,也就是我们用Json转过来的Java Bean,我们可能都知道,ViewModel要把数据映射到View中可能需要大量对Model的数据拷贝,拿Model 的字段去生成对应的ObservableField(我们不会直接拿Model的数据去做展示),这里其实是有必要在一个ViewModel 保留原始的Model引用,这对于我们是非常有用的,因为可能用户的某些操作和输入需要我们去改变数据源,可能我们需要把一个Bean 从列表页点击后传给详情页,可能我们需要把这个model 当做表单提交到服务器。这些都需要我们的ViewModel持有相应的model。
  • Data Field (数据绑定)
Data Field 就是需要绑定到控件上的ObservableField字段, 无可厚非这是ViewModel的必须品。这个没有什么好说,但是这边有一个建议:  
这些字段是可以稍微做一下分类和包裹的,比如说可能一些字段绑定到控件的一些Style属性上(如果说:长度,颜色,大小)这些根据业务逻辑的变化而动态去更改的,对于着一类针对View Style的的字段可以声明一个ViewStyle类包裹起来,这样整个代码逻辑会更清晰一些,不然ViewModel里面可能字段泛滥,不易管理和阅读性较差。而对于其他一些字段,比如说title,imageUrl,name这些属于数据源类型的字段,这些字段也叫数据字段,是和业务逻辑息息相关的,这些字段可以放在一块。
  • Command (命令绑定)
Command (命令绑定)说白了就是对事件的处理(下拉刷新,加载更多,点击,滑动等事件处理),我们之前处理事件是拿到UI控件的引用,然后设置Listener,这些Listener 其实就是Command,但是考虑到在一个ViewModel 写各种Listener 并不美观,可能实现一个Listener就需要实现多个方法,但是我们可能只想要其中一个有用的方法实现就好了。同时实现Listener 会拿到UI的引用,可能会去做一些和UI相关的事情,这和我们之前说的ViewModel 不持有控件的引用,ViewModel不更改UI 有相悖。更重要一点是实现一个Listener 可能需要写一些UI逻辑才能最终获取我们想要的,简单一点的比如说,你想要监听ListView滑到最底部然后触发加载更多的事件,这时候你就要在ViewModel里面写一个OnScrollListener,然后在里面的onScroll方法中做计算,计算什么时候ListView滑动底部了,其实ViewModel的工作并不想去处理这些事件,它专注做的应该是业务逻辑和数据处理,如果有一个东西它不需要你自己去计算是否滑到底部,而是在滑动底部自动触发一个Command,同时把当前列表的总共的item数量返回给你,方便你通过 page=itemCount/LIMIT+1去计算出应该请求服务器哪一页的数据那该多好啊。MVVM Light Toolkit 帮你实现了这一点:

public final ReplyCommand onLoadMoreCommand = new ReplyCommand<>((itemCount) -> {

int page=itemCount/LIMIT+1;

loadData(page.LIMIT)

});

接着在XML 布局文件中通过bind:onLoadMoreCommand绑定上去就行了

<android.support.v7.widget.RecyclerView

android:layout_width=“match_parent”

android:layout_height=“match_parent”

bind:onLoadMoreCommand=“@{viewModel.loadMoreCommand}”/>

当然Command并不是必须的,你完全可以依照你的习惯和喜好在ViewModel 写Listener,不过使用Command 可以使你的ViewModel 更简洁易读,你也可以自己定义更多的Command,自己定义其他功能Command,那么ViewModel的事件处理都是托管ReplyCommand来处理,这样的代码看起来会特别美观和清晰。

  • Child ViewModel (子ViewModel)
子ViewModel 的概念就是在ViewModel 里面嵌套其他的ViewModel,这种场景还是很常见的。比如说你一个Activity里面有两个Fragment,ViewModel 是以业务划分的,两个Fragment做的业务不一样,自然是由两个ViewModel来处理,Activity 本身可能就有个ViewModel 来做它自己的业务,这时候Activity的这个ViewModel里面可能包含了两个Fragment分别的ViewModel。这就是嵌套的子ViewModel。还有另外一种就是对于AdapterView 如ListView RecyclerView,ViewPager等。

//Child ViewModelpublic final

ObservableList itemViewModel = new ObservableArrayList<>();

它们的每个Item 其实就对应于一个ViewModel,然后在当前的ViewModel 通过ObservableList持有引用(如上述代码),这也是很常见的嵌套的子ViewModel。我们其实还建议,如果一个页面业务非常复杂,不要把所有逻辑都写在一个ViewModel,可以把页面做业务划分,把不同的业务放到不同的ViewModel,然后整合到一个总的ViewModel,这样做起来可以使我们的代码业务清晰,简短意赅,也方便后人的维护。

总得来说ViewModel 和View 之前仅仅只有绑定的关系,View层需要的属性和事件处理都是在xml里面绑定好了,ViewModel层不会去操作UI,只会操作数据,ViewModel只是根据业务要求处理数据,这些数据自动映射到View层控件的属性上。关于ViewModel类中包含哪些模块和字段,这个需要开发者自己去衡量,这边建议ViewModel 不要引入太多的成员变量,成员变量最好只有上面的提到的5种(context、model、…),能不进入其他类型的变量就尽量不要引进来,太多的成员变量对于整个代码结构破坏很大,后面维护的人要时刻关心成员变量什么时候被初始化,什么时候被清掉,什么时候被赋值或者改变,一个细节不小心可能就出现潜在的Bug。太多不清晰定义的成员变量又没有注释的代码是很难维护的。

2016 8月25日更新

我们会把UI控件的属性和事件都通过xml里面(如bind:text=@{…})绑定,但是如果一个业务逻辑要弹一个Dialog,但是你又不想在ViewModel里面做弹窗的事(ViewModel 不做UI相关的事)或者说改变ActionBar上面的图标的颜色,改变ActionBar按钮是否可点击,这些都不是写在xml里面(都是用java 初始化话),如何对这些控件的属性做绑定呢?我们先来看下代码:

public class MainViewModel implements ViewModel {

//true的时候弹出Dialog,false的时候关掉dialog

public final ObservableBoolean isShowDialog = new ObservableBoolean();

}

// 在View层做一个对isShowDialog改变的监听

public class MainActivity extends RxBasePmsActivity {

private MainViewModel mainViewModel;

@Override

protected void onCreate(Bundle savedInstanceState) {

mainViewModel.isShowDialog.addOnPropertyChangedCallback(new android.databinding.Observable.OnPropertyChangedCallback() {

@Override

public void onPropertyChanged(android.databinding.Observable sender, int propertyId) {

if (mainViewModel.isShowDialog.get()) {

dialog.show();

} else {

dialog.dismiss();

}

}

});

}

}

简单的说你可以对任意的ObservableField做监听,然后根据数据的变化做相应UI的改变,业务层ViewModel 只要根据业务处理数据就行,以数据来驱动UI。

  • ViewModel与Model的协作

从图1 中,Model 是通过Retrofit 去获取网络数据的,返回的数据是一个Observable( RxJava ),Model 层其实做的就是这些。那么ViewModel 做的就是通过传参数到Model层获取到网络数据(数据库同理)然后把Model的部分数据映射到ViewModel的一些字段(ObservableField),并在ViewModel 保留这个Model的引用,我们来看下这一块的大致代码(代码涉及到简单RxJava,如看不懂可以查阅入门一下):

//Model

private NewsDetail newsDetail;

private void loadData(long id) {

// Observable 用来获取网络数据

Observable<Notification<NewsDetailService.NewsDetail>> newsDetailOb =

RetrofitProvider.getInstance()

.create(NewsDetailService.class)

.getNewsDetail(id)

.subscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread())

// 将网络请求绑定到Activity 的生命周期

.compose(((ActivityLifecycleProvider) context).bindToLifecycle())

//变成 Notification 使我们更方便处理数据和错误

.materialize().share();

// 处理返回的数据

newsDetailOb.filter(Notification::isOnNext)

.map(n -> n.getValue())

// 给成员变量newsDetail 赋值,之前提到的5种变量类型中的一种(model类型)

.doOnNext(m -> newsDetail = m)

.subscribe(m -> initViewModelField(m));

// 网络请求错误处理

NewsListHelper.dealWithResponseError(

newsDetailOb.filter(Notification::isOnError)

.map(n -> n.getThrowable()));

}

//Model -->ViewModel

private void initViewModelField(NewsDetail newsDetail) {

viewStyle.isRefreshing.set(false);

imageUrl.set(newsDetail.getImage());

Observable.just(newsDetail.getBody())

.map(s -> s + “<style type=“text/css”>” + newsDetail.getCssStr())

.map(s -> s + “”)

.subscribe(s -> html.set(s));

title.set(newsDetail.getTitle());

}

以上代码基本把注释补全了,基本思路比较清晰,,Rxjava涉及的操作符都是比较基本的,如有不懂,可以稍微去入门,之后的源码里面ViewModel数据逻辑处理都是用Rxjava做,所以需要提前学习一下方便你看懂源码。

注:我们推荐使用MVVM 和 RxJava一块使用,虽然两者皆有观察者模式的概念,但是我们RxJava不使用在针对View的监听,更多是业务数据流的转换和处理。DataBinding框架其实是专用于View-ViewModel的动态绑定的,它使得我们的ViewModel 只需要关注数据,而RxJava 提供的强大数据流转换函数刚好可以用来处理ViewModel中的种种数据,得到很好的用武之地,同时加上Lambda表达式结合的链式编程,使ViewModel 的代码非常简洁同时易读易懂。

  • ViewModel与ViewModel的协作

在图 1 中 我们看到两个ViewModel 之间用一条虚线连接着,中间写着Messenger,Messenger 可以理解是一个全局消息通道,引入messenger最主要的目的就实现ViewModel和ViewModel的通信,也可以用做View和ViewModel的通信,但是并不推荐这样做。ViewModel主要是用来处理业务和数据的,每个ViewModel都有相应的业务职责,但是在业务复杂的情况下,可能存在交叉业务,这时候就需要ViewModel和ViewModel交换数据和通信,这时候一个全局的消息通道就很重要的。这边给出一个简单的例子仅供参考:

场景是这样的,你的MainActivity对应一个MainViewModel,MainActivity 里面除了自己的内容还包含一个Fragment,这个Fragment 的业务处理对应于一个FragmentViewModel,FragmentViewModel请求服务器并获取数据,刚好这个数据MainViewModel也需要用到,我们不可能在MainViewModel重新请求数据,这样不太合理,这时候就需要把数据传给MainViewModel,那么应该怎么传,彼此没有引用或者回调。那么只能通过全局的消息通道Messenger。

FragmentViewModel 获取消息后通知MainViewModel 并把数据传给它:

combineRequestOb.filter(Notification::isOnNext)

.map(n -> n.getValue())

.map(p -> p.first)

.filter(m -> !m.getTop_stories().isEmpty())

.doOnNext(m ->Observable.just(NewsListHelper.isTomorrow(date)).filter(b -> b).subscribe(b -> itemViewModel.clear()))

// 上面的代码可以不看,就是获取网络数据 ,通过send把数据传过去

.subscribe(m -> Messenger.getDefault().send(m, TOKEN_TOP_NEWS_FINISH));

MainViewModel 接收消息并处理:

Messenger.getDefault().register(activity, NewsViewModel.TOKEN_TOP_NEWS_FINISH, TopNewsService.News.class, (news) -> {

// to something…

}

在MainActivity onDestroy 取消注册就行了(不然导致内存泄露)

@Override

protected void onDestroy() {

super.onDestroy();

Messenger.getDefault().unregister(this);

}

当然上面的例子也只是简单的说明下,Messenger可以用在很多场景,通知,广播都可以,不一定要传数据,在一定条件下也可以用在View层和ViewModel 上的通信和广播。运用范围特别广,需要开发者结合实际的业务中去做更深层次的挖掘。

4、总结和源码###

  • 本篇博文讲解主要是一些个人开发过程中总结的Android MVVM构建思想,更多是理论上各个模块如何分工,代码如何设计,虽然现在业界使用Android MVVM模式开发还比较少,但是随着DataBinding 1.0 的发布,相信在Android MVVM 这块领域会更多的人来尝试,刚好最近用MVVM开发了一段时间,有点心得,写出来仅供参考。

  • 文中讲解的过程代码比较少,代码用到了自己开发的一个MVVM Light Toolkit 库,而且还是RxJava + Lambda 的代码,估计很多人看着都晕菜了,这边会把源码公布出来。如果你还没有尝试过用RxJava+Retrofit+DataBinding 构建Android MVVM 应用程序,那么你可以试着看一下这边的源码并且做一下尝试,说不定你会喜欢上这样的开发框架。

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**

[外链图片转存中…(img-Rv1qMDfJ-1715904359435)]

[外链图片转存中…(img-XkiJuwL5-1715904359436)]

[外链图片转存中…(img-9a1kwwo9-1715904359437)]

[外链图片转存中…(img-dKAuQqC3-1715904359437)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 7
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值