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

相关文章
相关标签/搜索