写点什么

Android 官方 MVP 架构示例项目解析

2016 年 4 月 28 日

前段时间 Google 在 Github 推出了一个项目,专门展示 Android 引用各种各样的 MVP 架构,算是官方教程了。趁着还新鲜,让我们来抛砖引玉一探究竟,看看在 Google 眼里什么样算是好的 MVP 架构。

App 架构在 Android 开发者中一直是讨论比较多的一个话题,目前讨论较多的有 MVP、MVVM、Clean 这三种。google 官方对于架构的态度一直是非常开放的,让开发者自主选择组织和架构 app 的方式,期望能留给开发者更多的灵活性。

由于没有一套权威的架构实现,现在很多 App 项目中在架构方面都有或多或少的问题。第一种常见问题是没有架构,需求中的一个页面对应项目中的一个 activity 或一个 fragment,所有的界面响应代码、业务逻辑代码、数据请求代码等等都集中在其中。第二种常见的问题是架构实现的不断变化,不断在各种架构间摇摆,一直找不到一个适合自己的架构。

就在近日,google 在官方示例中给出了一系列不同架构的 app 实现,这对于一直困惑于到底该用何种架构的 android 开发者来说确实是福音,开发者只要根据自己项目的情况,在不同的实现中选择一种即可,当然 google 也明确表示了这些示例只是用来做参考,而并不是要为了当做标准,下面先为大家简单介绍下此项目。

项目介绍

Google 把这个项目命名为:Android 架构蓝图。

项目地址为: https://github.com/googlesamples/android-architecture

下面的内容引用自项目说明:

项目目的是通过展示各种架构 app 的不同方式来帮助开发者解决架构问题。项目中通过不同的架构概念及方式实现了功能相同的 app。你可以用示例来当做参考,或是干脆拿来当做创建 app 项目的基础。项目中,希望大家能把关注点集中到代码结构、整体架构、可测试性、可维护性这四个方面。当然实现 app 有很多种方式,千万不要把它当做定式。

项目中有哪些示例

目前已经完成的示例有

  • todo-mvp(mvp 基础架构示例)
  • todo-mvp-loaders(基于 mvp 基础架构项目,获取数据部分使用了 Loaders 架构)
  • todo-mvp-databinding(基于 mvp 基础架构项目,使用了数据绑定组件)

仍在进展中的示例有

  • todo-mvp-contentproviders(基于 mvp 基础架构项目,使用了 Content Providers)
  • todo-mvp-clean(基于 mvp 基础架构项目,使用了 clean 架构的概念)
  • todo-mvp-dagger(基于 mvp 基础架构项目,使用了 dagger2 进行依赖注入)

如何进行选择

这个还是需要开发者自己来做决定,每个项目的说明文件中都说明了该实现的特性。app 规模、团队状况、维护工作量的大小、平板是否支持、代码简洁程度偏好,这些都会影响你的选择。

到了这里,想必大家都很想一探究竟了,到底官方示例是如何实现的呢?还是那句话,源码面前,了无秘密。为了能够更好的理解官方 mvp 架构实现,下面我们从源码的角度来分析 todo-mvp(mvp 基础架构示例)的实现。我们先从项目的整体组织方式开始,再看项目究竟使用了哪些组件,最后当然是最重要的具体 mvp 的实现方式。

源码分析

项目代码组织方式

项目含一个 app src 目录,4 个测试目录,分别是 androidTest(UI 层测试)、androidTestMock(UI 层测试 mock 数据支持)、test(业务层单元测试)、mock(业务层单元测试 mock 数据支持)。src 目录的代码组织方式完全是按照功能来组织的,功能内部分为 xactivity、xcontract、xfragment、xpresenter 四个类文件 (x 代表业务名称)。

平时用到较多的另一种组织方式是按照类型,比如按照 activity、adapter、fragment、contract、presenter 进行划分,不同的类文件分别放到不同的目录中,笔者觉得两种方式没有什么太大的区别,完全看个人喜好了。

组件使用

由于项目是基于 gradle 进行编译的,所以我们可以从 build.gradle 文件看到项目依赖的全貌。

Guava

项目中使用到了 Guava 库( https://github.com/google/guava ),该库是 Google 在基于 java 的项目中都会引用到得一个库,库中包含大约 14k 的方法数,是个很大的库,其中包含了集合、缓存、并发、基本注解、字符串处理、io 处理等等。项目中使用 Guava 库主要是处理 null 这种不安全的情况,因为一般我们在使用有可能为 null 的对象时,一般会增加一次判断,代码如下:

而如果有 Guava 的时候,可以通过如下方式

这样面对空的时候,就不用再多写很多代码了,确实是方便了很多。但是不建议为了 null 安全直接引入如此大的一个库,因为我们都知道 android apk 的 65k 方法数限制,如果要用的话可以把源码中涉及到得部分直接拿出来用。当然 Guava 中还有很多重要的功能,其他功能读者可以自行研究,关于 Guava 就先到这里了。

测试相关组件

示例项目在可测试方面做的非常好,由于对视图逻辑 (view 层) 和业务逻辑 (presenter 层) 进行了拆分,所以我们就可以对 UI、业务代码分别进行测试。为了进行 UI 测试引入了 Espresso,为了对业务层进行单元测试引入了 junit,为了生成测试 mock 对象引入了 mockito,为了支撑 mockito 又引入了 dexmaker,hamcrest 的引入使得测试代码的匹配更接近自然语言,可读性更高,更加灵活。

项目 MVP 实现方式

这节我们就具体来看官方示例到底是如何实现 mvp 的。这里我们先看下总体的轮廓,关于项目中业务代码我们仅列出了任务详情页(taskDetail)的相关类,其他业务代码类似。

基类

我们首先来看两个 Base 接口类,BasePresenter 与 BaseView,两类分别是所有 Presenter 与 View 的基类。

BasePresenter 中含有方法 start(), 该方法的作用是 presenter 开始获取数据并调用 view 中方法改变界面显示,其调用时机是在 Fragment 类的 onResume 方法中。

BaseView 中含方法 setPresenter,该方法作用是在将 presenter 实例传入 view 中,其调用时机是 presenter 实现类的构造函数中。

契约类

与笔者之前见到的所有 mvp 实现都不同,官方的实现中加入了契约类来统一管理 view 与 presenter 的所有的接口,这种方式使得 view 与 presenter 中有哪些功能,一目了然,维护起来也方便,实例如下

activity 在 mvp 中的作用

activity 在项目中是一个全局的控制者,负责创建 view 以及 presenter 实例,并将二者联系起来,下面是 activity 中创建 view 及 presenter 的代码

(点击放大图像)

我们可以从上面看到整个创建过程,而且要注意的是创建后的fragment 实例作为presenter 的构造函数参数被传入,这样就可以在presenter 中调用view 中的方法了。

mvp 的实现与组织

实例中将 fragment 作为 view 层的实现类,为什么是 fragment 呢?有两个原因,第一个原因是我们把 activity 作为一个全局控制类来创建对象,把 fragment 作为 view,这样两者就能各司其职。第二个原因是因为 fragment 比较灵活,能够方便的处理界面适配的问题。我们先看 view 的实现,我们只挑一部分重要的方法来看

(点击放大图像)

上面可以看到setPresenter 方法,该方法继承于父类,通过该方法,view 获得了presenter 得实例,从而可以调用presenter 代码来处理业务逻辑。我们看到在onResume 中还调用了presenter 得start 方法,下面我们再看presenter 的实现

(点击放大图像)

presenter 构造函数中调用了 view 得 setPresenter 方法将自身实例传入,start 方法中处理了数据加载与展示。如果需要界面做对应的变化,直接调用 view 层的方法即可,这样 view 层与 presenter 层就能够很好的被划分。

最后还剩下 model 层实现,项目中 model 层最大的特点是被赋予了数据获取的职责,与我们平常 model 层只定义实体对象截然不同,实例中,数据的获取、存储、数据状态变化都是 model 层的任务,presenter 会根据需要调用该层的数据处理逻辑并在需要时将回调传入。这样 model、presenter、view 都只处理各自的任务,此种实现确实是单一职责最好的诠释。

总结

到这里我们就基本分析完了,我们再来整体看下官方的实现方式有哪些特性。

首先是复杂度,我们可以从上面的分析看出整体的复杂度还是较低的,易学的;然后是可测试性,由于将 UI 代码与业务代码进行了拆分,整体的可测试性非常的好,UI 层和业务层可以分别进行单元测试;最后是可维护性和可扩展性,由于架构的引入,虽然代码量有了一定的上升,但是由于界限非常清晰,各个类职责都非常明确且单一,后期的扩展,维护都会更加容易。有了这个架构之后,我们再回头看下之前的实现是不是有很多不足,没有关系,那么接下来就是在项目中进行实践的时间了。


感谢徐川对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2016 年 4 月 28 日 17:4942799

评论

发布
暂无评论
发现更多内容

JAVA AGENT 学习

zane

Java

《零基础学 Java》 FAQ 之 15-Java范型做了两件事

臧萌

Java

全栈工程师为什么越混越困难,看这篇就够了

金刚小书童

职业规划 技术管理 全栈工程师 程序员成长 程序员次第

简单聊聊什么是苹果生态

李俊辰

重磅!Apache Flink 1.11 功能前瞻抢先看!

Apache Flink

大数据 flink 流计算 实时计算 大数据处理

mac 安装特定版本php-redis

HQ数字卡

php

MySQL查询优化一般步骤

HQ数字卡

MySQL sql 查询优化

RestTemplate 配置手册

zane

Spring Boot HTTP

ARTS - 第一周打卡

陈文昕

Android实现人脸识别(人脸检测)初识

sar

android OpenCV renlianshibie

工厂模式(二)MyBatis中展示的简单的工厂模式

LSJ

mybatis 工厂模式

使用docker-compose部署单机RabbitMQ

Kevin Liao

Docker Docker-compose RabbitMQ

【摘】Git-从零单排 01期

卡尔

git 效率工具 工具 开发工具

唯技术论坏处都有啥?如何跳出唯技术论思维?

KAMI

方法论 思考 思维方式 开发 唯技术论

浅谈使命、愿景、价值观。

石云升

价值观 使命 愿景

记:mybatis <foreach> 语法错误

Kevin Liao

mybatis foreach SQL语法 SQLSyntaxErrorException

IO多路复用整理

戈坞昂

Linux io

《零基础学 Java》 FAQ 之 14-访问控制符总结

臧萌

Java

《零基础学 Java》 FAQ 之 16-范型引用的通配符再解

臧萌

Java

1分钟学习Java中数组快速复制

HQ数字卡

Java 数组

听过很多道理,依然过不好这一生。

Neco.W

感悟 创业心态

RabbitMQ发送消息步骤&源码

云淡风轻

读书笔记 RabbitMQ

机器学习-有监督学习入门

第519区

学习 产品经理

程序员如何阅读英文资料

brave heart

学习

专业的力量

无量靠谱

淘宝 美团 专业 专业主义 大前研一

Git内部原理介绍

戈坞昂

git

《零基础学 Java》 FAQ 之 13-编程里的两个特殊的值

臧萌

Java

介绍一款文本分析工具

黄大路

数据挖掘 数据分析 nlp

李想解读《高效能人士的七个习惯》

我心依然

习惯 高效能人士的七个习惯 李想 汽车之家

OpenResty 部署配置和日志切割

wong

centos log openresty

"第1天,读以太坊白皮书 | 5天掌握以太坊 dApp 开发"

陈东泽 EuryChen

区块链 以太坊 dapp Ethereum blockchain

InfoQ 极客传媒开发者生态共创计划线上发布会

InfoQ 极客传媒开发者生态共创计划线上发布会

Android官方MVP架构示例项目解析-InfoQ