前言
相信很多同學(xué)對MVP和mvvm都玩的很6了,但本文還是想從2個框架的特性位喂、優(yōu)缺點(diǎn)來深層次解析一下,幫助大家更好的理解框架乱灵。本文有深度塑崖,也有故事,下面開車痛倚。
MVP
這里引用官方的一張圖來簡單介紹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)吧深啤。
圖片來自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講起