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

寫在前面

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

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

而此篇博客將要介紹的並不是google的MVP模型。而是根據我自身理解所創建的一種MVP模型。與google的MVP模型相比,此種MVP模型具有以下一些優勢:

  1. 支持單頁面綁定多個Presenter進行使用,便於進行Presenter複用
  2. 對Presenter進行生命週期派發,
  3. 自動對Presenter進行View的綁定與解綁。
  4. 剔除契約類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通信流程,可分爲以下幾步:

  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

學習分享,共勉

題外話,我從事Android開發已經五年了,此前我指導過不少同行。但很少跟大家一起探討,正好最近我花了一個多月的時間整理出來一份包括不限於高級UI、性能優化、移動架構師、NDK、混合式開發(ReactNative+Weex)微信小程序、Flutter等全方面的Android進階實踐技術,今天暫且開放給有需要的人,若有關於此方面可以轉發+關注+點贊後加羣 878873098 領取,或者評論與我一起交流探討。

資料免費領取方式:轉發+關注+點贊後,加入點擊鏈接加入羣聊:Android高級開發交流羣(878873098)即可獲取免費領取方式!

重要的事說三遍,關注!關注!關注!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章