Android MVP框架如何快速构建及一P对多M优化

  转发请注明出处:https://www.jianshu.com/p/b3e15cbf83a9
  开发android的几年时间,从一开始接触到MVP模式,就对于代码逻辑分离管理产生了浓厚的兴趣,对官方MVP模式进行研究与实战,发现在实际应用中,官方MVP模式不能满足绝大部分场景,近一年来,在不断的应用中,对MVP模式进行了改进,本文将对其一一解说,如果有更好的做法,还望能一起探讨。
  MVP模式的优势与弊端之类的,网上已经有太多的文章,不再对其进行述说,本文只对项目是如何构造的进行述说与解释:

一、MVP三部分简述

1.Model的基类
abstract class BaseMvpModel {

    private var mContext: Context? = null
    fun getContext(): Context? {
        return mContext
    }
    /*************分界线以上为外部使用的方法,以下为私有或生命周期方法不应主动调用*****************/
    fun onCreate(context: Context?) {
        mContext = context
    }
    fun onDestroy() {
        mContext = null
    }
}

  Model专门与数据打交道,此处会维护一个成员变量Context,Context由Presenter生成Model时传入,Context的生命周期跟界面视图生命周期保持一致,onCreate()与onDestroy()方法均为框架自动调用,调用者只需在生命周期内调用getContext()方法即可获取到Context对象,保持封闭性。
  此处维护Context的原因,是在于处理或者获取数据时,并不是所有数据均从网上获取,还有很多是本地数据,如SharedPreferences,或者是获取本地已安装APP信息,静默卸载APP时的处理等等,均需要用到Context。

2.Presenter的基类
abstract class BaseMvpPresenter<V> {

    private var mContext: Context? = null
    fun getContext(): Context? {
        return mContext
    }

    /**
     * MVP模式中持有View对象
     */
    private var mView: V? = null
    fun getView(): V? {
        return mView
    }

    /**
     * 此方法直接获取Model,自动生成管理
     */
    fun <M : BaseMvpModel> getModel(modelClass: Class<M>): M {
        val typeName = modelClass.name
        var model = mModelMap?.get(typeName)
        if (model == null) {
            model = modelClass.newInstance() as BaseMvpModel
            model.onCreate(mContext)
            mModelMap?.put(typeName, model)
        }
        @Suppress("UNCHECKED_CAST")
        return model as M
    }

    /*************分界线以上为外部使用的方法,以下为私有或生命周期方法不应主动调用*****************/

    /**
     * 保存所有Model的Map
     */
    private var mModelMap: ArrayMap<String, BaseMvpModel>? = ArrayMap()

    fun onCreate(context: Context?) {
        mContext = context
    }

    fun attach(view: V) {
        mView = view
    }

    fun detach() {
        cleanAllModel()
        mView = null
        mContext = null
    }

    private fun cleanAllModel() {
        val iterator = mModelMap?.keys?.iterator()
        if (iterator != null)
            while (iterator.hasNext()) {
                val model = mModelMap?.get(iterator.next()) as BaseMvpModel
                model.onDestroy()
            }
        mModelMap?.clear()
        mModelMap = null
    }
}

  Presenter是View与Model的桥梁,所以要同时处理好View与Model的初始化与销毁,此处的View使用泛型,类型转换都是自动和隐式的,提高代码的重用率。
  Presenter内部维护的Context与View视图,由Activity生成Presenter时传入,同样与界面视图生命周期保持一致,Context是为了生成Model时动态传参,有时候也需要在此处使用,View则不必多说,用于视图的回调。
  此处唯一特殊的就是在对Model的维护上,使用了泛型与反射的机制,由于经常会有多个不同的Model,所以此处用一个ArrayMap来统一管理Model,获取时调用getModel()方法,如果ArrayMap里面没有则会反射生成(泛型约束不存在强转换错误问题),同时保存在ArrayMap里面以便复用,所有的Model均会在界面视图销毁时统一销毁,外部只需要注意在生命周期内调用即可。

3.View的基类
abstract class BaseMvpActivity<V, P : BaseMvpPresenter<V>> : BaseActivity() {

    /**
     * MVP模式中Presenter对象
     */
    private var mPresenter: P? = null
    fun getPresenter(): P? {
        if (mPresenter == null) {
            throw RuntimeException("Presenter is Null, You can't get it at this time!")
        }
        return mPresenter
    }

    /*************分界线以上为外部使用的方法,以下为私有或生命周期方法不应主动调用*****************/

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mPresenter = createPresenter()
        @Suppress("UNCHECKED_CAST")
        mPresenter?.attach(this as V)
    }

    override fun onDestroy() {
        mPresenter?.detach()
        mPresenter = null
        super.onDestroy()
    }

    /**
     * 此方法自动生成Presenter
     */
    private fun <P : BaseMvpPresenter<V>> createPresenter(): P {
        val type = javaClass.genericSuperclass as ParameterizedType
        val actualTypeArguments = type.actualTypeArguments
        @Suppress("UNCHECKED_CAST")
        val presenterClass = actualTypeArguments[1] as Class<P>
        val presenter = presenterClass.newInstance() as BaseMvpPresenter<V>
        presenter.onCreate(this)
        @Suppress("UNCHECKED_CAST")
        return presenter as P
    }
}

  View同样使用泛型,V泛型为Presenter已经定义好要传入的视图实例,P泛型定义为必须是BaseMvpPresenter的子类,注意mPresenter?.attach(this as V)此代码同时也约束了View视图必须同时实现V,否则运行时会类型转换异常。
  初始化Presenter时同样使用泛型与反射的机制动态生成(泛型约束不存在强转换错误问题),且与界面视图生命周期一致,外部只需要注意在生命周期内调用getPresenter()获取即可。
  为什么定义V泛型:View视图必须实现V泛型,关联上Presenter需求的V泛型,实际上V泛型就是View视图的接口,定义了刷新视图的方法,关联之后能在Presenter直接调取从而实现刷新界面。

二、实际使用场景

  说得再多,不如直接使用看看效果如何来得实在,我们下面来看下每个模块的实现类,应用场景属于我最常遇到的一P对多M

1.接口的定义
interface IMainContract {

    interface IMainView {

        fun showTitle(title: String)

        fun showUser(user: String)
    }

    interface IMainPresenter {

        fun getTitle()

        fun getUser()
    }

    interface IMainModel {

        fun getTitle(callback: ResultCallBack<String>)
    }
}
interface ICommonContract {

    interface ICommonModel {

        fun getUser(callback: ResultCallBack<String>)
    }
}

  ResultCallBack只是一个回调接口,里面有成功或失败的回调方法。
  这里我们简单定义了几个方法,展示标题与展示用户数据,假设展示标题是该界面特有,展示用户数据是所有界面共有,此时ICommonContract为所有界面共用。

2.Model的实现类
class MainModel : BaseMvpModel(), IMainContract.IMainModel {

    override fun getTitle(callback: ResultCallBack<String>) {
        callback.onSuccess("I am title by kotlin")
    }
}
class CommonModel : BaseMvpModel(), ICommonContract.ICommonModel {

    override fun getUser(callback: ResultCallBack<String>) {
        callback.onSuccess("Mr.T")
    }
}

  Model的实现类比较简单,这里只为了示例用法,所以都直接返回字符串,同样CommonModel为所有界面共用。

3.Presenter的实现类
class MainPresenter : BaseMvpPresenter<IMainContract.IMainView>(), IMainContract.IMainPresenter {

    override fun getTitle() {
        getModel(MainModel::class.java).getTitle(object : ResultCallBack<String>() {
            override fun onSuccess(data: String, code: Int, msg: String) {
                getView()?.showTitle(data)
            }

            override fun onFail(e: Exception, code: Int, msg: String) {

            }
        })
    }

    override fun getUser() {
        getModel(CommonModel::class.java).getUser(object : ResultCallBack<String>() {
            override fun onSuccess(data: String, code: Int, msg: String) {
                getView()?.showUser(data)
            }

            override fun onFail(e: Exception, code: Int, msg: String) {

            }
        })
    }
}

  Presenter的实现类是重点,此时前面所做的一切,价值就提现出来了,Presenter的实现类,仅需要重写业务逻辑所定义的方法,实现只关心业务逻辑而并不需要操心其余有关生命周期的事情,并且我们在这里也看到,只需要传对应Model的Class就能使用其内部的方法,getView()也能直接调用对应的方法,不需要再进行强转,这就是泛型的好处,一心关注业务逻辑

3.View的实现类
class MainActivity : BaseMvpActivity<IMainContract.IMainView, MainPresenter>(), IMainContract.IMainView {

    override val mContentViewRes: Int = R.layout.activity_main

    override fun initView() {

    }

    override fun initData() {
        getPresenter()?.getTitle()
        getPresenter()?.getUser()
    }

    override fun showTitle(title: String) {
        mTitle_TV.text = title
    }

    override fun showUser(user: String) {
        mUser_TV.text = user
    }
}

  View的实现类是同样也是重点,mContentViewRes变量、initView()函数、initData()函数这三个为在BaseMvpActivity继承的BaseActivity所定义的初始化的接口,有兴趣可以看源码,这里只需关心MVP请求数据的逻辑,我们看到,在initData()初始化时候,直接可以调用Presenter获取数据,并且在回调方法里面实时刷新。

三、总结

  此时整个流程介绍完毕了,此时,在MVP的三个实现类中,均可以做到只关心业务逻辑而不需要去管其他,遇到业务逻辑大改的时候,为了不影响原来的逻辑,可以新写一个类替换原本的泛型类,不需要一发动全身,比如,此时遇到Presenter大改,可以重新写一个Presenter类的实现类,在Activity的泛型替换即可,不需要修改View的实现类,当然,你要是视图也大改的时候,就只能全部推倒了,但是无论如何,此时我们的代码结构就会变得异常清晰。
  还有一点需要提醒,或许有人觉得我在这里用了反射会不会很影响性能,其实一开始我也在担心这个问题,后面经过我的实战测试,实际上在比较复杂的界面中也只多出了几十毫秒,在人的感官上是根本感觉不出来,也有可能是反射在6.0以后的优化,加上现在的机器性能太好了的原因,所以不需要有这方面的担心,毕竟我不是在死循环反射(后期打算优化为用注解的方式,提高性能)。

  同时这个基础库已经开源,方便使用:
  1.确保项目的build.gradle

    allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }

  2.在模块中添加依赖,最新的版本请点击下面的链接跳转获取:

    dependencies {
            implementation 'com.github.MrTangFB:MVPCommon:1.0.1'
    }

  Demo源码介绍并获取最新版本
  该项目开源前是打算作为基础公共模块使用,后期还会不断更新迭代。
  如果你有什么问题,请私信或者在下方留言给我以便交流,必回哦。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容