Android架構設計:手把手教你擼一個簡潔而強大的MVP框架!

寫在前面

Android端的MVP架構已經出來有很長時間了。而對於Android的MVP實現模式,也並無個標準的實現方式。java

如今市面上最流行的是google開源出來的一套MVP模型,此模型可到此google家MVP開源地址進行查看。android

而此篇博客將要介紹的並非google的MVP模型。而是根據我自身理解所建立的一種MVP模型。與google的MVP模型相比,此種MVP模型具備如下一些優點:git

  1. 支持單頁面綁定多個Presenter進行使用,便於進行Presenter複用
  2. 對Presenter進行生命週期派發,
  3. 自動對Presenter進行View的綁定與解綁。
  4. 剔除契約類Contract,避免類爆炸

MVP概念說明

  • Model: 數據提供層,負責向Presenter提供數據或者提供數據處理入口
  • View: 視圖層,負責接收Presenter通知,進行界面更新
  • Presenter: View與Model的樞紐層,負責接收View層的命令,從Model層讀取並處理好數據後,通知View進行界面更新。

僅僅靠上面的文字來進行分層說明略顯空洞,因此這裏咱們來經過一個簡單的sample代碼來作MVP分層概念說明, 以下方是個簡單的登陸頁面的MVP實現:github

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. LoginViewapi

interface LoginView:MVPView {
    fun onLoginSuccess()
    fun onLoginFailed()
}
複製代碼

繼承並擴展MVPView接口。不少人喜歡直接將此類做爲MVP中的V層,可是實際上,我更願意稱此爲通訊協議接口,做用是由V層提供給P層進行P-V綁定,用於在P層中通知V層進行界面更新,相似於提供了一個Callback給P層進行使用bash

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協議接口,接收任務處理回調進行界面更新app

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通訊流程,可分爲如下幾步:

  1. V層向P層發起具體的處理任務
  2. P層接收到V層發起的任務。調用M層的api,獲取到原始數據
  3. P層對從M層獲取到的原始數據進行預處理(本示例由於較簡單,故沒有這一步)。
  4. 處理完畢後的數據。經過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?) {...}
}
複製代碼

能夠看到此鏈接器幹了如下幾件事:

  1. addPresenterremovePresenter:向容器presenters中添加或移除presenter實例作到對單頁面綁定多個presenter的效果。
  2. dispatchOnXXX:經過已添加的presenter進行生命週期通知. 作到V-P生命週期綁定的效果
  3. 在接收到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模塊。僅僅提供了MVPViewMVPPresenterMVPDispatcher這三個基礎支持類。避免引入無用代碼。

源碼連接

  • EasyAndroid開源組件庫地址

https://github.com/yjfnypeu/EasyAndroid

  • 基礎支持類MVPViewMVPPresenterMVPDispatcher源碼地址

https://github.com/yjfnypeu/EasyAndroid/tree/master/utils/src/main/java/com/haoge/easyandroid/mvp

  • V層基類實現簡單sample示例代碼地址

https://github.com/yjfnypeu/EasyAndroid/tree/master/app/src/main/java/com/haoge/sample/easyandroid/activities/mvp

相關文章
相關標籤/搜索