使用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內部發生了什么事情?

- 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內部又發生了什么?

- 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