前言

  本文属于《Android构建MVVM》系列开篇,共六个篇章,详见目录树。

  该系列文章旨在为Android的开发者入门MVVM架构,掌握其基本开发模式。

  辅以讲解Android Architecture Components,使得更好的实现MVVM架构。

目录树

  • 《Android构建MVVM》系列(一) 之 MVVM架构快速入门

    • 前言
    • 分层思想
    • 什么是MVC/MVP?
    • MVVM是什么,与MVC/MVP有何区别?
    • Android Architecture Components(架构组件)
    • 一个MVVM的Demo
    • 结语
  • 《Android构建MVVM》系列(二) 之 架构组件LiveData
  • 《Android构建MVVM》系列(三) 之 架构组件ViewModel
  • 《Android构建MVVM》系列(四) 之 架构组件Room
  • 《Android构建MVVM》系列(五) 之 构建更好的Demo
  • 《Android构建MVVM》系列(六) 之 总结与展望

正文

一、分层思想

  分层是一种思想,同时也是一种架构模式。它的特点是专职,即某一层的职责是相同的、确定的;比如我们平时所谓的Dao、Controller层…他们都有明确的职责。

  分层思想具体表现为,通过抽象某一类的逻辑构成一个水平功能面,进而对上层提供API;多个层面相互依赖、配合提供整体解决方案。层与层之间的依赖关系是自上而下的,即上层依赖下层,下层不能依赖上层,最底层的组件没有依赖。

 初学者往往搞不明白为什么明明可以直接编码业务逻辑,还要去做所谓的分层架构呢?

其实对仅仅实现需求来说,用不用分层架构没有关系的,不分层照样可以实现;那么为什么我们还要徒增烦恼呢?有句话说的好:“存在即合理”,也就是说既然我们的前辈研究出了所谓的分层架构,并且沿用至今;那么就一定有它的优点,一定是解决了某一领域的痛点而诞生的

试想以下场景:当你的项目随着需求的增加和不断调整,不可避免的就要去改动已有的代码,如果项目规模不大还好,如果是个老古董项目,可能某个类里面上万行的代码,没有注释,没有采用分层架构,相信我,你会哭的

  分层架构虽说不能完完全全的解决项目程序复杂度高的问题,但是通过分层,将大的问题抽象分解成了小的功能面,局部化在每一层中,这样就有效的降低了单个问题的规模和复杂度;另外层与层之间也可以通过简单的调整,插入新的层面,用以满足不断变化的需求,同一层面来说也可近乎0成本的水平扩展;并且易于后期的Debug、测试。

二、什么是MVC/MVP?

  先来说说MVC吧,其实MVX模式都是分层思想的一种具体实现,上文提到的分层思想实际上是一种抽象层面的分层,着重表现在抽象和解耦。

  MVC实际上是一种分层思想的践行者和改进者,在GUI编程中,MVC已经有几十年的历史了。

 顾名思义M(Model)即数据模型层,Model层很有意思,对于服务端编程来说我们把MVC中的M极有可能是包括了业务处理(Service)和实体类的,对于客户端编程来说MVC中的M可能就仅仅是数据模型,当然以上的说法只是于我个人而言的体会,不代表广义立场。

  V(View)即视图层/表现层,主要负责数据的展示和用户的交互,C(Controller)即控制层,主要负责一些数据传递、请求转发、业务处理的委派。

  以上是标准意义上的MVC,对于Android来说:

Model:数据模型(实体类、持久化、IO)
View:布局文件
Controller:对应于Activity、Fragment,包含一些业务逻辑的处理

  这里我们会发现,Android的MVC事实上V层的职责一部分被C层承担了,比如一些Activity/Fragment中不可避免的一些交互逻辑等,这样就会导致C层既包含UI交互,又有网络请求、业务处理等;导致C层过于臃肿,不利于项目后期的维护和扩展。

  所以,MVP就应运而生了,MVP实际上是由MVC进化而来,它比较好的解决了MVC时代遗留的问题,MVP中的各层含义:

Model:数据模型(实体类、持久化、IO)
View:Activity/Fragment和布局文件
Presenter:负责完成View和Model之间的交互和业务逻辑

  其核心思想是:设计一个抽象的V层接口,并由具体的View实现该接口,P层内部维护一个该接口的实例引用,一般在构造函数中传递进来赋值(即View层初始化P层实例时),彼时P层即可通过调用该接口来完成对View层的操作,V层也因持有P层实例,可以进行业务逻辑处理委派。

三、MVVM是什么?与MVC/MVP有何区别?

  MVVM是对MVP/MVC的一种改进,既解决了MVC时代的职责不明的问题,也很好的解决了MVP模式中需要编写过多繁琐的接口,以及V层和P层互相依赖所产生的一些隐式问题。

  在MVVM中,各层含义如下:

Model:数据模型(实体类、持久化、IO)
View:Activity/Fragment和布局文件
ViewModel:业务逻辑的处理、数据的转换、连接M层和V层的桥梁

  看上去似乎和MVP中各层的职责是类似的,并没有显著的不同和改进;那么我们为何要使用MVVM架构呢?

  引入美团技术团队的一段解释

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

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

  3. 可复用、易测试、方便协同开发
    一个ViewModel可以复用到多个View中。同样的一份数据,可以提供给不同的UI去做展示。对于版本迭代中频繁的UI改动,更新或新增一套View即可。如果想在UI上做A/B Testing,那MVVM是你不二选择。
    MVVM的分工是非常明显的,由于View和ViewModel之间是松散耦合的:一个是处理业务和数据、一个是专门的UI处理。所以,完全由两个人分工来做,一个做UI(XML和Activity)一个写ViewModel,效率更高
    ViewModel层做的事是数据处理和业务逻辑,View层中关注的是UI,两者完全没有依赖。不管是UI的单元测试还是业务逻辑的单元测试,都是低耦合的。在MVVM中数据是直接绑定到UI控件上(部分数据是可以直接反映出UI上的内容),那么我们就可以直接通过修改绑定的数据源来间接做一些Android UI上的测试。

四、Android Architecture Components(架构组件)

  实现MVVM的方式和工具有很多,既可以使用Google在2015年推出的DataBinding库,亦或是其他。也可以选择Google IO 2017 推出的Android Architecture Components即架构组件,亦或是其他方式。

  本文采用的解决方案:使用Architecture Components架构MVVM。

  上图是官方给出的架构模型,包含以下组件:

  • 生命周期管理库 - Lifecycle

    • Lifecycle组件,为下面两个组件提供了生命周期感知的基础

    • LiveData组件,可观测的、可感知生命周期的数据

    • ViewModel组件,不依赖于View、提供UI数据、桥接持久层、业务逻辑

  • 数据持久化库 - Room,Sqlite的ORM

  事实上,Architecture Components实现了一个比较理想化的依赖方式,自上而下,单向依赖;VM层并不持有View层的任何引用,但却是生命周期感知的,在新版的AS中View也不用去实现某些接口或继承特定的类,AppCompatActivity已经帮你整合了这一切。

  另外,关于Repository的解释,它并不是架构组件的成员,但是Google推荐引入Repository层,来作为我们唯一的数据来源接口,我们从图中也很好理解,他的职责就是使VM层对数据来源是无感知的,包装了数据来源,提供统一的API,供上层透明化的调用。

  更多的关于Android Architecture Components的教程,欢迎关注我们后续的架构组件篇章。

五、一个MVVM的Demo

  下面我们通过设计App《每日美文》的Demo,并使用Architecture Components架构MVVM的方式去完成。

  这个Demo使用Kotlin开发,没接触过Kotlin的童鞋也不必担心,本文没有用到Kotlin的一些高级特性,只需要Google花个半小时时间学习基本的Kotlin语法便可无障碍阅读

  项目地址:https://github.com/xykjlcx/OneArticleDemo

1. 首先我们创建工程

项目创建完成后的目录结构

架构组件的相关依赖

// livedata viewmodeldef lifecycle_version = "1.1.1"implementation "android.arch.lifecycle:extensions:$lifecycle_version"implementation "android.arch.lifecycle:viewmodel:$lifecycle_version"implementation "android.arch.lifecycle:livedata:$lifecycle_version"implementation "android.arch.lifecycle:runtime:$lifecycle_version"annotationProcessor "android.arch.lifecycle:compiler:$lifecycle_version"

  PS:视图(布局xml)就不带大家看了,很简单,有兴趣的童鞋可以直接到Github上看源码

2. 工具类:OceanUtil

  网络请求封装的有些随意,轻喷?

/*** Created by ocean on 2018/8/11* Author :  ocean* Email  :  348686686@qq.com*/object OceanUtil{object Holder{val OK_HTTP_CLIENT = OkHttpClient()val GSON = Gson()}private const val TAG:String = "ocean"/*** 网络请求工具方法demo* @param url api接口地址* @param handler*/fun httpRequest(url: String,params: HashMap<String,Any>,handler: Handler){var jsonObject = JSONObject(params)var requestBody = RequestBody.create(AppConstant.MEDIA_TYPE_JSON,jsonObject.toString())val okHttpClient = Holder.OK_HTTP_CLIENTval request = Request.Builder().url(url).post(requestBody).build()okHttpClient.newCall(request).enqueue(object: Callback{override fun onFailure(call: Call?, e: IOException?) {logE("请求失败")}override fun onResponse(call: Call?, response: Response?) {logE("请求成功")val result:String = response!!.body().string().toString()val message = Message()message.what = 200message.obj = resulthandler.sendMessage(message)}})}/*** 日志打印* @param any*/fun logE(any: Any) {if (AppConstant.isDegug)Log.e(TAG,"-> -> -> 日志打印【 $any 】")}/*** 数据转换*/fun convertData(result:String): ArticleModel {return Holder.GSON.fromJson(result,object : TypeToken<ArticleModel>(){}.type)}}

OK,下面我们将自下而上的构建MVVM

3. 第一步:创建实体Model(Java混编)

  Ps:使用了GsonFormat暂时还不支持Kotlin的数据类(data class),所以这里采用混编的方式。

  这里我们使用GsonFormat插件直接生成Java实体类,也就是我们的每日美文的Model。

  都是一些属性和get/set方法,我们用到的字段也就三四个,大家浏览一遍即可。

/*** Created by ocean on 2018/8/11* Author :  ocean* Email  :  348686686@qq.com*/
public class ArticleModel{/*** ResultCode : 1* ErrCode : OK* Body : {"id":4861,"vol":"VOL.2135","img_url":"http://image.wufazhuce.com/FhUGpJBjkcod8DHH7OSieT-8ODKz","img_author":"Ethan Yang","img_kind":"摄影","date":"2018-08-11 06:00:00","url":"http://m.wufazhuce.com/one/2165","word":"自己被伤害的时候,有的人生气,有的人伤心。生气的人是憎恨的,将自己束之高阁而去攻击对方,歇斯底里地喊叫起来。懂得悲伤的人,一定懂得爱,只是静静地如时间停滞般,独自哀伤。人们总说爱恨参半,其实这是不可能存在的,既爱之,何恨之。","word_from":"《高岭之花》","word_id":2165}*/private int ResultCode;private String ErrCode;private BodyBean Body;public int getResultCode() {return ResultCode;}public void setResultCode(int ResultCode) {this.ResultCode = ResultCode;}public String getErrCode() {return ErrCode;}public void setErrCode(String ErrCode) {this.ErrCode = ErrCode;}public BodyBean getBody() {return Body;}public void setBody(BodyBean Body) {this.Body = Body;}@Overridepublic String toString() {return "ArticleModel{" +"ResultCode=" + ResultCode +", ErrCode='" + ErrCode + '\'' +", Body=" + Body +'}';}public static class BodyBean {/*** id : 4861* vol : VOL.2135* img_url : http://image.wufazhuce.com/FhUGpJBjkcod8DHH7OSieT-8ODKz* img_author : Ethan Yang* img_kind : 摄影* date : 2018-08-11 06:00:00* url : http://m.wufazhuce.com/one/2165* word : 自己被伤害的时候,有的人生气,有的人伤心。生气的人是憎恨的,将自己束之高阁而去攻击对方,歇斯底里地喊叫起来。懂得悲伤的人,一定懂得爱,只是静静地如时间停滞般,独自哀伤。人们总说爱恨参半,其实这是不可能存在的,既爱之,何恨之。* word_from : 《高岭之花》* word_id : 2165*/private int id;private String vol;private String img_url;private String img_author;private String img_kind;private String date;private String url;private String word;private String word_from;private int word_id;public int getId() {return id;}public void setId(int id) {this.id = id;}public String getVol() {return vol;}public void setVol(String vol) {this.vol = vol;}public String getImg_url() {return img_url;}public void setImg_url(String img_url) {this.img_url = img_url;}public String getImg_author() {return img_author;}public void setImg_author(String img_author) {this.img_author = img_author;}public String getImg_kind() {return img_kind;}public void setImg_kind(String img_kind) {this.img_kind = img_kind;}public String getDate() {return date;}public void setDate(String date) {this.date = date;}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public String getWord() {return word;}public void setWord(String word) {this.word = word;}public String getWord_from() {return word_from;}public void setWord_from(String word_from) {this.word_from = word_from;}public int getWord_id() {return word_id;}public void setWord_id(int word_id) {this.word_id = word_id;}@Overridepublic String toString() {return "BodyBean{" +"id=" + id +", vol='" + vol + '\'' +", img_url='" + img_url + '\'' +", img_author='" + img_author + '\'' +", img_kind='" + img_kind + '\'' +", date='" + date + '\'' +", url='" + url + '\'' +", word='" + word + '\'' +", word_from='" + word_from + '\'' +", word_id=" + word_id +'}';}}
}

4. 第二步:引入Repository层

  前面我们说过,Repository层是为了屏蔽底层数据来源,透明的被上层所调用

class ArticRepository(application: Application){private var liveData = MutableLiveData<ArticleModel>()init {getHttpData()}fun getLiveDta():MutableLiveData<ArticleModel>{return liveData}fun getHttpData(){val params : HashMap<String,Any> = HashMap()params["TransCode"] = "030111"params["OpenId"] = "123456789"OceanUtil.httpRequest(AppConstant.URL,params,object : Handler(){override fun handleMessage(msg: Message?) {super.handleMessage(msg)val result = msg?.obj as StringOceanUtil.logE("打印请求结果:$result")liveData.value = OceanUtil.convertData(result)}})}}

  可以看到,内部获取数据实际上使用的就是Okhttp的工具方法,不过对于调用者来说,上层并不关心数据是从Sqlite读出来的,还是网络请求响应的,亦或是其他数据来源。这样在Repository层我们可以很轻松的完成缓存、数据转化等操作,而不影响上层。
  后面的文章,我们会使用Room对网络数据进行持久化缓存,在无网络环境下,保证用户使用软件的完整性,给用户更好的体验。

5. 第三步:创建你的ViewModel

  我们可以选择继承ViewModel/AndroidViewModel类来编写我们项目的ViewModel实现

/*** Created by ocean on 2018/8/12* Author :  ocean* Email  :  348686686@qq.com*/
class ArticleViewModel(application: Application) : AndroidViewModel(application) {private var repository : ArticRepository? = nullprivate var data:MutableLiveData<ArticleModel>? = nullinit {repository = ArticRepository(application)data = repository?.getLiveDta()}fun getData(): MutableLiveData<ArticleModel>? {return data}fun requestData(){repository?.getHttpData()}}

  VM层看起来很简洁,是的。得益于MutableLiveData(LiveData的子类),我们不必要做很多复杂的工作;就像这样,我们仅仅只是声明了一个MutableLiveData的引用、获取实例、调用Repository层得到数据这样微小的工作。

6. 第四步:在View层使用

  在View层调用VM非常简单,Architecture Components的开发者已经帮我们处理了这一切。

  另外,在Fragment中使用ViewModel我们通常在ViewModelProviders.of()方法中传入getActivity();事实上由于传入的Context相同(同一个activity),我们得到的VM也是相同的,所以ViewModel还可以处理Fragment之间的通信。

class MainActivity : AppCompatActivity() {private var vm : ArticleViewModel? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 获取vm对象vm = ViewModelProviders.of(this).get(ArticleViewModel::class.java)initData()}fun initData(){btn_get.setOnClickListener(View.OnClickListener {vm?.requestData()})vm?.getData()?.observe(this, Observer {it?.let { it1 -> updateView(it1) }})}fun updateView(model: ArticleModel){tv_title.text = model.body.word_fromtv_author.text = "—— " +  model.body.img_authortv_digest.text = model.body.wordGlide.with(this).load(model.body.img_url).dontAnimate().centerCrop().into(img_left)Toast.makeText(this,"更新成功",Toast.LENGTH_SHORT).show()}}

  就像这样,我们只需要通过ViewModelProviders.of().get方法得到VM的引用,接下来我们只需要获取LiveData对象的引用,对其添加.observe()方法,之后组件便会自动观察LiveData数据的变化,当数据发生变化时,系统会调用Observer接口的onChanged()方法,所以我们只需要将更新UI的逻辑写在onChanged()方法体内即可。

7. 告一段落

  至此,我们基本已经完成了这个Demo,应该说还是蛮Easy的,后期我们会迭代初一个更好的Demo。

  希望大家持续关注我的微信公众号?

Ps:附上接口地址

// url接口地址:https://api.hibai.cn/api/index/index
// 入参:"TransCode"="030111","OpenId":"123456789"
// 这是接口返回json数据
{"ResultCode": 1,"ErrCode": "OK","Body": {"id": 4860,"vol": "VOL.2136","img_url": "http://image.wufazhuce.com/FpXhraPX6RVOiEVBkxL2mJzd1Lb5","img_author": "狐狸狐狸鱼","img_kind": "插画","date": "2018-08-12 06:00:00","url": "http://m.wufazhuce.com/one/2166","word": "还是想在夏天与你相恋,不仅是夜晚温热的风,清爽的白色短袖,还是冰镇西瓜或者幻想中的漫长暑假,可能是曾经觉得夏天就属于慵懒,所以才会觉得要搭配一个你,在懒洋洋里带着些许的紧张。是你啊,又见面了。","word_from": "咸贵人","word_id": 2166}
}

六、结语

  不知不觉已经写了这么多了,这是作者第一次写这么长的技术文章。
  在发稿前,Review文章;总绝对没有符合“单一职责”原则
  自己也在想,技术类文章在讲重心之前,做一些前置知识点的解释,是否必要。比如本文:分层思想->MVC/MVP->MVVM,相比较开门见山的讲解MVVM是更好理解还是觉得更加臃肿呢?
  所以,笔者也希望大家如果读了这篇文章,可以在留言区评论自己的感受,我将进一步改善文章框架,尽量让大家可以高效的学习。
  最后,笔者技术能力和文笔能力有限,有什么写的不妥的地方,也请大家予以斧正和谅解。

转载于:https://www.cnblogs.com/xykjlcx/p/9469690.html

《Android构建MVVM》系列(一) 之 MVVM架构快速入门相关推荐

  1. rust放置木箱转向_[易学易懂系列|rustlang语言|零基础|快速入门|(17)|装箱crates]...

    [易学易懂系列|rustlang语言|零基础|快速入门|(17)|装箱crates] 实用知识 装箱crates 我们今天来讲讲装箱技术crates. 什么是crates? 英语翻译是: 英 [kre ...

  2. 【Sharding-JDBC系列二】一文快速入门分库分表中间件 Sharding-JDBC (必修课)

    作为Sharding-JDBC 分库分表实战系列的开篇文章,我们在前文中回顾了一下分库分表的基础知识,对分库分表的拆分方式有了一定的了解,下边我们介绍一下 Sharding-JDBC框架和快速的搭建一 ...

  3. [易学易懂系列|golang语言|零基础|快速入门|(一)]

    golang编程语言,是google推出的一门语言. 主要应用在系统编程和高性能服务器编程,有广大的市场前景,目前整个生态也越来越强大,未来可能在企业应用和人工智能等领域占有越来越重要的地位. 本文章 ...

  4. 我爱Java系列---【1.Vue的快速入门案例】

    1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8" /> 5 & ...

  5. [易学易懂系列|golang语言|零基础|快速入门|(二)]

    现在我们来写代码,首先我们要新建一个项目. 新建项目: 点击:File>>New>>Project...如下图: 在New Project窗口,Location:输入:&quo ...

  6. dynamo方程怎么写_Dynamo代码怎么写?BIM系列之dynamo代码块快速入门

    代码块快速入门教程: 1.dynamo等效数字 2.字串 3.顺序 开始..#数量..间距 3.范围 开始..结束..间距 5.取得索引的项目列表: 6.建立清单 7.连接字符串 8.条件陈述语法 9 ...

  7. (李兴华)【FANUC FOCAS1/2 Library 开发系列教程】-10分钟快速入门

    准备工作 1.下载Focas开发包并解压 2.下载Visual Studio2015 社区版并安装 以上具体的实现这里不做具体说明,我相信你完成这些非常容易~~ 确保网络及硬件没问题 我是一枚苦逼的程 ...

  8. c# wpf listbox 高度_WPF快速入门系列(1)——WPF布局概览

    一.引言 关于WPF早在一年前就已经看过<深入浅出WPF>这本书,当时看完之后由于没有做笔记,以至于我现在又重新捡起来并记录下学习的过程,本系列将是一个WPF快速入门系列,主要介绍WPF中 ...

  9. 【Python数据科学快速入门系列 | 06】Matplotlib数据可视化基础入门(一)

    这是机器未来的第52篇文章 原文首发地址:https://robotsfutures.blog.csdn.net/article/details/126899226 <Python数据科学快速入 ...

最新文章

  1. 【pytorch】nn.conv2d的使用
  2. 9.9学python靠谱吗-宅家亲测:9.9的python体验课,到底值不值?
  3. linux time shell统计脚本运行时间
  4. 读书笔记 23种设计模式总结
  5. 接口也可以创建对象吗_面试时,一个小小的设计模式可以把你虐成渣
  6. wxWidgets随笔(7)-utf8中文(3)
  7. Asp.Net登陆记住用户功能实现
  8. 2021河南固高高考成绩查询,河南信阳最好的4所高中,前三所学霸如云,看看有没有你的母校?...
  9. linux: sort排序数据 grep搜索数据
  10. Python 实现单词翻译
  11. windows cmd 提示 ‘系统找不到指定路径‘ 提示 ‘ECHO 处于关闭状态‘
  12. 真香啊,20张高清数据分析全知识地图,要学的东西全都概况了
  13. 7.3万字肝爆Java8新特性,我不信你能看完!(建议收藏)
  14. 英国留学经验分享:发下呆会被拒 有特长受欢迎
  15. F28335第十篇——增强型捕获模块(eCAP)
  16. logstash mutate split日志切分
  17. 【学习笔记】欧拉公式证明(定义法、泰勒公式法)
  18. Android 深入理解 ANR 触发原理:Service
  19. 计算机cbr代表什么,cbr是什么文件格式
  20. 眼球追踪如何预测头部追踪

热门文章

  1. W: Possible missing firmware /lib/firmware/i915/bxt_guc_ver8_7.bin for module i915
  2. Visual Translation Embedding Network for Visual Relation Detection论文中的术语以及对论文的理解笔记...
  3. HNOI2018酱油记
  4. 网络编程-之粘包现象
  5. [设计模式][c++]状态切换模式
  6. freemodbus线圈中的位操作
  7. 八皇后问题 回溯方法
  8. 6个免费的C++图形和游戏库
  9. 为DataGrid创建自定义列控件(四)
  10. linux 基础知识及命令总结