[译]MVP实践(Android)-Part1:让我们了解这个项
使用MVP, RxJava Dagger2, Retrofit2, Test 以及所有最新的现代方法、库来实现一个Android实例应用。
我在StackOverFlow上发现一些有关MVP使用的问题没有回答,这促使我产生强烈的兴趣来写下这一系列文章,并提供一个样例工程(即我自己的实践经验)。
我从去年开始熟悉MVP,一个tuxedo开发,并开始寻找样例和教程。我花了很长时间来处理、连接这个未知大谜题的不同部分。我可以推荐的最有用的网站是caster.io,它总是充满了新的Android视频教程。
解释*MVP本身看起来有些古怪!因为已经有许多文章解释它是怎么工作的,以及它是怎么分层的等等,但是附带足够的评论(注释)来帮助新手了解这个方法的样例少之又少。
变化是怎么开始的。。。
这一切都从SOLID(面向对象的设计原则)开始,感谢亲爱的Robert C. Martin。
从维基文章的内容我们知道SOLID代表:
- S (SRP): 单一功能原则(认为对象应该仅具有一种单一功能的概念)
- O (OCP): 开闭原则(认为“软件体应该是对于扩展开放的,但是对于修改封闭的”的概念)
- L (LSP): 里氏替换原则(认为“程序中的对象应该是可以在不改变程序正确性的前提下被它的子类所替换的”的概念)
- I (ISP): 接口隔离原则(认为“多个特定客户端接口要好于一个宽泛用途的接口”的概念)
- D (DIP): 依赖反转原则(认为一个方法应该遵从“依赖于抽象而不是一个实例”的概念。依赖注入是该原则的一种实现方式。)
MVP在一定程度上尝试遵循这5条原则的全部。我将竭尽全力在示例项目中逐一定位这些原则,以使它们更加透明。
通过这篇完美的MVP文章,MVP代表:
Model 就是将在View(用户界面)中展现的数据。
View就是用于展示数据(Model)的的界面,同时将用户的命令(events)传递给Presenter,由Presenter对数据进行操作。view通常持有一个Presenter的引用
Presenter就是一个“中间人”(MVC模式中Controller扮演的角色),它同时持有vew和model的引用。
Model?!!!
请注意“Model”这个词语有误导性
它应该是检索或者操作Model的业务逻辑。
比如:如果你有一个数据库,在一个表中存储了User
数据,你的view想要展示一个用户列表,那么Presenter应该持有一个数据库业务逻辑(比如一个DAO)的引用,通过这个业务逻辑Presenter可以查询到一个用户列表。
你能够对MVP再多做一点解释吗?
不不不不不不!!通过关键字Google(不能翻墙的就Bing),你会发现所有关于这个新方法的理论。(或者至少阅读一下这篇文章)。
这个样例项目是关于什么的?
这个应用是Marvel的人物搜索程序,Marvel.com的一个简单Android客户端。此应用程序由我创建,作为smava GmbH技术团队的技术评估的一部分.
这个应用需要搜索人物,展现搜索结果并缓存上一次搜索。
这个项目的实现使用了MVP,包含了一些现代的Android开发理念和第三方库,这些都可以改变你的职业生涯!
在接下来的系列文章的不同部分我将竭尽所能去解释一切,即:Dagger,Retrofit,RxJava和Tests。
这个项目使用了Circleci.com和Travis-ci.org来做持续集成(CI),使用了Codecov.io来做代码覆盖测试,还使用了google的Firebase,这一部分你可以自己学习,因为一定程度上这已经脱离了本文的主题。
在开始之前,你可以先阅读该工程的README和任务列表文件来多做一些了解。
Okey,告诉我你都了解到了什么:
让我们先来看一下项目的结构:
我个人喜欢整洁的代码,所以我喜欢将项目分成有意义的模块,以便我和整个团队保持更清晰的任务。
Modules:
整个工程包含两个main module 和一个java console sample module:
- 1.app(marvel-app): Anddroid Application module
- 2.core-lib(marvel-core):Core Library module
- 3.console(marvel-console):java console sample module
app module包含MVP中的Android View层,其余两层(Model和Presentation)都放在了core中,core是一个纯粹的java包,编译后可以生成一个jar库。
把代码按照这种方式分成几个module有什么好处?!
- 首先,将Android Application module 分开,是为了提醒你不要传递Context或者任何Android相关的对象给Presenter或者Model!所以请现在就停止那样做!!!
- 其次,你能够确保你的core部分是非常完整,你甚至可以把它和另一个UI搭配使用(即:java Console sample,Web控件,甚至在未来的某一天当 一个iOS使用java!!)
- 最后,我和我们的团队真的喜欢以这样的方式开发应用!并且单独分离core部分使整个团队都受益,我们甚至将core放到git的一个submodule中在不同的项目中使用,大家使用同一个core而使用不同的UI。
java sample module和Android Application一样使用core的运行结果
模块名称清理:
为了使modules看起来方便且好看,你可以像这样编辑settings.gradle文件:
include ':marvel-app', ':marvel-core', ':marvel-console'
project(':marvel-app').projectDir = new File('app')
project(':marvel-core').projectDir = new File('core-lib')
project(':marvel-console').projectDir = new File('console')
这样会使modules看起来像这样:
当然这也会导致相关APK文件的名称发生变化。
怎样避免不同的module中版本冲突以及冗余?
在你的Project module 中使用gradle的一个特性可以很方便的获取一份整洁的build.gradle文件,同时还能避免版本冲突和冗余问题。
首先,将你所有project的依赖放到一个gradle文件中,比如libraries.gradle:
ext {
minSdkVersion = 9
compileSdkVersion = 25
buildToolsVersion = "25.0.0"
//Android
androidSupportVersion = "25.0.0"
butterknifeVersion = "8.0.1"
/*...*/
libraries = [
androidSupport : "com.android.support:support-v4:${androidSupportVersion}",
appCompat : "com.android.support:appcompat-v7:${androidSupportVersion}",
designSupport : "com.android.support:design:${androidSupportVersion}",
/*...*/
]
/*...*/
}
然后将他放到你的project的主build.gradle文件(请注意最后一行):
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.2'
/*...*/
}
}
apply from: "./libraries.gradle"
最后,在你的app module的build.gradle文件中像一个插件一样使用(注意依赖部分):
apply plugin: 'com.android.application'
/*...*/
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
/*...*/
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile project(':marvel-core')
testCompile rootProject.ext.testLibraries.junit
testCompile rootProject.ext.testLibraries.robolectric
androidTestCompile rootProject.ext.testLibraries.mockito
compile rootProject.ext.libraries.appCompat
compile rootProject.ext.libraries.androidSupport
compile rootProject.ext.libraries.designSupport
/*...*/
}
在你的core module 的build.gradle文件中使用:
apply plugin: 'java'
/*...*/
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile rootProject.ext.libraries.rxjava
testCompile rootProject.ext.testLibraries.junit
testCompile rootProject.ext.testLibraries.mockito
compile rootProject.ext.libraries.retrofit
/*...*/
}
core module内部发生了什么事情?
core module的文件结构- base package: 包含了所有的基础接口,包括所有Intersctor Presentres和Views的通用方法。
- character package: 包含了应用程序的主要功能,即Marvel人物的搜索和缓存信息。
- database package:在本应用中数据的缓存是通过OrmLite完成的,这里我不会做过多的解释,因为那样又脱离了主题,但是你可以阅读所有的源码!
- domain package:包含了通过retrofit2和RaJava库连接网络api的源码。
- util package: 本项目中所需要的所有有用的工具类,即:Constants 包含了所有的核心常量。HashGenerator用于Marvel 的api需要的哈希参数。SchedulerProvider是一个调度接口用于RxJava和RxAndroid的多线程(我将在本文的相关部分做详细介绍)。
参考SOLID的依赖性反转原理,“应该依赖抽象而不依赖于具体”或引用Novoda的这篇精彩文章“你不应该把一盏灯直接连接到你的房子”!,所有的两个模块之间的链接 (app&core)通过接口实现,并用Dagger连接。
app module内部又发生了什么?
app module的文件结构- activity package: 包含了3个作为Android应用程序UI支柱的 activity。
- base package: 包含了两个activity和fragment的基本抽象类,抽象类中包括了用于注入的通用方法。
- character package: 包含了该应用的主要功能,通过两个Search & Cachefragment实现。
- daabase package: 包含了Android侧数据相关代码,这里使用了OrmLite!
- util package:本项目Android侧所需要的全部工具类,即:AppConstants 继承了 core中的Constants,包含了Application的常量定义。AppSchedulerProvider实现了core中的SchedulerProvider,并提供RxAndroid调度者。CustomBindingAdapter,帮助新的Android DataBinding插件使用Picasso库加载图片。GridSpacingItemDecoration帮助RecyclerView调整网格项目间距。(这只是一个简要的信息,在本文的相关部分都会做详细的介绍)。
好了,就这么多吧
请从github上clone一份代码并熟悉一下,因为从下一部分我将更多的介绍dagger以及它是怎样连接各个module和各层的不同对象。
我期待您的意见和帮助以便更好的改进这篇文章。
继续下一篇:MVP实践(Android)-Part2:Dagger使用
原文链接:Yet another MVP article — Part 1: Lets get to know the project