Kotlin-MVP框架搭建

作者: 雷小歪 | 来源:发表于2018-04-17 19:03 被阅读388次

    Github地址:https://github.com/cn-ljb/mvp-kotlin

    该项目是本人使用Kotlin语言搭建的Android MVP架构实现案例,已在部分小项目中使用,感兴趣的同学可以看看,欢迎指证不足。

    什么是MVP架构?

    MVP架构的概念其实也不必我多说了,网上有大把大把的详解文章,这里就简单介绍下:

    通常一般的Android项目结构,我们会在Activity\Fragment中编写大量代码,例如:网络请求、数据填充、页面切换等等,这种项目结构宏观的称之为MVC。

    MVC:我们可以把数据源(网络请求、IO...)看作Model层,xml等布局文件看作View层,Activity\Fragment看作Controller层。但在android中xml能力太薄弱了,以至于Activity不得不做很多本不属于它的工作。

    MVP:在MVP架构中Model层与MVC一样作为数据源,不过将Activity\Fragment都看作为View层的一部分负责数据的展示和填充,将Model层与View层的关联操作交给了Presenter层。

    一个基本的MVP架构图如下:

    image

    与之前的Android MVC相比,不仅Activity的分工不明确问题得到了解决,还带来另一个好处:Model层与View层不再直接可见,耦合问题得到解决。

    该项目MVP架构

    在此基础上,该项目中的MVP架构对每个模块进行细化,大致架构图如下:

    68747470733a2f2f692e696d6775722e636f6d2f65384675694d442e706e67.png
    • 1、View层根据自己的需要继承对应的BaseMvpActivity\BaseMvpFragment\BaseMvpFragmentActivity,并实现createPresenter()函数,它们提供基础的View层模版;

        /**
         *  1、继承BaseMvpActivity
         *  2、通过泛型告诉View层,当前Presenter的契约接口LoginContract.IPresenter
         *  3、实现自己的契约接口LoginContract.IView
         */
        class LoginActivity : BaseMvpActivity<LoginContract.IPresenter>(), LoginContract.IView {
        
            override fun createPresenter() = LoginPresenter(this)
            ...
        }   
      
    • 2、Presenter层提供了基础的IBasePresent接口模板,考虑到整个项目使用rxjava2作为异步库,为了方便管理rx生命周期,额外提供了一个BaseRxLifePresenter抽象类;

        /**
         * 1、继承BaseRxLifePresenter
         * 2、通过泛型告诉Presenter层,当前View的契约接口LoginContract.IView
         * 3、实现自身的契约接口LoginContract.IPresenter
         */
        class LoginPresenter(mvpView: LoginContract.IView) : BaseRxLifePresenter<LoginContract.IView>(mvpView), LoginContract.IPresenter {
            
            //rxjava生命周期管理举例
            override fun delayGoHomeTask() {
                Observable.timer(1500, TimeUnit.MILLISECONDS)
                        .subscribe { getMvpView().goHome() }
                        .bindRxLifeEx(RxLife.ON_DESTROY)
            }
                
            //登录功能
            override fun login(userName: String) {
                RxUtils.dispose(mLoginDisposable)
                mLoginDisposable = UserProtocol.getUserInfoByName(userName)
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribeEx({
                            if (it.message.isNullOrBlank()) {
                                getMvpView().loginSuccess()
                            } else {
                                getMvpView().loginError(it.message)
                            }
                        }, {
                            getMvpView().loginError(null)
                        }).bindRxLifeEx(RxLife.ON_DESTROY)
            }
        }
      
    • 3、View层与Presenter层的交互通过接口的形式规范化行为进行解耦。例如上方的LoginActivity与LoginPresenter的交互范围都在LoginContract中进行限制;

        /**
         * 登录页View层\Presenter层通讯契约接口
         */
        interface LoginContract {
        
            interface IView : IBaseViewContract {
                fun loginSuccess()
                fun loginError(errorMsg: String?)
                fun goHome()
            }
        
            interface IPresenter : IBasePresenterContract {
                fun login(userName: String)
                fun delayGoHomeTask()
            }
        }
      
    • 4、Modle层封装Protocol,用于包装数据源并提供转换为Observable的函数,从而方便与Rxjava2结合使用(项目中提供两个基础的BaseDAOProtocol、BaseHttpProtocol,也可自行定义适合自己的数据源封装类),并实现自身向Presenter公开的约束接口;

        object UserProtocol :  BaseHttpProtocol(), IUserHttp {
        
           override fun getUserInfoByName(userName: String): Observable<User> {
                val url = "$HTTP_API_DOMAIN/users/${nvl(userName)}"
                return createObservable(url, XgoHttpClient.METHOD_GET) {
                    JsonParser.fromJsonObj(it, User::class.java)
                }
            }
            
            ...
        }
      

      --------------- 分割线 ------------------

        //Model层对外提供的约束接口
        interface IUserHttp : HttpInterface {
            /**
             * @param userName 用户名
             * @return  用户基本信息
             * */
            fun getUserInfoByName(userName: String): Observable<User>
        
            ...
        }
      
    • 5、每个Protocol对象建议通过Factory产出;

        object HttpFactory {
        
            private val mHttpGroup = HttpFactoryGroup()
        
            @Suppress("UNCHECKED_CAST")
            fun <T : HttpInterface> getProtocol(clazz: Class<T>): T {
                return mHttpGroup.getProtocol(clazz) ?: registerNewProtocol(clazz)
            }
        
            @Suppress("UNCHECKED_CAST")
            private fun <T : HttpInterface> registerNewProtocol(clazz: Class<T>): T {
                //TODO 在此处注册Http接口
                val protocol = when (clazz) {
                    IUserHttp::class.java -> UserHttpProtocol
                    else -> throw IllegalStateException("Http Interface Class Object NotFound : ${clazz.name}")
                } as T
                mHttpGroup.register(clazz, protocol)
                return protocol
            }
        }
      

    例如:通过HttpFactory获取UserHttpProtocol的父级IUserHttp接口引用,而不是它的自身引用,从而避免直接操作接口约束之外的公共域:

    HttpFactory.getProtocol(IUserHttp::class.java).getUserInfoByName(userName)
    
    • 6、一个View对应一个Presenter,View与Presenter交互通过Contract接口进行约束,一个Presenter通过Factory可操作多个Protocol,每个Protocol都是单例.

    截图:

    anim.gif

    相关文章

      网友评论

      • 雷小歪:## 20180810更新

        本次更新内容:

        * 1、网络库单独提为netlib;
        * 2、优化dao相关操作;
      • 雷小歪:## 20180806更新

        本次更新内容(该框架已在公司内部使用,目前未发现什么未知问题):

        * 1、为了方便使用,单独提为一个Module;
        * 2、考虑到实际开发,将网络层更换为Retrofit+OkHttp3,Dao暂时没有替换;
      • 雷小歪:*** 20180507更新 ***

        本次更新内容:

        1、抽取Protocol接口,解耦Presenter与Model层,Presenter仅持有Protocol接口引用;

        2、为Model层创建静态Factory,投入Protocol接口Class对象产出对应引用;

        3、由于Factory为静态工厂,所以需要我们手动进行接口注册,现提供了两种注册方式,详见HttpFactory\DaoFactory.
      • 雷小歪:*** 2018-04-27更新 ***
        本次更新主要解决两个问题:
        1、使用BaseRxLifePresenter的导致Contract中Presenter契约接口必须是抽象类问题——将BaseRxLifePresenter与Contract接口抽离开来,BaseRxLifePresenter单独实现Presenter声明周期操作,Contract的Presenter契约接口定义自身独有通讯约束,从而达到面向接口编程的初衷;
        2、同时解决View层直接持有的Presenter层对象,导致除Contract接口约束的函数外,依旧可调用Presenter自身公共函数问题——仅让View层持有Contract中Prestener的接口引用。

      本文标题:Kotlin-MVP框架搭建

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