Android MVP框架如何快速構(gòu)建及一P對多M優(yōu)化

??轉(zhuǎn)發(fā)請注明出處:http://www.reibang.com/p/b3e15cbf83a9
??開發(fā)android的幾年時間怜校,從一開始接觸到MVP模式间影,就對于代碼邏輯分離管理產(chǎn)生了濃厚的興趣,對官方MVP模式進行研究與實戰(zhàn)茄茁,發(fā)現(xiàn)在實際應用中魂贬,官方MVP模式不能滿足絕大部分場景,近一年來裙顽,在不斷的應用中付燥,對MVP模式進行了改進,本文將對其一一解說愈犹,如果有更好的做法键科,還望能一起探討闻丑。
??MVP模式的優(yōu)勢與弊端之類的,網(wǎng)上已經(jīng)有太多的文章勋颖,不再對其進行述說嗦嗡,本文只對項目是如何構(gòu)造的進行述說與解釋:

一、MVP三部分簡述

1.Model的基類
abstract class BaseMvpModel {

    private var mContext: Context? = null
    fun getContext(): Context? {
        return mContext
    }
    /*************分界線以上為外部使用的方法饭玲,以下為私有或生命周期方法不應主動調(diào)用*****************/
    fun onCreate(context: Context?) {
        mContext = context
    }
    fun onDestroy() {
        mContext = null
    }
}

??Model專門與數(shù)據(jù)打交道侥祭,此處會維護一個成員變量Context,Context由Presenter生成Model時傳入茄厘,Context的生命周期跟界面視圖生命周期保持一致矮冬,onCreate()與onDestroy()方法均為框架自動調(diào)用,調(diào)用者只需在生命周期內(nèi)調(diào)用getContext()方法即可獲取到Context對象次哈,保持封閉性胎署。
??此處維護Context的原因,是在于處理或者獲取數(shù)據(jù)時亿乳,并不是所有數(shù)據(jù)均從網(wǎng)上獲取硝拧,還有很多是本地數(shù)據(jù),如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
    }

    /*************分界線以上為外部使用的方法,以下為私有或生命周期方法不應主動調(diào)用*****************/

    /**
     * 保存所有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使用泛型,類型轉(zhuǎn)換都是自動和隱式的勋磕,提高代碼的重用率妈候。
??Presenter內(nèi)部維護的Context與View視圖,由Activity生成Presenter時傳入挂滓,同樣與界面視圖生命周期保持一致苦银,Context是為了生成Model時動態(tài)傳參,有時候也需要在此處使用赶站,View則不必多說幔虏,用于視圖的回調(diào)。
??此處唯一特殊的就是在對Model的維護上贝椿,使用了泛型與反射的機制想括,由于經(jīng)常會有多個不同的Model,所以此處用一個ArrayMap來統(tǒng)一管理Model烙博,獲取時調(diào)用getModel()方法瑟蜈,如果ArrayMap里面沒有則會反射生成(泛型約束不存在強轉(zhuǎn)換錯誤問題)烟逊,同時保存在ArrayMap里面以便復用,所有的Model均會在界面視圖銷毀時統(tǒng)一銷毀踪栋,外部只需要注意在生命周期內(nèi)調(diào)用即可焙格。

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
    }

    /*************分界線以上為外部使用的方法,以下為私有或生命周期方法不應主動調(diào)用*****************/

    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已經(jīng)定義好要傳入的視圖實例眷唉,P泛型定義為必須是BaseMvpPresenter的子類,注意mPresenter?.attach(this as V)此代碼同時也約束了View視圖必須同時實現(xiàn)V囤官,否則運行時會類型轉(zhuǎn)換異常冬阳。
??初始化Presenter時同樣使用泛型與反射的機制動態(tài)生成(泛型約束不存在強轉(zhuǎn)換錯誤問題),且與界面視圖生命周期一致党饮,外部只需要注意在生命周期內(nèi)調(diào)用getPresenter()獲取即可肝陪。
??為什么定義V泛型:View視圖必須實現(xiàn)V泛型,關(guān)聯(lián)上Presenter需求的V泛型刑顺,實際上V泛型就是View視圖的接口氯窍,定義了刷新視圖的方法,關(guān)聯(lián)之后能在Presenter直接調(diào)取從而實現(xiàn)刷新界面蹲堂。

二狼讨、實際使用場景

??說得再多,不如直接使用看看效果如何來得實在柒竞,我們下面來看下每個模塊的實現(xiàn)類政供,應用場景屬于我最常遇到的一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只是一個回調(diào)接口朽基,里面有成功或失敗的回調(diào)方法布隔。
??這里我們簡單定義了幾個方法,展示標題與展示用戶數(shù)據(jù)稼虎,假設展示標題是該界面特有衅檀,展示用戶數(shù)據(jù)是所有界面共有,此時ICommonContract為所有界面共用霎俩。

2.Model的實現(xiàn)類
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的實現(xiàn)類比較簡單术吝,這里只為了示例用法,所以都直接返回字符串茸苇,同樣CommonModel為所有界面共用。

3.Presenter的實現(xiàn)類
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的實現(xiàn)類是重點沦寂,此時前面所做的一切学密,價值就提現(xiàn)出來了,Presenter的實現(xiàn)類传藏,僅需要重寫業(yè)務邏輯所定義的方法腻暮,實現(xiàn)只關(guān)心業(yè)務邏輯而并不需要操心其余有關(guān)生命周期的事情彤守,并且我們在這里也看到,只需要傳對應Model的Class就能使用其內(nèi)部的方法哭靖,getView()也能直接調(diào)用對應的方法具垫,不需要再進行強轉(zhuǎn),這就是泛型的好處试幽,一心關(guān)注業(yè)務邏輯筝蚕。

3.View的實現(xiàn)類
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的實現(xiàn)類是同樣也是重點,mContentViewRes變量铺坞、initView()函數(shù)起宽、initData()函數(shù)這三個為在BaseMvpActivity繼承的BaseActivity所定義的初始化的接口,有興趣可以看源碼济榨,這里只需關(guān)心MVP請求數(shù)據(jù)的邏輯坯沪,我們看到,在initData()初始化時候擒滑,直接可以調(diào)用Presenter獲取數(shù)據(jù)腐晾,并且在回調(diào)方法里面實時刷新。

三丐一、總結(jié)

??此時整個流程介紹完畢了藻糖,此時,在MVP的三個實現(xiàn)類中钝诚,均可以做到只關(guān)心業(yè)務邏輯而不需要去管其他颖御,遇到業(yè)務邏輯大改的時候,為了不影響原來的邏輯凝颇,可以新寫一個類替換原本的泛型類潘拱,不需要一發(fā)動全身,比如拧略,此時遇到Presenter大改芦岂,可以重新寫一個Presenter類的實現(xiàn)類,在Activity的泛型替換即可垫蛆,不需要修改View的實現(xiàn)類禽最,當然,你要是視圖也大改的時候袱饭,就只能全部推倒了川无,但是無論如何,此時我們的代碼結(jié)構(gòu)就會變得異常清晰虑乖。
??還有一點需要提醒懦趋,或許有人覺得我在這里用了反射會不會很影響性能,其實一開始我也在擔心這個問題疹味,后面經(jīng)過我的實戰(zhàn)測試仅叫,實際上在比較復雜的界面中也只多出了幾十毫秒帜篇,在人的感官上是根本感覺不出來,也有可能是反射在6.0以后的優(yōu)化诫咱,加上現(xiàn)在的機器性能太好了的原因笙隙,所以不需要有這方面的擔心,畢竟我不是在死循環(huán)反射(后期打算優(yōu)化為用注解的方式坎缭,提高性能)竟痰。

??同時這個基礎庫已經(jīng)開源,方便使用:
??1.確保項目的build.gradle

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

??2.在模塊中添加依賴幻锁,最新的版本請點擊下面的鏈接跳轉(zhuǎn)獲瓤痢:

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

??Demo源碼介紹并獲取最新版本
??該項目開源前是打算作為基礎公共模塊使用,后期還會不斷更新迭代哄尔。
??如果你有什么問題假消,請私信或者在下方留言給我以便交流,必回哦岭接。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末富拗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鸣戴,更是在濱河造成了極大的恐慌啃沪,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窄锅,死亡現(xiàn)場離奇詭異创千,居然都是意外死亡,警方通過查閱死者的電腦和手機入偷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門追驴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人疏之,你說我怎么就攤上這事殿雪。” “怎么了锋爪?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵丙曙,是天一觀的道長。 經(jīng)常有香客問我其骄,道長亏镰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任拯爽,我火速辦了婚禮拆挥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己纸兔,他們只是感情好,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布否副。 她就那樣靜靜地躺著汉矿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪备禀。 梳的紋絲不亂的頭發(fā)上洲拇,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音曲尸,去河邊找鬼赋续。 笑死,一個胖子當著我的面吹牛另患,可吹牛的內(nèi)容都是我干的纽乱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼昆箕,長吁一口氣:“原來是場噩夢啊……” “哼鸦列!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鹏倘,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤薯嗤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后纤泵,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體骆姐,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年捏题,在試婚紗的時候發(fā)現(xiàn)自己被綠了玻褪。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡涉馅,死狀恐怖归园,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情稚矿,我是刑警寧澤庸诱,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站晤揣,受9級特大地震影響桥爽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜昧识,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一钠四、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦缀去、人聲如沸侣灶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽褥影。三九已至,卻和暖如春咏雌,著一層夾襖步出監(jiān)牢的瞬間凡怎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工赊抖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留统倒,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓氛雪,卻偏偏與公主長得像房匆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子注暗,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容