美文网首页Android开发Android开发经验谈
Android架构设计:手把手教你撸一个简洁而强大的MVP框架!

Android架构设计:手把手教你撸一个简洁而强大的MVP框架!

作者: 881ef7b85f62 | 来源:发表于2019-08-19 16:07 被阅读41次

    写在前面

    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 领取,或者评论与我一起交流探讨。

    image image

    资料免费领取方式:转发+关注+点赞后,加入点击链接加入群聊:Android高级开发交流群(878873098)即可获取免费领取方式!

    重要的事说三遍,关注!关注!关注!

    相关文章

      网友评论

        本文标题:Android架构设计:手把手教你撸一个简洁而强大的MVP框架!

        本文链接:https://www.haomeiwen.com/subject/vjqesctx.html