寫在前面
Android端的MVP架構已經出來有很長時間了。而對於Android的MVP實現模式,也並沒有個標準的實現方式。
現在市面上最流行的是google開源出來的一套MVP模型,此模型可到此google家MVP開源地址進行查看。
而此篇博客將要介紹的並不是google的MVP模型。而是根據我自身理解所創建的一種MVP模型。與google的MVP模型相比,此種MVP模型具有以下一些優勢:
- 支持單頁面綁定多個Presenter進行使用,便於進行Presenter複用
- 對Presenter進行生命週期派發,
- 自動對Presenter進行View的綁定與解綁。
- 剔除契約類Contract,避免類爆炸
MVP概念說明
- Model: 數據提供層,負責向Presenter提供數據或者提供數據處理入口
- View: 視圖層,負責接收Presenter通知,進行界面更新
- Presenter: View與Model的樞紐層,負責接收View層的命令,從Model層讀取並處理好數據後,通知View進行界面更新。
僅僅靠上面的文字來進行分層說明略顯空洞,所以這裏我們來通過一個簡單的sample代碼
來做MVP分層概念說明, 如下方是個簡單的登錄頁面的MVP實現:
interface LoginView:MVPView {
fun onLoginSuccess()
fun onLoginFailed()
}
class LoginPresenter(view:DemoView):MVPPresenter<DemoView>(view) {
fun login(username:String, password:String) {
LoginApis.login(username, password, object Callback {
override fun onSuccess() {
view.onLoginSuccess()
}
override fun onFailed() {
view.onLoginFailed()
}
})
}
}
class LoginActivity:BaseMVPActivity(),LoginView {
// 創建與綁定Presenter。
val presenter = LoginPresenter(this)
override fun createPresenters() = arrayOf(presenter)
override fun onLoginSuccess() {
// 接收數據請求任務的返回數據並展示
EasyToast.DEFAULT.show("登錄成功")
}
override fun onLoginFailed() {
// 接收數據請求任務的返回數據並展示
EasyToast.DEFAULT.show("登錄成功")
}
...
// 點擊登錄
fun onLoginClick() {
val username = ...
val password = ...
presenter.login(username, password)// 發起login任務請求
}
}
1. LoginView
interface LoginView:MVPView {
fun onLoginSuccess()
fun onLoginFailed()
}
繼承並擴展MVPView接口。很多人喜歡直接將此類作爲MVP中的V層
,但是實際上,我更願意稱此爲通信協議接口
,作用是由V層
提供給P層
進行P-V綁定
,用於在P層
中通知V層
進行界面更新,類似於提供了一個Callback給P層進行使用
2. LoginActivity
class LoginActivity:BaseMVPActivity(),LoginView {
override fun onLoginSuccess() {...}
override fun onLoginFailed() {...}
// 發起login任務請求
fun onLoginClick() {presenter.login(username, password)}
}
真正的View
層。可以是Activity
, Fragment
等。是真正進行界面更新的地方。
View
層需要持有Presenter
的對象,用於在需要的時候使用presenter
發起具體的數據請求處理任務
,比如上例中點擊進行登錄時。通過presenter
發起了登錄任務, 並通過LoginView
協議接口,接收任務處理回調
進行界面更新
3. LoginApis
LoginApis.login(username, password, object Callback {
override fun onSuccess() { view.onLoginSuccess() }
override fun onFailed() { view.onLoginFailed() }
})
Model
層,也叫數據提供層。
與其他的MVP
不同,這裏並沒有要求Model
層需要定義一個特殊的接口去進行實現。所有的功能性api
。均可被視作爲Model
層。比如這裏LoginApis
,便是用於提供登錄模塊的網絡任務入口
。
Model
層與Presenter
層的通信方式主要分爲兩種:一種直接從Model層同步獲取
到指定數據,另一種是通過異步回調
的方式獲取到指定數據,即此處LoginApis
的通信方式。
請注意避免直接向Model
層傳遞Presenter
去進行數據獲取。保證Model
的獨立性
4. LoginPresenter
Presenter
層,連接V-M的中間樞紐層。複雜的數據業務邏輯都在這裏進行處理。
結合上方示例:一個完整的M-P-V通信流程,可分爲以下幾步:
- 由
V層向P層
發起具體的處理任務 -
P層
接收到V層
發起的任務。調用M層
的api,獲取到原始數據
-
P層
對從M層
獲取到的原始數據
進行預處理(本示例因爲較簡單,故沒有這一步)。 - 將
處理完畢後
的數據。通過V層
提供的協議接口,通知到V層
去進行界面更新。
MVP實現
通過上面的實例與講解。相信可以使大家對MVP模型有一定的瞭解了,下面我們將一步步的介紹整個MVP模型框架的搭建
1. 基礎通信協議接口定義:MVPView
interface MVPView {
fun getHostActivity():Activity
fun showLoadingDialog()
fun hideLoadingDialog()
fun toastMessage(message:String)
fun toastMessage(resId:Int)
}
MVPView
中定義了一些基礎的協議方法。這些方法是所有V層
都需要的功能。比如Toast展示
、進行異步任務時的加載中Dialog展示
等。
2. 基礎Presenter創建
open class MVPPresenter<T:MVPView>(private var view:T?){
fun attach(t:T) {
this.view = t
}
fun detach() {
this.view = null
}
fun isViewAttached() = view != null
fun getActivity() = view?.getHostActivity()?:throw RuntimeException("Could not call getActivity if the View is not attached")
// Lifecycle delegate
open fun onCreate(bundle: Bundle?) {}
...
open fun onDestroy(){}
}
我們來一條條的捋一下:
首先。我們在上面的MVP說明中說過,MVPView
爲協議接口,提供出來用於進行P-V綁定
,所以相應的就會有P-V的綁定與解綁功能
(爲了方便使用,這裏也讓默認構造器自動進行了P-V綁定)
fun attach(t:T) { this.view = t }
fun detach() { this.view = null }
然後,我們會經常需要在P層中使用綁定的Activity去進行各種操作。而p層
是不建議去持有Context實例
的,所以在此提供一個getActivity
方法,從綁定的view
中去獲取正確的Activity實例
提供使用:
fun getActivity() = view?.getActivity()?:throw RuntimeException("Could not call getActivity if the View is not attached")
而isViewAttached
方法,則是專門爲異步回調任務
所設計的api。因爲如果是使用異步回調的方式
去從model層獲取的數據。那麼很可能,接收到回調消息的之前,這個時候view已被解綁置空
。導致通知失敗
所以。一般來說。對於任務中有異步回調操作
的,應該在回調處。先行判斷是否已解綁
。若已解綁則跳過當前操作:
if (!isViewAttached()) return
view?.hideLoadingDialog()
最後,則是提供的一堆onXXX
生命週期方法了。用於與activity/fragment 中的生命週期進行綁定。
3. V-P連接器
與其他的MVP
相比不同,這裏提供MVPDispatcher
作爲V-P連接器
:
class MVPDispatcher{
private val presenters:MutableList<MVPPresenter<*>> = mutableListOf()
// ==== 添加與移除Presenter ========
fun <V:MVPView> addPresenter(presenter:MVPPresenter<V>) {...}
internal fun <V:MVPView> removePresenter(presenter:MVPPresenter<V>) {...}
// ==== 綁定生命週期 ==========
fun dispatchOnCreate(bundle:Bundle?) {...}
...
fun dispatchOnRestoreInstanceState(savedInstanceState: Bundle?) {...}
}
可以看到此連接器幹了以下幾件事:
-
addPresenter
與removePresenter
:向容器presenters
中添加或移除presenter實例
。 做到對單頁面綁定多個presenter的效果。 -
dispatchOnXXX
:通過已添加的presenter
進行生命週期通知
. 做到V-P生命週期綁定的效果 - 在接收到V層
destroy
銷燬通知時,自動移除解綁所有的presenter實例
。
fun dispatchOnDestroy() {
presenters.forEach {
if (it.isViewAttached()) { it.onDestroy() }
// 生命方法派發完畢後。自動解綁
removePresenter(it)
}
}
4. BaseMVPActivity的創建
最後就是真正的V層
的創建了:Activity/Fragment
的MVP基類搭建!
這裏使用BaseMVPActivity
作爲舉例說明。fragment
的基類搭建與此大同小異。
首先,我們需要確定BaseMVPActivity
所需要盡到的職責:
1. 一個具體的V層,需要持有一個唯一的MVPDispatcher進行操作
abstract class BaseMVPActivity:Activity() {
val mvpDispatcher = MVPDispatcher()
}
2. 統一實現默認協議方法:MVPView
abstract class BaseMVPActivity:Activity(), MVPView {
...
override fun getHostActivity():Activity {...}
override fun showLoadingDialog() {...}
override fun hideLoadingDialog() {...}
override fun toastMessage(message:String) {...}
override fun toastMessage(resId:Int) {...}
}
3. 藉助MVPDispatcher實現V-P,一對多的綁定
abstract class BaseMVPActivity:Activity(), MVPView {
...
// 由子類提供當前頁面所有需要綁定的Presenter。
open fun createPresenters():Array<out MVPPresenter<*>>? = null
override fun onCreate(savedInstanceState: Bundle?) {
...
// 創建所有的presenter實例,並通過mvpDispatcher進行綁定
createPresenters()?.forEach { mvpDispatcher.addPresenter(it) }
}
}
比如在最上面我們所舉例的LoginActivity。假設現在我們需要對登錄頁再添加一個驗證碼校驗邏輯
. 此邏輯被放在了CaptchaPresenter
中:
class LoginActivity:BaseMVPActivity(),LoginView, CaptchaView {
// 登錄的Presenter實現
val loginPresenter = LoginPresenter(this)
// 驗證碼的Presenter實現
val captchaPresenter = CaptchaPresenter(this)
// 綁定多個Presenter
override fun createPresenters() = arrayOf(loginPresenter, captchaPresenter)
...
}
這就做到了Presenter的複用。在需要共用一些基礎業務邏輯
的時候。此一對多的綁定
是個很好的特性!
4. 藉助MVPDispatcher實現V-P生命週期關聯管理
abstract class BaseMVPActivity:Activity(), MVPView {
...// other codes
override fun onCreate(savedInstanceState: Bundle?) {
...
mvpDispatcher.dispatchOnCreate(intent?.extras)
}
...
override fun onDestroy() {
...
// 銷燬時mvpDispatcher會自動進行所有的Presenter的解綁。
// 所以具體的V層並不需要再自己去手動進行解綁操作了
mvpDispatcher.dispatchOnDestroy()
}
}
這就是一個基本的V層基類實現類需要做到的事。到這裏。整個MVP基礎框架的搭建就算完成了!
5. MVP架構開源
由於此MVP架構其實是個挺簡單的架構。所以我將此架構源碼存放在了EasyAndroid組件庫中了。
EasyAndroid作爲一款集成組件庫,此庫中所集成的組件,均包含以下特點,你可以放心使用~~
1. 設計獨立
組件間獨立存在,不相互依賴,且若只需要集成庫中的部分組件。也可以很方便的
只copy對應的組件文件
進行使用
2. 設計輕巧
因爲是組件集成庫,所以要求每個組件的設計儘量精練、輕巧。避免因爲一個小功能而引入大量無用代碼.
每個組件的方法數均
不超過100
. 大部分組件甚至不超過50
。
由於V層基類實現
不同項目都會有一定的差異性。比如Activity基類選擇
(AppCompatActivity/v4Activity/Activity)、或者MVPView的展示樣式設計
等。所以BaseMVPActivity
這類真正的V層基類實現
並沒有被放入lib中。而是在示例工程中
單獨進行了提供.
在需要使用的時候,通過copy此部分的源碼直接到工程中使用即可
EasyAndroid庫中的mvp模塊。僅僅提供了MVPView
、MVPPresenter
、MVPDispatcher
這三個基礎支持類。避免引入無用代碼。
源碼鏈接
- EasyAndroid開源組件庫地址
- 基礎支持類
MVPView
、MVPPresenter
、MVPDispatcher
源碼地址
https://github.com/yjfnypeu/EasyAndroid/tree/master/utils/src/main/java/com/haoge/easyandroid/mvp
-
V層基類實現
及簡單sample示例代碼
地址
學習分享,共勉
題外話,我從事Android開發已經五年了,此前我指導過不少同行。但很少跟大家一起探討,正好最近我花了一個多月的時間整理出來一份包括不限於高級UI、性能優化、移動架構師、NDK、混合式開發(ReactNative+Weex)微信小程序、Flutter等全方面的Android進階實踐技術,今天暫且開放給有需要的人,若有關於此方面可以轉發+關注+點贊後加羣 878873098 領取,或者評論與我一起交流探討。
資料免費領取方式:轉發+關注+點贊後,加入點擊鏈接加入羣聊:Android高級開發交流羣(878873098)即可獲取免費領取方式!
重要的事說三遍,關注!關注!關注!