[译]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技术团队的技术评估的一部分.

Marvel Android 应用程序截屏
Marvel Android 应用程序截屏

这个应用需要搜索人物,展现搜索结果并缓存上一次搜索。

这个项目的实现使用了MVP,包含了一些现代的Android开发理念和第三方库,这些都可以改变你的职业生涯!

在接下来的系列文章的不同部分我将竭尽所能去解释一切,即:Dagger,Retrofit,RxJava和Tests。

这个项目使用了Circleci.comTravis-ci.org来做持续集成(CI),使用了Codecov.io来做代码覆盖测试,还使用了google的Firebase,这一部分你可以自己学习,因为一定程度上这已经脱离了本文的主题。

在开始之前,你可以先阅读该工程的README任务列表文件来多做一些了解。

Okey,告诉我你都了解到了什么:

让我们先来看一下项目的结构:
我个人喜欢整洁的代码,所以我喜欢将项目分成有意义的模块,以便我和整个团队保持更清晰的任务。

Modules:

整个工程包含两个main module 和一个java console sample module:

app module包含MVP中的Android View层,其余两层(ModelPresentation)都放在了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的运行结果
    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的文件结构
core module的文件结构
  • base package: 包含了所有的基础接口,包括所有Intersctor PresentresViews的通用方法。
  • character package: 包含了应用程序的主要功能,即Marvel人物的搜索和缓存信息。
  • database package:在本应用中数据的缓存是通过OrmLite完成的,这里我不会做过多的解释,因为那样又脱离了主题,但是你可以阅读所有的源码!
  • domain package:包含了通过retrofit2和RaJava库连接网络api的源码。
  • util package: 本项目中所需要的所有有用的工具类,即:Constants 包含了所有的核心常量。HashGenerator用于Marvel 的api需要的哈希参数。SchedulerProvider是一个调度接口用于RxJavaRxAndroid的多线程(我将在本文的相关部分做详细介绍)。

参考SOLID依赖性反转原理“应该依赖抽象而不依赖于具体”或引用Novoda的这篇精彩文章“你不应该把一盏灯直接连接到你的房子”!,所有的两个模块之间的链接 (app&core)通过接口实现,并用Dagger连接。

你不应该把一盏灯直接连接到你的房子
你不应该把一盏灯直接连接到你的房子

app module内部又发生了什么?

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容