Android MVP && MVVM深度解析

前言

相信很多同學(xué)對MVP和mvvm都玩的很6了,但本文還是想從2個框架的特性位喂、優(yōu)缺點(diǎn)來深層次解析一下,幫助大家更好的理解框架乱灵。本文有深度塑崖,也有故事,下面開車痛倚。

MVP

MVP.png

這里引用官方的一張圖來簡單介紹MVP模式规婆,可以看出Model層是真正處理數(shù)據(jù)的,Presenter是聯(lián)系M和V的中介蝉稳,P持有M和V的引用抒蚜,P和V是雙向引用。

看一個最基礎(chǔ)的MVP

##Model
class LoginModel : BaseModel() {
    fun login(userName: String, pwd: String): Int {
        //...省略網(wǎng)絡(luò)請求
        return 1
    }
}

##Presenter
class LoginPresenter(var iLoginView: ILoginView) :
    BasePresenter<LoginModel, ILoginView>(iLoginView) {
    init {
        mModel = LoginModel()
    }

    fun login(userName: String, pwd: String) {
        var loginResult = mModel?.login(userName, pwd)
        iLoginView.loginResult(loginResult == 1)
    }
}

##View
class MVPActivity : BaseMVPActivity<LoginModel,ILoginView>(),ILoginView {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_mvp)
        
        loginBtn.setOnClickListener { 
            (mPresenter as LoginPresenter).login("u1","123")
        }

    }

    override fun initPresenter() {
        mPresenter=LoginPresenter(this)
    }

    override fun loginResult(isSuccess: Boolean) {
        TODO("Not yet implemented")
    }

    override fun showLoading() {
        TODO("Not yet implemented")
    }

    override fun dismissLoading() {
        TODO("Not yet implemented")
    }

    override fun loadFailure() {
        TODO("Not yet implemented")
    }
}

這個案例非常簡單耘戚,就是一個登錄校驗操作嗡髓,可以看出Activity把業(yè)務(wù)邏輯都委托給Presenter操作,Activity只需要做發(fā)出請求/根據(jù)請求結(jié)果展示UI即可收津,從分層思想上來說還是很清晰的饿这。下面先小結(jié)一下:

1.核心要點(diǎn)

把數(shù)據(jù)和業(yè)務(wù)邏輯從視圖層(View)剝離出來,V和P通過接口回調(diào)來通信撞秋。

2长捧、優(yōu)點(diǎn)

  • 這種分層思想在一定程度實現(xiàn)了解耦,符合類的單一職責(zé)設(shè)計原則吻贿;
  • m串结、v、p 3層都可以復(fù)用舅列,p也可以做單獨(dú)的單元測試奉芦;

從上面的代碼可以看出至少2點(diǎn),第1剧蹂,Activity/Fragment職責(zé)變輕了声功,代碼量也就少了;第2宠叼,像登錄操作在大多數(shù)項目是有多處會用到的先巴,這樣的話,就可以把它單獨(dú)抽出來冒冬,下次只需要創(chuàng)建LoginPresenter對象就可以調(diào)用登錄功能了伸蚯,總結(jié)一下就是那些可以復(fù)用的邏輯都可以抽離成單獨(dú)的MVP。在沒有UI界面的時候简烤,你甚至可以單獨(dú)調(diào)試Presenter剂邮。

那MVP模式有沒有什么缺點(diǎn)或者不足呢?答案是有的横侦。第1挥萌,MVP充斥著大量的接口绰姻,你的model、view引瀑、presenter都需要寫接口狂芋,這個任務(wù)量是很繁重的,而且類的數(shù)量會很快膨脹憨栽;第2帜矾,V和P還有一定耦合,如果V層某個UI元素更改屑柔,那相關(guān)的接口也需要更改屡萤,非常麻煩。第3掸宛,內(nèi)存泄漏問題灭衷,比如說,Activity還在做網(wǎng)絡(luò)請求旁涤,用戶等不及退出了翔曲,由于P持有View的引用,Activity無法及時回收劈愚,就發(fā)生了內(nèi)存泄漏瞳遍;小結(jié)一下:

3、缺點(diǎn)

  • 接口太多菌羽,類膨脹問題掠械;
  • 在業(yè)務(wù)邏輯比較復(fù)雜的時候,接口粒度大小不好控制注祖。粒度太小猾蒂,就會存在大量接口的情況;粒度太大是晨,解耦效果不好肚菠。
  • P和V需要通過接口交互,還是存在一定耦合罩缴,算不上真正的解耦蚊逢;如果接口有所變化的時候,需要改動的地方太多;
  • 內(nèi)存泄漏和空指針問題箫章。由于P和V是互相引用烙荷,如果頁面銷毀時P還有正在進(jìn)行的任務(wù),那Activity無法回收檬寂,就發(fā)生了內(nèi)存泄漏终抽。

下面針對這些缺點(diǎn),提出一些解決思路

接口太多,類膨脹問題
網(wǎng)上一些方案是引入契約類昼伴,就是接口套接口的形式匾旭,把MVP相關(guān)的接口都整合在一起。Google官方的demo確實引入了Contract亩码。

public interface Contract {

    interface Model extends BaseModel {

    }

    interface View extends BaseView<Presenter> {

    }


    interface Presenter extends BasePresenter {

    }
}

但是這樣其實是不好的。你可能會覺得這樣讓邏輯更緊密野瘦,代碼更好讀描沟。但這樣做,反而違背了mvp解耦的本質(zhì)鞭光,讓代碼更加復(fù)雜了吏廉。此外,在復(fù)用的情況惰许,比如一個Presenter有多個Model席覆,一個Activity有多個Presenter,這種情況下用契約類就比較復(fù)雜汹买,不好處理佩伤。

接口粒度
之前說過接口粒度大小難以控制。先說一下粒度太小晦毙,如果多個Activity有相同的回調(diào)方法生巡,我把他做成單獨(dú)的接口,比如我的頁面有網(wǎng)絡(luò)請求中见妒、請求失敗孤荣、登錄、上傳圖片等可以復(fù)用的接口方法须揣,那我就需要實現(xiàn)多個接口盐股,這樣會造成接口太多。如果粒度太大耻卡,每個接口會有很多方法疯汁,就會出現(xiàn)很多重復(fù)的內(nèi)容,也就變得耦合了卵酪。
再舉個例子涛目,我有一個業(yè)務(wù),包含增刪改查4個方法凛澎,但是A頁面只需改查霹肝、B頁面只需要增刪,按接口設(shè)計來說塑煎,我應(yīng)該寫在一起吧沫换,這樣就會造成2個頁面都會實現(xiàn)2個空方法,這是粒度太大;如果我把它拆成2個接口吧讯赏,接口數(shù)量會增加垮兑,這是粒度太小。關(guān)于接口粒度漱挎,目前不好解決系枪。

P和V耦合
此外P和V之間通過接口回調(diào)來交互,還是存在耦合的磕谅,沒有完全實現(xiàn)視圖和業(yè)務(wù)的解耦私爷。如果因為需求變更導(dǎo)致接口有所改動,需要改動的地方太多膊夹〕幕耄總的來說,關(guān)于接口問題目前來說是沒有完美的解決辦法的放刨。

內(nèi)存泄漏問題
剛才說過內(nèi)存泄漏是因為P持有V的引用工秩,導(dǎo)致gc來的時候發(fā)現(xiàn)m->p->v這條GC引用鏈存在,就不會回收Activity进统,于是Activity內(nèi)存泄漏了助币。解決思路:在onDestroy()斷開引用關(guān)系,并取消網(wǎng)絡(luò)任務(wù)螟碎。

override fun onDestroy() {
        super.onDestroy()
       //  防止內(nèi)存泄漏
        mPresenter?.onDestroy()
        mPresenter = null
    }

class ePresenter{
      fun onDestroy() {
          //取消網(wǎng)絡(luò)請求
          cancalNetTask()
          mView = null
    }
}

也可以通過弱引用來解決

class TestPresenter<M : BaseModel, V : IBaseView>(view: V) {

    var iView: WeakReference<V>? = null

    init {
        iView = WeakReference(view)
    }

    fun onDestroy() {
        iView?.clear()
        iView = null
    }
}

MVVM

說一段歷史

現(xiàn)在網(wǎng)上仍然充斥著大量不規(guī)范的MVVM的文章奠支,百度首頁很多都是,其中也包括我在17年寫的一篇抚芦,所謂不規(guī)范是指這些MVVM僅僅是在MVP基礎(chǔ)上引入DataBinding倍谜,就被當(dāng)作MVVM模式了。 我來解釋一下這個情況叉抡,mvvm和MVP的區(qū)別有2點(diǎn)尔崔,第1,vm和v是單向引用褥民;第2季春,基于觀察者模式把數(shù)據(jù)從vm傳給View,v和vm不再需要接口回調(diào)來聯(lián)系消返。

mvvm其實分為2個階段载弄,在2017之前,是基于databinding的撵颊,在2017之后是基于AAC架構(gòu)的宇攻,也就是livedata、viewmodel相關(guān)倡勇。由于在16逞刷,17年Jetpack相關(guān)的Viewmodel、LiveData還沒有推廣開,在2017之前要把數(shù)據(jù)從vm傳給v是比較麻煩夸浅,不用接口回調(diào)的話仑最,用觀察者模式來做是比較方便的,但是那時候livedata還沒有出來帆喇,就只能用databinding的觀察者模式或自己手寫觀察者警医,由于這樣做比較麻煩,很多人甚至直接沿用接口回調(diào)去更新UI數(shù)據(jù)坯钦。正是由于當(dāng)時的技術(shù)和認(rèn)知不足以及很多誤導(dǎo)博客的廣泛傳播预皇,導(dǎo)致了一部分人以為MVP+databinding就是mvvm了。

故事說完了葫笼,下面來了解一下MVVM的特性和實現(xiàn)吧深啤。

mvvm.png

圖片來自https://juejin.im/post/5c2f43796fb9a04a04412a18

1拗馒、核心要點(diǎn)
數(shù)據(jù)和UI完全解耦路星、數(shù)據(jù)驅(qū)動、不存在內(nèi)存泄漏問題诱桂、代碼更簡潔洋丐。可以說解決了MVVM大部分弊端挥等。

2友绝、優(yōu)點(diǎn)

從設(shè)計上解決了內(nèi)存問題
在MVP中存在內(nèi)存泄漏問題,需要手動管理肝劲,很是麻煩迁客;而MVVM從系統(tǒng)設(shè)計上解決了這個問題,開發(fā)者再也不需要擔(dān)心內(nèi)存問題了辞槐。V和VM是單向引用掷漱,VM不持有任何View相關(guān)的對象,這樣就解決了內(nèi)存泄漏榄檬。由于ViewModel和LiveData內(nèi)部都是通過lifecycle關(guān)聯(lián)生命周期卜范,會在頁面正常銷毀的時候(onDestory),解除觀察者鹿榜,銷毀自身海雪。

數(shù)據(jù)驅(qū)動
數(shù)據(jù)變化自動更新UI,用戶輸入和操作需要數(shù)據(jù)自動更新舱殿,可以通過LiveData和DataBinding來完成奥裸,二者都是基于觀察者模式。

數(shù)據(jù)和UI完全解耦
數(shù)據(jù)和業(yè)務(wù)邏輯都在的ViewModel中沪袭,ViewModel只需要關(guān)注數(shù)據(jù)和業(yè)務(wù)邏輯刺彩,完全不需要管UI操作和變化。

更新UI
在子線程操作完數(shù)據(jù)之后,可以直接更新ViewModel的數(shù)據(jù)即可创倔,不需要考慮線程切換嗡害,因為ViewModel中的LiveData已經(jīng)幫我們做了這個事情。

mvvm基礎(chǔ)

##Model
class NewsModel {
    /**
     * 模擬加載網(wǎng)絡(luò)數(shù)據(jù)
     */
    fun loadDataFromNet(): String {
        //...省略網(wǎng)絡(luò)操作
        return "this data from net"
    }
}

##ViewModel
class NewsViewModel : ViewModel() {
    private val mModel by lazy {
        NewsModel()
    }

    val liveData = MutableLiveData<String>()

    fun loadData() {
        var result = mModel.loadDataFromNet()
        //更新數(shù)據(jù)
        liveData.value = result
    }
}


##Activity
class MvvmActivity : AppCompatActivity() {

    private val newsVm: NewsViewModel by lazy {
          ViewModelProvider(this).get(NewsViewModel::class.java)
      }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_mvvm)

        initLiveData()
        newsVm.loadData()
    }

    private fun initLiveData() {
        newsVm.liveData.observe(this, object : Observer<String> {
            override fun onChanged(t: String?) {
                //更新UI
                textView.text = t
            }
        })
    }
}

這是最基礎(chǔ)的基于AAC方案的MVVM畦攘,沒有過度封裝霸妹。當(dāng)然你也可以結(jié)合databinding庫來使用。

我還想說明一點(diǎn)知押,一個項目中你可以同時使用mvc叹螟、MVP、mvvm台盯,這取決于你的業(yè)務(wù)罢绽,記住一點(diǎn),框架始終是為業(yè)務(wù)服務(wù)的静盅。歡迎下方留言良价,說出你的觀點(diǎn)。

感謝以下作者

Android架構(gòu)設(shè)計---MVP模式第(二)篇蒿叠,如何減少類爆炸
https://blog.csdn.net/qq137722697/article/details/78275882
https://blog.csdn.net/u011033906/article/details/89448350
https://tech.meituan.com/2016/11/11/android-mvvm.html
Android MVVM架構(gòu)分析
從最簡單的Android MVP講起

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末明垢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子市咽,更是在濱河造成了極大的恐慌痊银,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件施绎,死亡現(xiàn)場離奇詭異溯革,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)谷醉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門致稀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人孤紧,你說我怎么就攤上這事豺裆。” “怎么了号显?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵臭猜,是天一觀的道長。 經(jīng)常有香客問我押蚤,道長蔑歌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任揽碘,我火速辦了婚禮次屠,結(jié)果婚禮上园匹,老公的妹妹穿的比我還像新娘。我一直安慰自己劫灶,他們只是感情好裸违,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著本昏,像睡著了一般供汛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上涌穆,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天怔昨,我揣著相機(jī)與錄音,去河邊找鬼宿稀。 笑死趁舀,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的祝沸。 我是一名探鬼主播矮烹,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼奋隶!你這毒婦竟也來了擂送?” 一聲冷哼從身側(cè)響起悦荒,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤唯欣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后搬味,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體境氢,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年碰纬,在試婚紗的時候發(fā)現(xiàn)自己被綠了萍聊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡悦析,死狀恐怖寿桨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情强戴,我是刑警寧澤亭螟,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站骑歹,受9級特大地震影響预烙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜道媚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一扁掸、第九天 我趴在偏房一處隱蔽的房頂上張望翘县。 院中可真熱鬧,春花似錦谴分、人聲如沸锈麸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掐隐。三九已至,卻和暖如春钞馁,著一層夾襖步出監(jiān)牢的瞬間虑省,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工僧凰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留探颈,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓训措,卻偏偏與公主長得像伪节,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子绩鸣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355