用代码手把手教你使用MVVM
来源:互联网 发布:数据恢复大师下载 编辑:程序博客网 时间:2024/06/06 03:15
源码请点击:https://github.com/shuaijia/JsMVVM
您还可以关注我的微信公众号——安卓干货营,与我交流和获取更多精彩内容。
概述
说到Android MVVM,就会联想到DataBinding框架。然而两者的概念是不一样的,不能混为一谈。MVVM是一种架构模式,而DataBinding是一个实现数据和UI绑定的框架,是构建MVVM模式的一个工具。
网上关于MVVM框架的搭建和使用的文章很少,大多提到MVVM框架,就是在介绍DataBinding的使用。对于MVVM中各模块之间如何划分,如何定义,又是如何配合实现高度解耦的文章更是少之又少。大家看完后还是一头雾水,只是对MVVM有个大概的了解,并不很清楚如何上手。
接下来,我们先认识什么是MVVM,然后再一步一步来设计整个MVVM框架。
MVC、MVP简介
MVC、MVP和MVVM都是在安卓开发中经常使用的模式,我们在认识MVVM之前先回顾一下MVC和MVP。
MVC
- View:xml布局
- Model:数据层,负责数据交互、存储和实体类定义
- Controller:业务处理层
Android开发本身还是比较符合MVC架构的,但是Android中纯粹作为View的XML视图功能太弱,我们大量处理View的逻辑只能写在Activity中,这样Activity就充当了View和Controller两个角色,直接导致Activity中的代码臃肿、混乱,导致阅读困难、重用困难和维护困难。相信大多数Android开发者都遇到过一个Acitivty数以千行的代码情况吧!所以,更贴切的说法是,这个MVC结构最终其实只是一个Model-View(Activity:View&Controller)的结构。
MVP
- View:xml文件及对应的Activity或Fragment,负责界面展示和交互
- Model:数据层,负责数据交互、存储和实体类定义
- Presenter:负责View层和Model层之间的逻辑处理
前面我们说,Activity充当了View和Controller两个角色,MVP就能很好地解决这个问题,其核心理念是通过一个抽象的View接口(不是真正的View层)将Presenter与真正的View层进行解耦。Persenter持有该View接口,对该接口进行操作,而不是直接操作View层。这样就可以把视图操作和业务逻辑解耦,从而让Activity成为真正的View层。
不足的是,MVP模式中定义了大量的接口,使得代码结构变大和复杂;MVP是UI和事件驱动,需要手动调用大量的方法来进行实现,缺乏自动性。
所以我们迎来了MVVM框架,当然得首先感谢google爸爸提供得DataBinding,真的是很强大!
MVVM简介
在MVVM模式中,将程序结构分为三层——View-ViewModel-Model,接下来我们一起来认识它们:
View:
View层负责和UI相关的工作,我们只在XML、Activity和Fragment写View层的代码,View层不进行业务处理,也就是我们在Activity不写业务逻辑和业务数据相关的代码。
更新UI通过数据绑定实现,尽量在ViewModel里面做,Activity要做的事就是初始化一些控件(如RecyclerView设置LayoutManager或者控件的显隐),View层可以通过数据来驱动更改UI,UI事件通过Command来绑定。
简而言之:View层不做任何业务逻辑、不涉及操作数据,UI和数据严格的分开。 UI更新和事件相应全部使用数据绑定,也就是DataBinding来实现。这就是MVVM和MVP、MVC很明显的不同之处。
ViewModel
ViewModel层做的事情刚好和View层相反,ViewModel只负责业务逻辑,不做任何和UI相关的事情。
同时DataBinding框架已经支持双向绑定,让我们可以通过双向绑定获取View层反馈给ViewModel层的数据,并对这些数据上进行操作。
事件的处理,我们也希望能把这些事件处理绑定到控件上,并把这些事件的处理统一化,为此我们通过使用BindingAdapter对一些常用的事件做封装,把一个个事件封装成一个个Command,对于每个事件我们用一个ReplyCommand去处理就行了,ReplyCommand会把你可能需要的数据带给你,这使得我们在ViewModel层处理事件的时候只需要关心处理数据就行了,具体见MVVM Light Toolkit 使用指南的Command部分。
Model
Model层不仅包括实体类的定义,还需要对数据进行处理和读写。例如:使用Retrofit或okHttp进行网络请求,或着如数据库操作等等。
优点:
- 数据驱动
- 低耦合
- 主线程更新UI
- 可复用性
- 方便单元测试
我们再来看下这张图:
简述下数据流走向:
View中使用DataBinding的Command来绑定事件和响应事件,触发网络请求;ViewModel进行分析处理,调用Model的数据请求方法;Model将收到的请求参数等信息封装,调用网络请求库;网络库(Retrofit等)与服务器进行交互;
服务器将json数据返回Retrofit等网络库,再返回到Model层中,ViewModel在回调中收到返回的实体类对象;
因为xml与实体类对象实现了双向绑定,实体类更新,使得UI更新!
ok!接下来我们就用活生生的例子来实现MVVM吧
A、实体类定义
/** * Description: * Created by jia on 2017/11/3. * 人之所以能,是相信能 */public class NewslistBean extends BaseObservable { private String ctime; private String title; private String description; private String picUrl; private String url; public NewslistBean(String ctime, String title, String description, String picUrl, String url) { this.ctime = ctime; this.title = title; this.description = description; this.picUrl = picUrl; this.url = url; } public String getCtime() { return ctime; } public void setCtime(String ctime) { this.ctime = ctime; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getPicUrl() { return picUrl; } public void setPicUrl(String picUrl) { this.picUrl = picUrl; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } @BindingAdapter("bind:imageUrl") public static void loadImage(ImageView imageView, String picUrl) { Glide.with(imageView.getContext()) .load(picUrl) .into(imageView); } public void onItemClick(View pView) { Toast.makeText(pView.getContext(), title, Toast.LENGTH_SHORT).show(); }}
这和平时写的实体类是不是没啥区别!
是的,所有的属性我们依旧如原来原来一样定义和设置get、set方法。但是,有一点不同的是实体类继承了BaseObservable,稍后我们再说。
B、Model类
/** * Description: 新闻网络请求model * Created by jia on 2017/11/3. * 人之所以能,是相信能 */public class NewsModel { public void getNews(final OnCallBack onCallBack) { HttpMethod.getInstance().getNews(new Subscriber<News>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { onCallBack.onFaile(e.toString()); } @Override public void onNext(News news) { onCallBack.onSuccess(news); } }); } public interface OnCallBack { void onSuccess(News news); void onFaile(String errorInfo); }}
这里呢,我使用的是自己封装过的Retrofit+RxJava的网络请求库,上面的Model用来进行新闻实体类News的网络请求;
也定义了一个CallBack接口:此回调可以让接下的ViewModel获得Model请求回来的实体类。
每个项目的网络请求库和方法都会不同,符合自己的就是最好的!(●ˇ∀ˇ●)
C、View
xml中
先看示例:
<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="news" type="com.jia.jsmvvm.home.viewmodel.NewslistBean" /> </data> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".home.view.MainActivity"> <android.support.v7.widget.CardView android:id="@+id/cv_tuijian" android:layout_width="match_parent" android:layout_height="130dp" android:layout_margin="15dp" android:background="#ffffff" android:elevation="5dp" android:onClick="@{news.onItemClick}" app:cardElevation="5dp"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="5dp"> <ImageView android:id="@+id/iv_tuijian" android:layout_width="120dp" android:layout_height="match_parent" android:layout_margin="15dp" android:scaleType="fitXY" app:imageUrl="@{news.picUrl}" /> <TextView android:id="@+id/tv_tuijian_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignTop="@id/iv_tuijian" android:layout_toRightOf="@id/iv_tuijian" android:ellipsize="end" android:maxLines="2" android:text="@{news.title}" android:textColor="#111111" android:textSize="18sp" /> <TextView android:id="@+id/tv_tuijian_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:ellipsize="end" android:lines="3" android:singleLine="true" android:text="@{news.ctime}" android:textColor="#777777" android:textSize="14sp" /> </RelativeLayout> </android.support.v7.widget.CardView> </RelativeLayout></layout>
大家可定已经发现了:布局的编写和往常比还是又较大变化的。
熟悉DataBinding的朋友可以直接跳过这趴。由于本人对DataBinding也不是特别熟练,所以也只能和大家分享自己了解的一点使用方法。DataBinding拥有非常强大的功能,想深入了解的可以网上搜索,当然,本人不久也会把自己了解的DataBinding的知识整理成一篇博客,敬请期待!
- 我们使用 layout 作为布局文件的跟节点
- layout中包含data节点和普通的布局
- data节点中创建variable
- variable中有两个“属性”:name和type
- type声明实体类,格式为 包名.类名
- name为type中的实体类定义“名字”,供以下布局中使用
- 定义了data属性后,就相当于xml布局已和实体类绑定
- 在控件中引用实体类属性的格式为: @{实体类.属性名}
- 在控件中引用实体类方法的格式为: @{实体类.方法名}
- 涉及到图片加载:在实体类中使用@BindingAdapter注解图偏加载方法,在布局中引用url即可
因为本篇文章重点在于讲述MVVM框架的使用,所以DataBinding只进行粗略简介,如有错误,还望大家及时提出,我们一起进步!
Activity中
在Activity中设置布局,我们不再使用Activity的setContentView方法,取而代之的是:DataBindingUtil.setContentView
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
所返回的变量类型怎么来的呢?
将使用了DataBinding的布局名字,去掉所有下划线,将所有单词首字母大写,直接进行拼接,最后加上 Binding即可!
View层中这样就可以了!哈哈!
D、ViewModel
ViewModel层大家比较不熟悉,他和MVC的Controller、MVP的Presenter到底有什么区别呢?
ViewModel类应该怎么写呢?
先看下代码:
/** * Description: 新闻ViewModel类 * Created by jia on 2017/11/3. * 人之所以能,是相信能 */public class NewsViewModel { public Activity activity; public ActivityMainBinding activityMainBinding; public NewslistBean news; public NewsModel model; private int num=1; public NewsViewModel(Activity activity, final ActivityMainBinding activityMainBinding) { this.activity = activity; this.activityMainBinding = activityMainBinding; model=new NewsModel(); model.getNews(new NewsModel.OnCallBack() { @Override public void onSuccess(News news) { activityMainBinding.setNews(news.getNewslist().get(0)); } @Override public void onFaile(String errorInfo) { news=new NewslistBean("error","error","error","error","error"); activityMainBinding.setNews(news); } }); }}
看看里边有些啥:
- Context或Activity对象(这个应该好理解把)
- 在Activity中创建的Binding对象
- 实体类对象
- Model层对象
- ChildViewModel(例如Activity中嵌套多个Fragment的情况)
将实体类对象通过setXXX方法,设置给Binding对象。
当事件触发时,Model进行网络请求,在回调中更新实体类,便可对应的更新UI界面。
总结
实例中只是一个简单的功能的展示,大家在熟悉了MVVM后可再深度封装。
本文主要讲解了一些本人再开发过程中总结的Android MVVM构建思想,更多是理论上各个模块如何分工、代码如何设计。虽然在现实生产中用Android MVVM模式开发还比较少,但是随着DataBinding 1.0的发布,相信在Android MVVM 这一领域会更多的人来尝试。
由于时间有限,能力有限,文中不免有错误或不足的地方,还请大家提出,我们一同进步!
源码请点击:https://github.com/shuaijia/JsMVVM
您还可以关注我的微信公众号——安卓干货营,获取更多精彩内容!
- 用代码手把手教你使用MVVM
- 手把手教你使用 axis1.4 生成客户端代码
- 手把手教你使用VSS
- 手把手教你使用Git
- 手把手教你使用Git
- 手把手教你使用Git
- 手把手教你使用Git
- 手把手教你使用Git
- 手把手教你使用Git
- 手把手教你使用git
- 手把手教你使用Git
- 手把手教你使用Git
- 手把手教你使用Git
- 手把手教你使用GiT
- 手把手教你使用Git
- 手把手教你使用Git
- 手把手教你使用Git
- 手把手教你使用Git
- 【经典】mysql ISSAM如何快速去掉某个表中的重复名称
- 面试题之java基础:equals()和hashCode()区别?
- 使用可变参数,实现函数,求函数参数的平均值
- MySQL子查询
- linux共享内存踩的坑
- 用代码手把手教你使用MVVM
- 2017年双十一周六——别样的项目经理培训经历
- 在java中String类为什么要设计成final?
- 一道题看清动态规划的前世今生(一)
- 在Windows10上搭建基于sublime text 3的C/C++开发环境
- SpringBoot集成elasticseach入门实战
- Gradle官方历史版本大全
- Linux 三个命令工具!
- Mybatis学习笔记(十八)【spring和mybatis整合】