前言
在開發(fā)模式的演進過程中 MVC俊马,MVP,MVVM一一登上舞臺。但是并不意味著MVVM一定就比MVC MVP優(yōu)秀。不同的項目有不同的體量索守,開發(fā)中要根據項目體量選擇合適的開發(fā)模式。
市面上介紹mvvm的項目不在少數,但是看了很多衙解,都在介紹源碼原理,開發(fā)中的踩坑過程,而且有的是過時的資料,卻很少見到能夠直接從項目需求入手幫助不熟悉MVVM的開發(fā)者從入門到熟悉原理角虫,再到框架優(yōu)化的均驶。這個空缺我來補充隶债,如果提及到一些其他原理性的東西不便長篇展示死讹,我會引用鏈接妓忍,或者引用他文的原文加以說明。
特別說明:
以下所有信息都基于截止到 2020年6月2日10點23分的最新官方資料和源碼版本。如果存在任何歷史版本的差異,本文不會過分糾結粱年。
正文大綱
- 有關MVVM的幾個重要組件
- ViewModel
- DataBinding
- LiveData
- LiveDataBus
- 案例實操
- MVVM的優(yōu)缺點
正文
有關MVVM的幾個重要組件
ViewModel
ViewModel 是androidx包中抽象類赐俗。它是谷歌開放給全世界開發(fā)者用來改善項目架構的一個組件粱快。
既然是探索ViewModel的本源漫雷,那就從它的官方注解開始吧谤辜。
這段話的大意是:
ViewModel是一個準備和管理Activity和Fragment的數據的類涡戳。它也可以掌控Activity、Fragment和應用中其他部分的通訊。
一個ViewModel總是關聯到一個域(Activity或Fragment)被創(chuàng)建乳丰。并且只要域是存活的夜郁,ViewModel就會一直被保留。比如。如果域是一個Activity,ViewModel就會存活恬汁,直到Activity被finish。
換句話說纹坐,這意味著瓷马,如果它的持有者由于配置改變而被銷毀時(比如屏幕旋轉)片林,ViewModel并不會被銷毀蒋伦。新的持有者實例韧献,將會僅僅重新連接到已經存在的ViewModel嚷炉。
ViewMode存在的目的绘证,就是為Activity/Fragment 獲得以及保留 必要信息忌栅。 Activity / Fragment 應該可以觀察到VIewModel的變化贫悄,ViewModel通常通過LiveData或者DataBinding 暴露信息。你也可以你自己喜歡的使用可觀察的結構框架。
ViewModel僅有的職責,就是為UI管理數據,它不應該訪問到你任何的View層級 或者 持有Activity 、Fragment的引用坑律。
谷歌爸爸其實已經把意思講的很明白淘捡,上面一段話中有幾個重點:
ViewModel 唯一的職責 就是 在內存中保留數據
多個Activity或者Fragment可以共用一個ViewModel
在屏幕旋轉時作彤,ViewModel 不會被重建,而只會連接到重新創(chuàng)建的Fragment/Activity
使用ViewModel有兩種方式灿渴,LiveData或者DataBinding(或者你可以自定義觀察者模式框架),用他們來暴露ViewModel給V層
核心功能
ViewModel的核心功能:在適當的時機執(zhí)行回收動作,也就是 onCleared() 函數釋放資源。而這個合適的時機,可以理解為 Activity銷毀,或者Fragment解綁。
借用一張圖來解釋,就是:
在整個Activity還處于存活狀態(tài)時,ViewModel都會存在。而當Activity被finish的時候,ViewModel的onCleared函數將會被執(zhí)行,我們可以自己定義函數內容,清理我們自己的資源掉房,在Activity被銷毀之后璧帝。該ViewModel也不再被任何對象持有,下次GC時它將被GC回收。
基本用法
創(chuàng)建一個新的項目,定義我們自己的UserModel類,繼承ViewModel:
import android.util.Log
import androidx.lifecycle.ViewModel
class UserModel : ViewModel() {
init {
Log.d("hankTag", "執(zhí)行ViewModel必要的初始化")
}
override fun onCleared() {
super.onCleared()
Log.d("hankTag", "執(zhí)行ViewModel 清理資源")
}
fun doAction() {
Log.d("hankTag", "執(zhí)行ViewModel doAction")
}
}
在View層使用定義好的ViewModel:
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 獲取ViewModel對象
val userModel = ViewModelProvider(this).get(UserModel::class.java)
// 使用ViewModel對象的函數
userModel.doAction()
}
}
就這么簡單,運行程序能看到日志:
同時ViewModelProvider也支持兩個參數的構造函數郑叠,除了上面的owner=this之外,還可以傳入另一個Factory參數婉宰。
如果不傳入這個Factory馒铃,源碼中會在拿到ViewModel的class對象之后通過無參構造函數進行反射創(chuàng)建對象。但是如果ViewModel要用有參構造函數來創(chuàng)建的話,那就必須借助Factory:
// ViewModel
class UserModel(i: Int, s: String) : ViewModel() {
var i: Int = i
var s: String = s
init {
Log.d("hankTag", "執(zhí)行ViewModel必要的初始化")
}
override fun onCleared() {
super.onCleared()
Log.d("hankTag", "執(zhí)行ViewModel 清理資源")
}
fun doAction() {
Log.d("hankTag", "執(zhí)行ViewModel doAction: i = $i, s : $s")
}
}
// ViewModelFactory
class UserModelFactory(val i: Int, val s: String) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return modelClass.getConstructor(Int::class.java, String::class.java).newInstance(i, s)
}
}
// View層
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 獲取ViewModel對象
val userModel = ViewModelProvider(this, UserModelFactory(1, "s")).get(UserModel::class.java)
// 使用ViewModel對象的函數
userModel.doAction()
}
}
運行結果:
06-02 11:20:53.196 32569-32569/com.zhou.viewmodeldemo D/hankTag: 執(zhí)行ViewModel必要的初始化
06-02 11:20:53.196 32569-32569/com.zhou.viewmodeldemo D/hankTag: 執(zhí)行ViewModel doAction: i = 1, s : s
06-02 11:20:57.836 32569-32569/com.zhou.viewmodeldemo D/hankTag: 執(zhí)行ViewModel 清理資源
核心原理
源碼探索的目標是逼裆,ViewModel是如何感知Activity的生命周期清理自身資源的。其實也就是看 onCleared函數是如何被調用的。
-
ViewModelProvider(this).get(UserModel::class.java)
上面這句代碼是是用來獲得ViewModel對象的。這里分為2個部分碎罚,其一,ViewModelProvider的構造函數:
上面標重點的注釋的意思是:創(chuàng)建一個ViewModelProvider,這將會創(chuàng)建ViewModel并且把他們保存到給定的owner所在的倉庫中。這個函數最終調用了重載的構造函數:
這個構造函數有兩個參數,一個store,是剛才通過owner拿到的,一個是,Factory。store顧名思義,是用來存儲ViewModel對象的,而Factory的意義,是為了通過class反射創(chuàng)建對象做準備的铣减。
使用構造函數創(chuàng)建出一個ViewModelProvider對象之后,再去get(UserModel::class.java)
通過一個class對象,拿到他的canonicalName全類名。然后調用重載get方法來獲取真實的ViewModel對象哄褒。
這個get函數有兩個參數,其一,key,字符串類型凉当。用于做標記,使用的是一個定死的字符串常量DEFAULT_KEY拼接上modelClass的全類名售葡,其二看杭,modelClass的class對象挟伙,內部代碼會使用class進行反射楼雹,最終創(chuàng)建出ViewModel對象。
上面提到了一個重點:Store倉庫尖阔,創(chuàng)建出來的ViewModel都會被存入owner所在的倉庫贮缅。那么,閱讀倉庫的源碼:
那么一個Activity介却,它作為ViewModelStoreOwner携悯,他自己的viewModelStore何時清理?
答案是:onDestroy() . 但是這里有一個特例,配置改變筷笨,比如屏幕旋轉時憔鬼,ViewModelStore并不會被清理。并且胃夏,Fragment的源碼中也有類似的調用:
總結
ViewModel的核心轴或,是自動清理資源。我們可以重寫onCleared函數仰禀,這個函數將會被ViewModel所在的Activity/Fragment 執(zhí)行onDestory的時候被調用照雁,但是當屏幕旋轉的時候,并不會清理。在ViewModel的架構中饺蚊,有幾個關鍵類萍诱,
ViewModelProvider 用于獲取ViewModel
ViewModelStore 用于存儲ViewModel
ViewModelStoreOwner 用于提供ViewModelStore對象,Activity和Fragment都是ViewModelStoreOwner 的實現
ViewModelProvider的內部類Factory污呼,用于支持ViewModel的有參構造函數裕坊,畢竟ViewModel對象是通過class反射創(chuàng)建出來的,需要支持默認無參燕酷,以及手動定義有參構造函數
DataBinding
DataBinding籍凝,單詞意思: 數據綁定,用于降低布局和邏輯的耦合性苗缩,使代碼邏輯更加清晰饵蒂。MVVM 相對于 MVP,其實就是將 Presenter 層替換成了 ViewModel層酱讶。DataBinding 能夠省去我們一直以來的findViewById() 步驟退盯,大量減少Activity 內的代碼,數據能夠單向或雙向綁定到 layout 文件中泻肯,有助于防止內存泄漏渊迁,而且能自動進行空檢測以避免空指針異常.
DataBinding:
- 支持在java代碼中不用findViewById來獲取控件,而直接通過DataBinding對象的引用即可拿到所有的控件id
- 進行數據綁定软免,使得ViewModel(數據)變化時宫纬,View控件的屬性隨之改變
- 支持 數據的雙向綁定,改變View控件的屬性膏萧,那該屬性綁定的ViewModel(數據)隨之改變
- 支持將任何類型的ViewModel綁定到View控件上漓骚,包括系統(tǒng)提供的類型(包括基礎類型和集合類型),以及自定義的類型
- 支持特殊的View屬性榛泛,比如ImageView的圖片源蝌蹂,可以自定義圖片加載的具體過程(Glide...)
- 支持在xml中寫簡單的表達式, 比如函數調用,三元表達式...
- 支持對res資源文件的引用曹锨,比如 dimen孤个,string...
- 支持與 LiveData(下文會解釋概念)合作,讓數據的變動 關聯 Activity / Fragment 的 生命周期
從ViewModel 的注釋中我們得知沛简,DataBinding是向View層暴露ViewModel的一種方式齐鲤。但是事實上并非如此,DataBinding只是數據綁定椒楣,它和ViewModel抽象類沒有半毛錢關系给郊。DataBinding綁定的雙方:是 數據(別多想,就是純粹的數據捧灰,不涉及到生命周期
) 和 視圖淆九。而MVVM的核心是ViewModel抽象類,核心功能是感知持有者Activity/Fragment的生命周期來釋放資源,防止泄露炭庙。我們使用DataBinding饲窿,創(chuàng)建封裝數據類型,也不用繼承ViewModel抽象類焕蹄。至于ViewModel抽象類的注釋上為什么這么說逾雄,我也是很費解。但是看了許多DataBinding的資料擦盾,項目嘲驾,包括在自己的項目中使用DataBinding之后淌哟,它給我的感受就是:很糟糕迹卢。沒錯,糟透了徒仓,也許是因為時代進步了腐碱,也許是因為我的代碼潔癖,DataBinding放入我的代碼掉弛,我總感覺有一種黏乎乎的感覺症见,就和最早的JSP一樣,一個HTML文件中殃饿,混入了HTML標簽谋作,js代碼,以及 java代碼乎芳,盡管我承認DataBinding的功能很強大遵蚜,但是使用起來確實不舒服。有一些老代碼如果大量使用了這種寫法奈惑,我們了解一些DataBinding核心原理也是有必要的吭净。
核心功能
DataBinding的核心功能是:支持View和數據的單向或者雙向綁定關系,并且最新版源碼支持 setLifecycleOwner 設置生命周期持有者肴甸。
基本用法
在所在module的build.gradle文件中寂殉,找到androd節(jié)點:插入以下代碼來開啟DataBinding
dataBinding{
enabled true
}
改造布局xml,使用<layout></layout>標簽包裹原來的布局原在,并且插入<data>節(jié)點
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<import type="androidx.databinding.ObservableMap" />
<import type="androidx.databinding.ObservableList" />
<variable
name="userBean"
type="com.zhou.databinding.UserBean" />
<variable
name="map"
type="ObservableMap<String, Object>" />
<!-- 首先在這里定義友扰,然后才能在代碼中使用 -->
<!-- 定義頂層字段 -->
<variable
name="title"
type="java.lang.String" />
</data>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<!-- 雙向綁定 -->
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={title}"
android:textSize="30sp"
tools:text="title:" />
<!-- 單向綁定 -->
<TextView
android:id="@+id/tvTitle2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{title}"
android:textSize="30sp"
tools:text="title:" />
<...></...>
</LinearLayout>
</layout>
這里支持import操作,類似java的import導包庶柿,導入之后就能在@{} 中使用引入之后的函數和類. 如果想雙向綁定村怪,就使用@={}。Varilable標簽是用來定義數據的澳泵,name隨意实愚,字符串即可。type必須是封裝類型的全類名,支持泛型實例。
Java/kotlin 代碼層面:
數據的綁定支持幾乎所有類型腊敲,包括jdk击喂,sdk提供的類,或者可以自定義類:
class UserModel {
val user = User("hank001", "man")
}
對碰辅,這里命名為UserModel懂昂,但是它和androidX里面的抽象類ViewModel沒有半毛錢關系。
在Activity中没宾,需要使用DataBindingUtil將當前activity與布局文件綁定凌彬。
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.lifecycleOwner = this
binding.title = "asfdsaf"
val map = ObservableArrayMap<String, Any>().apply { put("count", 0) }
binding.map = map
binding.userBean = UserBean()
Thread {
for (i in 0..100) {
binding.title = "asfdsaf$i" // 數據變更時,UI會發(fā)生變更
map["count"] = "count$i"
Thread.sleep(10)
}
}.start()
}
}
上面的代碼循衰,如果運行起來铲敛,
可以看到我并未主動去使用textview的引用去操控它的text屬性。這些工作都是在databinding框架中完成的会钝。至于更具體更復雜的用法伐蒋,本文不再贅述。網上很多騷操作迁酸。
核心原理
核心功能是 數據綁定先鱼,也就是說,只要知道了databinding是如何在數據變化時奸鬓,通知到view讓它改變屬性的焙畔,databinding的秘密就算揭開。直接從代碼進入源碼串远。這一切的源頭宏多,都是由于我們使用了DataBindingUtil來進行綁定引起的。那么就從它開始抑淫。
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.title = "asfdsaf"
注釋的大意是:將Activity的內容View設置給 指定layout布局绷落,并且返回一個關聯之后的binding對象。指定的layout資源文件不能是merge布局始苇。
隨后該函數調用到了:
這里首先使用 activity.setContentView,將layoutId設置進去砌烁,常規(guī)操作。然后催式,拿到activity的decorView函喉,進而拿到contentView,隨后調用bindToAddViews荣月。
繼續(xù)追蹤bind函數:
目標轉移到了sMapper.getDataBinder()管呵,進去看了之后發(fā)現是抽象類,找到他的實現類:
結果發(fā)現了我自己的包名哺窄,看到這里應該有些明白了捐下,我并沒有寫這個DataBindingMapperImpl類账锹,它只能是as幫我們自動生成的。
所以坷襟,綁定View和數據的具體代碼應該在這個類里面有答案奸柬,經過追蹤,發(fā)現代碼走到了這一行:
回到一開始婴程,
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
這句話廓奕,饒了一大圈,最終得到了一個ActivityMainBindingImpl對象档叔,隨后我們用這個對象去操作view引用來綁定數據
binding.title = "asfdsaf"
那就直接從這個title看起桌粉,上面是kotlin的setTitle寫法,直接看setTitle方法:
其實它就是把xml布局文件中的 title屬性值設置為 傳入的形參值衙四。然后 notifyPropertyChanged(BR.title): 通知 ID為 BR.title的屬性值發(fā)生了改變铃肯。
從上面的命名可以看出谦屑,DataBinding框架應該是給每一個xml中定義的變量variable都建立了一個獨立的監(jiān)聽器鲫寄,在variable發(fā)生變化時碟嘴,這個監(jiān)聽器會在 variable 發(fā)生改變時霎匈,通知界面元素發(fā)生屬性變更悬襟。,查找這個監(jiān)聽器的調用位置 executeBinding()函數惧互,結果有了意外發(fā)現碱呼,“雙向數據綁定”的原理也被揭開忽你。
這里傳了3個參數仪或,BeforeTextChanged确镊,OnTextChanged,AfterTextChanged范删,剛好對應了TextWatcher接口中的3個方法蕾域。進入看一眼上面的setTextWatcher():
在 textView的內容發(fā)生變更的時候,也會執(zhí)行到監(jiān)聽器的onChange函數到旦,進行數據變更旨巷。
上面的追蹤僅僅是針對
<variable
name="title"
type="java.lang.String" />
這一種定義databinding變量的方式。如果是map類型添忘,map的內部元素發(fā)生變更采呐,UI也是可以隨之更新的,又是怎么回事呢搁骑?
<variable
name="map"
type="ObservableMap<String, Object>" />
經過一番追蹤斧吐,發(fā)現有點不同又固。map的用法有點不同:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(map.count)}"
android:textSize="30sp"
tools:text="userBean:" />
當我在activity中變更map的元素值的時候,
會執(zhí)行到setMap方法:
當map的某一個元素值發(fā)生變化時煤率,會執(zhí)行到handlerFieldChange口予,
隨后 onFieldChange函數,
如果確認發(fā)生變更涕侈,就會requestRebind()重新去綁定最新的map對象沪停。
補充說明一點:
binding.lifecycleOwner = this
這句代碼,
如果一個DataBinding對象的mLifeCycleOwner不是空裳涛,那么:
在綁定數據的時候木张,就會去判定當前mLifeCycleOwner是不是STARTED之后,如果不是端三,數據的綁定都不會執(zhí)行舷礼。
總結
綜合以上所有小結論,總結一下:
- 在layout中定義的每一個被使用的variable都會建立獨立的監(jiān)聽器郊闯,沒有被用到的妻献,并不會有監(jiān)聽
- 數據的單項綁定:數據變化,通知View設置屬性团赁,是通過觀察者模式設置監(jiān)聽器來實現育拨,具體的監(jiān)聽方式和variable的類型有關
- 數據的雙向綁定:其實是通過 textWatch接口來實現,當view的text屬性發(fā)生改變欢摄,會通知到該view已經綁定的監(jiān)聽器更新數據
- DataBinding提供了很多可觀察的類型, 如 ObservableMap熬丧,ObservableList等,如果是集合類型怀挠,必須用這個析蝴,才能實現內部元素的變動對接UI的變動。自定義類型可以繼承BaseObservable并且重寫get set方法來實現同樣的綁定效果(具體如何做绿淋,這里不贅述)闷畸。
- DataBinding的mLifeCycleOwner屬性可以讓數據綁定感應Activity/Fragment的生命周期,防止內存泄漏.
LiveData
時代發(fā)展了吞滞,DataBinding的替代品來的很快佑菩。隨著Kotlin的興起,DataBinding可以減少findViewById的優(yōu)勢不復存在冯吓,而LiveData的發(fā)布倘待,又為MVVM提供了一種新的可能。
LiveData, 意為: 存活的數據组贺。我們還是從權威的官方注釋開始:
大意翻譯為:
LiveData是數據的持有者類凸舵,它可以被給定的"lifeCycleOwner"來觀察。
這意味著失尖,一個"observer"可以連同一個 lifeCycleOwner 被添加到一個pair中, 并且啊奄,當且僅當lifeCycleOwner 處于存活狀態(tài)時渐苏,觀察者將會被通知到封裝數據的變動。當 lifeCycleOwner 的狀態(tài)時 STARTED或者RESUMED時菇夸,它會被認為時存活狀態(tài)琼富。 通過observerForever(observer)添加進去的觀察者,會被認為總是處于存活狀態(tài)庄新,這樣它就會永遠被通知數據的變動鞠眉。對于這樣的觀察者,我們應該手動調用 removeObserver(observer)
連同Lifecycle被添加進去的觀察者择诈,如果相關的lifecycle轉移到DESTORY狀態(tài)時械蹋,將會被自動移除。這對于Activity/Fragment將會十分有用羞芍,他們可以安全地觀察LiveData哗戈,并且不用擔心泄露。
另外荷科,LiveData擁有 onActive()和onInactive()方法來使得當存活的觀察者數量在0和1之間變化時被通知到唯咬。這將允許LiveData在它沒有任何存活觀察者的時候釋放重量級資源。
這個類被設計用來持有ViewModel的個別數據字段畏浆,但是它也可以被用來實現一個application中多個module之間的解耦的共享數據胆胰。
關鍵詞重點:
- LiveData 自身是被觀察者,創(chuàng)建它的時候可以指定observer以及lifeCycleOwner 全度,前者控制數據變化之后的處置煮剧,后者控制數據的變化是否需要通知
- lifeCycleOwner 也就是我們通常的Activity/Fragment,當他們處于STARTED和RESUMED狀態(tài)時将鸵,觀察者observer可以收到通知,如果是處于其他狀態(tài)佑颇,則不會收到通知顶掉。
- 當lifeCycleOwner 變?yōu)?strong>DESTORY狀態(tài)時,觀察者observer將會被自動移除挑胸。
- 一個LiveData可以擁有多個觀察者痒筒,它提供了onActive和onInactive的方法來綁定和釋放重量級資源,避免資源浪費
- LiveData是谷歌提供給我們的一種多個模塊之間不耦合的數據共享的方式
之所以選擇用LiveData來實現MVVM茬贵,是因為 LiveData的代碼足夠純粹簿透,純java/kotlin代碼,不再像databinding那樣黏乎乎解藻,并且 在ViewModel抽象類的官方注釋上老充,也推薦了LiveData的標準寫法,寫法足夠簡單實用螟左。
核心功能
LiveData啡浊,其實他本身就是數據觅够,一個可以被觀察的活著的數據。它的唯一職責巷嚣,就是提供觀察接口給View喘先,讓View可以感知它的變化。
基本用法
LiveData作為給View層暴露ViewModel的一種方式廷粒,它一般要配合ViewModel來組成MVVM架構窘拯。
但是它也可以單獨使用,下面的案例用純粹的方式(不涉及到MVVM)來展示LiveData的用法:
// javabean
class User(var name: String, var sex: String) {
override fun toString(): String {
return "[name:$name,sex:$sex]"
}
}
// Livedata的管理類坝茎,雖然名字也叫xxModel树枫,但是和ViewModel沒有任何關系
class UserModel {
val userLiveData = MutableLiveData<User>() // LiveData除了可以發(fā)送數據之外,還可以緩存數據(參見setValue getValue)
private var seri = 0
init {
userLiveData.postValue(User("hank$seri", "male"))
}
fun loadUser() {
userLiveData.postValue(User("hank${++seri}", "male"))//post一次之后便會緩存起來
}
}
// View層Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val user = UserModel()
// 讓界面元素通過觀察者來感知數據的變化
user.userLiveData.observe(this, Observer<User> {
Log.d("hanTag", "MainActivity:偵測到User發(fā)生變化$it")
textView.text = it.toString()
})
// 通過textView的點擊事件來主動觸發(fā)數據的變化
textView.setOnClickListener {
Log.d("hanTag", "MainActivity:主動觸發(fā)User的變化景东,可能是觸發(fā)網絡請求")
user.loadUser()
}
}
}
以上代碼僅作示例砂轻。
以上代碼有3個部分,一個javaBean,一個定義LiveData的管理類UserModel(雖然名字是xxModel斤吐,但是和ViewModel沒有任何關系)搔涝,還有一個是View層Activity.
運行效果:
核心原理
LiveData的核心功能,是把數據的變化通知給觀察者和措,也就是這行代碼
val userLiveData = MutableLiveData<User>()
userLiveData.postValue()// 入口代碼
那么進入源碼:
它實際上調用的是父類的postValue()
這段注釋的意思大概是:
發(fā)送一個任務給主線程去設置給定的value庄呈。如果你在主線程中執(zhí)行了如下代碼:
liveData.postValue("a")
liveData.setValue("b")
這個value值 b 將會被首先設置,并且隨后主線程將會用a覆蓋它派阱。
如果你在主線程執(zhí)行post任務之前多次調用這個方法诬留,那么只有最后一個value才會被分發(fā)。
接著往下看贫母,方法內容:
這里進行了一系列判定文兑,規(guī)避了無需執(zhí)行任務的情況.
ArchTaskExecutor是一個單例類,用來在主線程中執(zhí)行任務腺劣,細節(jié)無需關心绿贞。
來看看 mPostValueRunnable
這個runnable,其實就做了兩件事橘原,1籍铁,傳遞剛剛更新的mPendingData給newValue,然后還原mPendingData趾断。2拒名,將newValue值繼續(xù)往下傳輸。
看setValue做了什么
發(fā)現了重點芋酌,這個函數的注釋上說明增显,如果存在存活狀態(tài)的觀察者,將會把這個value值分發(fā)給他們隔嫡。
繼續(xù)觀察considerNotify()方法:
可以看到甸怕,一個observer本身就有是否存活的狀態(tài)值 mActive. 如果判定存活甘穿,就繼續(xù)往下走程序,
這里我得到幾個關鍵信息:
觀察者本身就有mActive屬性
為了防止觀察者的mActive狀態(tài)在通過了第一層判定之后突然改變(因為觀察者的狀態(tài)不可預知)梢杭,又增加了第二層判定温兼,如果這次判定發(fā)現觀察者不再存活,也不通知
觀察者存在一個mLastVersion屬性武契,影響是否會被通知到(這個有用募判,下一節(jié)LiveDataBus會講),并且在正式通知觀察者之后咒唆,mLastVersion會更新
-
最終執(zhí)行的是觀察者的onChange方法届垫,也就是我們在示例代碼中所寫的
Observer<User>
user.userLiveData.observe(this, Observer<User> { Log.d("hanTag", "MainActivity:偵測到User發(fā)生變化$it") textView.text = it.toString() })
接下來的重點就轉移到了ObserverWrapper這個類,它是 LiveData的內部抽象類:
這個類,在我們去注冊觀察者的時候其實就用到了:
那它作為一個觀察者全释,他自己的存活狀態(tài)mActive是由什么決定的呢装处?
是ObserverWrapper自己的 activeStateChanged(). 這個函數的調用有4處,但是經過Debug追蹤浸船,最終鎖定一處:
在LifecycleBoundObserver類的onStateChange函數中妄迁,這里已經說的很明白了,上面的代碼解讀一下:
- mOwner 是 Activity或者Fragment李命,他們都是LifeCycleOwner接口的實現者
- 判定觀察者是否存活的依據登淘,是 mOwner 是否處于至少START狀態(tài),但是排除DESTORYED狀態(tài)
- 如果mOwner的狀態(tài)被判定為大于START 小于 DESTROYED封字, 那么觀察者會被認定為存活,mActive被設置為true
OK.到此為止黔州,作結論:
總結
LiveData之所以可以僅在觀察者存活的時候通知到多個觀察者,是因為:
- 借助了LifeCycle阔籽,使用它來判定lifeCycleOwner的狀態(tài)值流妻,大于START小于DESTORY,則判定觀察者是mActive是true, 反之 false
- 當LiveData數據發(fā)生變化時仿耽,輪詢所有觀察者合冀,逐個判斷觀察者狀態(tài),發(fā)現mActive是true项贺,則通知,反之峭判,則不通知
LiveDataBus
LiveDataBus基于LiveData开缎, 是一種在application全局范圍內的事件總線的新方案,當然這個并不是谷歌直接給的,在此之前林螃,可能存在EventBus奕删,RxBus這種寫法,但是他們都有內存泄漏的問題疗认。而使用基于LiveData的LiveDataBus完残,將不會有類似的問題伏钠。
必須提到的是,原始的MutableLiveData存在多次通知的問題谨设,解決方式也不止一種熟掂,后文再細講。
核心功能
負責Activty/Fragment之間扎拣,多個Activity之間赴肚,以及Fragment與Activity之間的數據通信。
基本用法
由于它不是谷歌提供的二蓝,是開發(fā)者根據需求創(chuàng)造的誉券,所以這里我給出參考源碼,實際開發(fā)中每個人的寫法都可能不同:
package com.zhou.baselib.bus
import android.util.Log
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import java.util.concurrent.atomic.AtomicBoolean
class LiveDataBus {
/**
* 只有當前存活的owner才能收到消息, 即刊愚,尚未創(chuàng)建成功的owner將不會收到消息 踊跟,
* 即使它進行了監(jiān)聽
*/
private val mapOfAliveOwner: HashMap<String, AliveOwnerMutableLiveData<Any?>> = HashMap()
/**
* 全部owner都能收到消息,就算將要跳轉的Activity并沒創(chuàng)建成功的情況
*/
private val mapOfAllOwner: HashMap<String, MutableLiveData<Any?>> = HashMap()
/**
*
*/
private val mapOfSingleEventOwner: HashMap<String, SingleLiveData<Any?>> = HashMap()
// 內部類的單例寫法鸥诽,靜態(tài)成員天生就是線程安全的
class SingleHolder {
companion object {
val DATA_BUS = LiveDataBus()
}
}
companion object {
// 提供給外界一個get方法來獲取單例對象
fun get(): LiveDataBus {
return SingleHolder.DATA_BUS
}
}
/**
* 獲取消息通道, 僅支持當前存活的 lifeCycleOwner
*
* @key 消息通道的key
*/
fun getAliveOwnerChannel(key: String): MutableLiveData<Any?> {
if (!mapOfAliveOwner.containsKey(key)) {
mapOfAliveOwner[key] = AliveOwnerMutableLiveData()
}
return mapOfAliveOwner[key]!!
}
/**
* 獲取默認消息通道, 支持所有l(wèi)ifeCycleOwner商玫,包括目前還沒創(chuàng)建成功的
*/
fun getDefaultChannel(key: String): MutableLiveData<Any?> {
if (!mapOfAllOwner.containsKey(key)) {
mapOfAllOwner[key] = MutableLiveData()
}
return mapOfAllOwner[key]!!
}
fun getSingleEventChannel(key: String): SingleLiveData<Any?> {
if (!mapOfSingleEventOwner.containsKey(key)) {
mapOfSingleEventOwner[key] = SingleLiveData()
}
return mapOfSingleEventOwner[key]!!
}
}
/**
* 只有當前存活的lifeCycleOwner才會收到 消息, 重寫它的observer的mLastVersion
*/
class AliveOwnerMutableLiveData<T> : MutableLiveData<T>() {
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner, observer)
hook(observer)
}
private fun hook(observer: Observer<in T>) {
val classLiveData: Class<LiveData<*>> = LiveData::class.java
val fieldObservers = classLiveData.getDeclaredField("mObservers")
fieldObservers.isAccessible = true
val mObservers = fieldObservers[this]
val classObservers: Class<*> = mObservers.javaClass
val methodGet = classObservers.getDeclaredMethod("get", Any::class.java)
methodGet.isAccessible = true
val objectWrapperEntry = methodGet.invoke(mObservers, observer)
val objectWrapper =
(objectWrapperEntry as Map.Entry<*, *>).value!!
val classObserverWrapper: Class<*>? = objectWrapper.javaClass.superclass
val fieldLastVersion =
classObserverWrapper!!.getDeclaredField("mLastVersion")
fieldLastVersion.isAccessible = true
val fieldVersion = classLiveData.getDeclaredField("mVersion")
fieldVersion.isAccessible = true
val objectVersion = fieldVersion[this]
fieldLastVersion[objectWrapper] = objectVersion
}
}
/**
* 如果希望創(chuàng)建一個消息通道,只允許通知一次衙传,那就使用SingleLiveEvent
*
* @param <T> the data type
*/
class SingleLiveData<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(true)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.d(
"SingleLiveEvent",
"Multiple observers registered but only one will be notified of changes."
)
}
// Observe the internal MutableLiveData
super.observe(owner, Observer { t ->
if (mPending.compareAndSet(true, false)) {// 獲取bool值决帖,并且在獲取之后將它的值設置成false
observer.onChanged(t)
}
})
}
}
以Activity之間的通信為例:
-
使用情形1,Activity之間的跳轉蓖捶,從MainActivity跳轉到Main2Activity地回,要進行通信
// 第一個Activity class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) btn.setOnClickListener { startActivity(Intent(this, Main2Activity::class.java)) // 獲取通道,發(fā)送消息俊鱼,通道的key是MainActivity LiveDataBus.get().getDefaultChannel("MainActivity2").postValue("發(fā)送給第二個Activity的消息") } } } // 第二個Activity class Main2Activity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main2) // 監(jiān)聽事件通道 LiveDataBus.get().getDefaultChannel("Main2Activity").observe(this, Observer { Log.d("MainActivityTag", "$it") }) } }
運行程序刻像,從MainActivity跳轉到Main2Activity時,會看到日志:
Main2Activity收到消息: 發(fā)送給第二個Activity的消息
消息發(fā)送成功并闲,這里的postValue()的參數類型细睡,可以是任意對象,或者基礎類型帝火。
-
使用情形2:以上的情況是從一個Activity溜徙,跳轉到另一個Activity2,其實在發(fā)送消息之時犀填,目標Activity2并沒有創(chuàng)建成功蠢壹,它也能在Activity2創(chuàng)建成功之后收到消息(下一節(jié)分析原理 )。但是也存在另一種情況九巡,如果我們只希望目標Activity/Fragment存在的時候图贸,才收到消息(而不是等他創(chuàng)建成功)。可以這么用疏日。
// 第一個Activity class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) LiveDataBus.get().getAliveOwnerChannel("MainActivity").observe(this, Observer { Log.d("MainActivityTag", "MainActivity 收到消息: $it") }) btn.setOnClickListener { startActivity(Intent(this, Main2Activity::class.java)) LiveDataBus.get().getAliveOwnerChannel("Main2Activity").postValue("發(fā)送給第二個Activity的消息") } } } // 第二個Activity class Main2Activity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main2) LiveDataBus.get().getAliveOwnerChannel("Main2Activity").observe(this, Observer { Log.d("MainActivityTag", "Main2Activity收到消息: $it") }) btn.setOnClickListener { finish() LiveDataBus.get().getAliveOwnerChannel("MainActivity").postValue("回饋給Activity1的消息") } } }
當從MainActivty點按鈕跳到Main2Activity時偿洁,Main2Activity并收不到消息。
這是因為沟优,消息發(fā)送時涕滋,Main2Activity對象并不存在(如果Main2Activity已經處于棧內,并且它是棧內唯一的啟動模式 SingleInstance净神,也能收到何吝,親測 )。
而從Main2Activity點按鈕跳回MainActivity時鹃唯, MainActivity能收到消息爱榕。
Fragment的情形也是一樣,篇幅有限坡慌,這里就不做示例了...
-
使用情形3
有的時候黔酥,當數據變化時,只需要通知一次View層即可洪橘。此時單獨使用
SingleLiveData
(參照LiveData基本用法)跪者,也可以使用LiveDataBus的getSingleEventChannel("xxx")取得 單次消息通道即可。//注冊監(jiān)聽 LiveDataBus.get().getSingleEventChannel("NoticeTipsFragment").observe(this, Observer { tvMsg.text = it.toString() }) // 發(fā)送消息 var seriCode = 1 tvMsg.setOnClickListener {//點擊事件 LiveDataBus.get().getSingleEventChannel("NoticeTipsFragment").postValue("哈哈哈${seriCode++}") }
就算點擊多次按鈕熄求,發(fā)送了多次事件渣玲,監(jiān)聽器也只會收到一次通知。
核心原理
使用上述LiveDataBus類弟晚,只需要做兩件事忘衍,
其一:獲得消息通道 LiveDataBus.get().getXXXChannel("xxxx") 然后 observer(this,Observer{...})進行監(jiān)聽注冊攒暇。
其二:獲得消息通道 LiveDataBus.get().getXXXChannel("xxxx") ** 然后 postValue(xxx) 發(fā)送消息
上面的LiveDataBus類提供了3種消息通道踊赠,
一個是不支持粘性消息的getAliveOwnerChannel,內部使用的是hook之后的自定義AliveOwnerMutableLiveData對象來當作消息通道.
一個是支持粘性消息的getDefaultChannel狡门,使用的是純天然的MutableLiveData瑟押。
還有一種是支持單次消息通知的 SingleEventLiveData搀捷, 也是重寫了observer方法, 使用AtomicBoolean 的特性(compareAndSet(true,false)獲取當前值多望,并且使用之后更改為false)完成單次通知嫩舟。
LiveDataBus本身的設計,就是基于 訂閱怀偷,發(fā)布者模式至壤,和Rxjava如出一轍,但是由于利用到了LiveData,LifeCycle枢纠,它純天然就帶有生命周期感知機制,無需我們操心去做更多防止內存泄漏的工作。但是晋渺,LiveDataBus與RxJava有一個共同之處镰绎,那就是支持粘性消息,即使 訂閱者對象尚未創(chuàng)建木西,待到創(chuàng)建成功之后畴栖,也能收到。
進入源碼, 本次探索源碼八千,分析的目標是:
為何使用默認的MutableLiveData時吗讶,它支持粘性消息呢?
從發(fā)布者入手:
LiveDataBus.get().getDefaultChannel("Main2Activity").postValue("發(fā)送給第二個Activity的消息")
postValue之后恋捆,追蹤到LiveData類的mPostValueRunnable...
繼續(xù)往下照皆,進入到setValue:
observer.mActive值是由 Activity/Fragment的狀態(tài)決定的(前面LiveData的核心原理已經說明),
如果我想阻止消息的通知沸停,想在這里執(zhí)行return不現實膜毁。所以唯一的解決方案就鎖定在 下圖中的observer.mLastVersion >= mVersion 的判斷上。只要我們能夠讓判斷成立愤钾,那么這里就會return瘟滨。onChange就不會執(zhí)行,消息也就不會具備粘性能颁。
一向嚴謹的谷歌工程師居然對這里的 mVersion變量加注釋杂瘸,但是根據代碼邏輯可以判定:
只有在消息的版本mVersion 大于 訂閱者的版本mLastVersion 時,才執(zhí)行消息 通知.
這個也好理解伙菊,畢竟不能讓訂閱者收到過時的消息败玉。
LiveData類的mVersion屬性,唯一一處和當前情形有關的變動占业,就是:
而它的初始值是 START_VERSION -1
observer.mLastVersion 的初始值也是 START_VERSION -1
那么就很有意思了绒怨。 LiveData的mVersion, 可以理解為消息通道的版本號。而 observer.mLastVersion可以理解為 消息訂閱者的版本號谦疾。前者南蹂,會隨著每一次的postValue->調用到 setValue,從而 mVersion++念恍, 那后者呢六剥?
在消息確認要發(fā)送之后,把消息的版本號賦值給 訂閱者的版本號峰伙,防止重復發(fā)送疗疟。
OK,問題基本確定了瞳氓。先總結一波:
LiveDataBus基于 發(fā)布/訂閱者 模型策彤,發(fā)布者是 MutableLiveData,訂閱者是Observer,兩者都存在一個版本號店诗,并且初始值都是-1裹刮。
當發(fā)布者發(fā)布消息的時候,它自身的版本號+1庞瘸。
此時捧弃,判定 發(fā)布者的版本號是不是大于訂閱者版本號,如果大于擦囊,那么消息就是有效消息违霞,進行通知。否則瞬场,就認定為過時消息买鸽,不通知訂閱者。
所以泌类,要解決粘性消息癞谒,就只需要針對訂閱者的版本號進行hook。hook的思路是:
在訂閱者Activity獲取了消息通道刃榨,然后注冊監(jiān)聽的時候弹砚,把訂閱者Observer自身的版本號設置得和 消息發(fā)布者的版本號一樣,讓 if(observer.mLastVersion>=mVersion){return;} 語句滿足判定枢希,執(zhí)行return.
于是就看到了本節(jié)開頭LiveDataBus代碼中的桌吃,hook方法, 在注冊監(jiān)聽的時候,把LiveData消息通道的mVersion賦值給observer的mLastVersion:
class AliveOwnerMutableLiveData<T> : MutableLiveData<T>() {
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner, observer)
hook(observer)
}
private fun hook(observer: Observer<in T>) {
// 獲得 當前 ObserverWrapper
val classLiveData: Class<LiveData<*>> = LiveData::class.java
val fieldObservers = classLiveData.getDeclaredField("mObservers")
fieldObservers.isAccessible = true
val mObservers = fieldObservers[this]
val classObservers: Class<*> = mObservers.javaClass // 拿到了 map 對象
//
val methodGet = classObservers.getDeclaredMethod("get", Any::class.java)
methodGet.isAccessible = true
val objectWrapperEntry = methodGet.invoke(mObservers, observer)
val objectWrapper =
(objectWrapperEntry as Map.Entry<*, *>).value!!// 拿到ObserverWrapper
val classObserverWrapper: Class<*>? = objectWrapper.javaClass.superclass //
val fieldLastVersion =
classObserverWrapper!!.getDeclaredField("mLastVersion")// 取得ObserverWrapper 的 mLastVersion屬性
fieldLastVersion.isAccessible = true
val fieldVersion = classLiveData.getDeclaredField("mVersion")// 拿到 LiveData的mVersion屬性
fieldVersion.isAccessible = true
val objectVersion = fieldVersion[this]
fieldLastVersion[objectWrapper] = objectVersion
}
}
而由于有時候粘性消息也同樣有作用苞轿,比如Activity跳轉的數據傳遞茅诱。所以,LiveDataBus中我保留了原有的MutableLiveData通道m(xù)ap. 要不要使用粘性消息搬卒,視情況而定瑟俭。
總結
LiveDataBus作為RxJava或者EventBus的替代品,在防止內存泄漏方面確實給開發(fā)者省了不少心契邀,而且 View層內部的數據通信摆寄,也完全可以用LiveDataBus來替代傳統(tǒng)的 全局變量方式,接口方式坯门,Bundle封裝方式等微饥,寫法上更加簡潔一致,唯一需要管理的可能就是 消息通道的key值古戴,如果用一個統(tǒng)一的類來管理欠橘,會更加嚴謹。
案例實操
Demo地址:https://github.com/18598925736/MvvmStandartDemo
上述有關MVVM的幾個重要組件现恼,在MVVM模式開發(fā)中會經常用到肃续。但是個人不會去采用DataBinding黍檩,讀者如果有需求可以自己查閱其他資料。
一般框架的開發(fā)設計思路都是痹升,
-
完成核心功能
MVVM開發(fā)框架的核心功能建炫,就是分離 M模型層,V視圖層 和 VM數據模型層
-
接口/基類抽取
對于MVVM三個層級疼蛾,分別定義統(tǒng)一的抽象類或者接口,來約束該類的行為艺配。
-
解決Bug,提升兼容性和使用體驗
僅僅是做Demo察郁,發(fā)現不了隱藏的一些問題,必須在實踐中慢慢總結提升優(yōu)化转唉。
主要業(yè)務 : 一個簡單的登錄邏輯皮钠。從簡單的案例入手,更容易感受到框架的本質赠法。
完成核心功能
如上圖所示麦轰,Demo中,存在明顯的三層架構砖织,
-
數據層M
只負責數據的獲取
-
視圖層V
只負責界面UI的調度
-
數據模型層VM
負責聯絡M和V層款侵。VM層內的方法,由V層調用侧纯,方法內部新锈,會執(zhí)行M層的方法。
M-VM-M 三者之間的引用關系眶熬,現在是一條直線.V直接持有VM妹笆,VM直接持有M。
相比之前MVP娜氏,P和V層的對象之間實際上是存在互相引用的關系拳缠,只是用接口隔離了,為了不泄漏內存,還要在適當的時機斷開引用贸弥。
但是窟坐,現在,內存泄漏的問題不用操心了茂腥。
詳細代碼如下:
V層代碼, 略微使用接口約束:
interface LoginView {
fun getUserName(): String
fun getPassword(): String
fun showResult(res: String)
}
/**
* MVVM中View層依然是純粹的視圖層狸涌,不要涉及到任何業(yè)務邏輯,它只負責調用VM層的業(yè)務最岗,
* 與MVP相比帕胆,P必須持有V的引用,然后驅動UI的更新,最后還要寫釋放引用的代碼般渡。
*
* 但是MVVM中懒豹,可以直接用 觀察者回調來實現
*
*/
class LoginActivity : AppCompatActivity(), LoginView {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
// 獲取ViewModel
val userModel = ViewModelProvider(this).get(LoginActivityViewModel::class.java)
// 點擊事件觸發(fā)芙盘,出發(fā)VM的業(yè)務邏輯
btnLogin.setOnClickListener {
showLoading() // 開始請求數據顯示加載中
userModel.doLogin(getUserName(), getPassword())
}
// 定義數據回調邏輯
userModel.userLiveData.observe(this, Observer {
hideLoading() // 得到數據返回,隱藏加載中
showResult(it.toString())// 處理數據
})
}
//...省略N行
}
M層代碼:
/**
* 約束業(yè)務數據的接口
*/
interface UserModelInterface {
fun login(userName: String, password: String, callback: HttpCallback<UserBean>)
}
/**
* 純粹的M層, 只有數據獲取的代碼
*/
class UserModel : UserModelInterface {
override fun login(userName: String, password: String, callback: HttpCallback<UserBean>) {
HttpRequestManager.doLogin(userName, password, callback)
}
}
VM層的代碼:
/**
* 業(yè)務邏輯轉移到這里
*
* 原則上脸秽,一個界面(Activity或者Fragment)對應一個 UserModel
*
*/
class LoginActivityViewModel : ViewModel() {
val userLiveData: MutableLiveData<UserBean> by lazy {
MutableLiveData<UserBean>()
}
fun doLogin(userName: String, password: String) {
// 發(fā)送網絡請求儒老,并且執(zhí)行回調
UserModel().login(userName, password, object : HttpCallback<UserBean> {
override fun onSuccess(result: UserBean?) {
userLiveData.postValue(result)
}
override fun onFailure(e: Exception?) {
userLiveData.postValue(UserBean())// 失敗,則手動構建一個userBean
}
})
}
}
運行效果和MVP沒有區(qū)別:
接口/基類抽取
設計一個框架记餐,除了面向對象思想的封裝繼承和多態(tài)之外驮樊,還需要了解設計模式,注解技術片酝,泛型約束囚衔,反射原理,有可能還需要了解一些 數據結構來追求高效雕沿。
提取基類练湿,M和V層其實還是一樣,需要BaseModel和BaseView來約束基本行為审轮,就算是接口是空肥哎,也必須有,空接口可以作為類型標記疾渣。
BaseModel
interface BaseModel {
}
后續(xù)的Model層子接口以及實現類篡诽,都要以它為父類:
BaseView
// View層基類,
interface BaseView {
fun showLoading()// 展示加載中
fun hideLoading()// 隱藏加載中
}
所有View層類都是它的子類:
ViewModel不用提取
至于VM層稳衬,個人習慣霞捡,一般不用接口約束,因為它不再像MVP的P層一樣持有View的引用薄疚。提取不提取都問題不大碧信。
BaseActivity/BaseFragment
MVP中,一個V街夭,對應一個P砰碴,同樣,MVVM中板丽,一個V對應一個VM呈枉,所以,BaseActivity中埃碱,VM可以用泛型來進行約束猖辫。 抽離VM泛型之后,BaseActivity如下:
open abstract class BaseActivity<T : ViewModel> : AppCompatActivity() {
/**
* 布局ID
*/
abstract fun getLayoutId(): Int
private fun getViewModelClz(): Class<T> {
return analysisClassInfo(this)
}
fun getViewModel(): T {
return ViewModelProvider(this).get(getViewModelClz())
}
/**
* 獲取對象的參數化類型,并且把第一個參數化類型的class對象返回出去
*
* @param obj
* @return
*/
private fun analysisClassInfo(obj: Any): Class<T> {
val type = obj.javaClass.genericSuperclass //
val param = (type as ParameterizedType?)!!.actualTypeArguments // 獲取參數化類型
return param[0] as Class<T>
}
/**
* 界面元素初始化
*/
abstract fun init()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(getLayoutId())
init()
}
}
這里有個難點:
由于通過ViewModelProvider需要傳入具體的ViewModel的Class對象砚殿,所以要從BaseActivity的泛型參數中將泛型的具體類型解析出來啃憎,也就是這里的 analysisClassInfo()方法 . 具體的,這里不是重點似炎,詳情就略過了辛萍。
在使用了這個BaseActivity之后悯姊,LoginActivity就變成了如下模樣:
BaseFragment也是類似:
open abstract class BaseFragment<T : ViewModel> : Fragment() {
/**
* 布局ID
*/
abstract fun getLayoutId(): Int
private fun getViewModelClz(): Class<T> {
return analysisClassInfo(this)
}
fun getViewModel(): T {
return ViewModelProvider(this).get(getViewModelClz())
}
/**
* 獲取對象的參數化類型,并且把第一個參數化類型的class對象返回出去
*
* @param obj
* @return
*/
private fun analysisClassInfo(obj: Any): Class<T> {
val type = obj.javaClass.genericSuperclass //
val param = (type as ParameterizedType?)!!.actualTypeArguments // 獲取參數化類型
return param[0] as Class<T>
}
/**
* 界面元素初始化
*/
abstract fun init()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(getLayoutId(), container, false)
}
/**
* onViewCreated之后,才能用kotlin的viewId去操作view
*/
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
init()
}
}
使用BaseFragment也是一樣:
基類統(tǒng)一管理
將基類移動到基礎lib模塊中贩毕,
解決Bug,提升兼容性和使用體驗
Demo只是Demo悯许,實際使用MVVM的過程中,還會遇到不可預知的問題辉阶。
LiveData成員屬性膨脹
一般我們認定先壕,一個Activity/Fragment對應一個ViewModel,但是一個ViewModel中可以認定多個數據源睛藻,也就是LiveData, 一旦業(yè)務代碼積累過多启上,就可能會出現一個ViewModel中N個liveData成員屬性 :
class LoginActivityViewModel : ViewModel() {
private val userLiveData: MutableLiveData<UserBean> by lazy { MutableLiveData<UserBean>() }
private val noticeLiveData: SingleLiveData<String> by lazy { SingleLiveData<String>() }
// 業(yè)務積累
private val noticeLiveData: SingleLiveData2<String> by lazy { SingleLiveData<String>() }
private val noticeLiveData2: SingleLiveData3<String> by lazy { SingleLiveData<String>() }
private val noticeLiveData3: SingleLiveData4<String> by lazy { SingleLiveData<String>() }
private val noticeLiveData4: SingleLiveData5<String> by lazy { SingleLiveData<String>() }
private val noticeLiveData5: SingleLiveData6<String> by lazy { SingleLiveData<String>() }
private val noticeLiveData6: SingleLiveData7<String> by lazy { SingleLiveData<String>() }
//.....
}
而且多個ViewModel還有可能公用同一個類型的 LiveData,比如有一個Fragment也在用 NoticeLiveData:
class NoticeFragmentViewModel : ViewModel() {
val noticeLiveData: SingleLiveData<String> by lazy { SingleLiveData<String>() }
}
那每個地方都去寫一遍店印,并不是最理想的寫法。
解決方案:提煉出LiveDataHolder類倒慧,讓每一個ViewModel子類都擁有一個自己的LiveDataHolder對象按摘,從holder中去獲取LiveData, 去執(zhí)行數據的發(fā)送和監(jiān)聽.
import androidx.lifecycle.MutableLiveData
// 三種LiveData的類別
enum class EventType {
SINGLE,// 單次通知
ALIVE,// 只通知存活的lifeCycleOwner
DEFAULT// 默認,粘性消息
}
/**
* 這個函數是為了簡化一個ViewModel中存在N個LiveData定義的情況纫谅,其實只需要一個類炫贤,去獲取就行了
*
* 每個ViewModel應該有自己的LiveDataHolder,應該是每一個ViewModel都有一個LiveDataHolder, 用來獲取LiveData
*/
class LiveDataHolder {
companion object {
fun get(): LiveDataHolder {
return LiveDataHolder()
}
}
private val map: HashMap<Class<out Any?>, MutableList<MutableLiveData<out Any?>>> = HashMap()
fun <T> getLiveData(key: Class<T>): MutableLiveData<T> {
return getLiveData(key, EventType.DEFAULT)
}
/**
*
* 獲得一個指定的LiveData
*
* @param key 指定返回值類型
* @param eventType 消息通道的類別
* @see EventType
*/
fun <T> getLiveData(key: Class<T>, eventType: EventType): MutableLiveData<T> {
val liveDataClz: Class<MutableLiveData<T>> = when (eventType) {
EventType.SINGLE -> {
SingleLiveData<T>().javaClass
}
EventType.ALIVE -> {
AliveOwnerMutableLiveData<T>().javaClass
}
else -> {
MutableLiveData<T>().javaClass
}
}
if (map[key] == null) {
map[key] = ArrayList()
}
val currentList = map[key]!!
for (a in currentList) {
if (liveDataClz.isInstance(a)) {
return a as MutableLiveData<T>
}
}
val newLiveData = liveDataClz.getConstructor().newInstance()
currentList.add(newLiveData)
return newLiveData
}
}
為了防止在每一個ViewModel子類中都去定義一次LiveDataHolder付秕,我把它提煉到 ViewModel的基類中去:
// ViewModel基類
abstract class BaseViewModel : ViewModel() {
// 一個ViewModel可以存在多個LiveData兰珍,所以使用LiveDataHolder管理所有的LiveData
val liveDataHolder = LiveDataHolder.get()
}
經過此次優(yōu)化,所有ViewModel子類中不再需要去存放LiveData成員屬性询吴,要使用的時候直接如下這樣寫
class LoginActivityViewModel : BaseViewModel() {
/**
* 觸發(fā)業(yè)務
*/
fun getMsg() {
val noticeLiveData =
liveDataHolder.getLiveData(String::class.java)
noticeLiveData.postValue(NoticeModel().getNotice())
}
/**
* 監(jiān)聽業(yè)務
*
* 為了不向外界暴露LiveData成員,提供一個注冊監(jiān)聽的函數
*/
fun observerGetMsg(lifecycleOwner: LifecycleOwner, observer: Observer<String>) {
val noticeLiveData =
liveDataHolder.getLiveData(String::class.java)
noticeLiveData.observe(lifecycleOwner, observer)
}
/**
* 觸發(fā)登錄業(yè)務
*/
fun doLogin(userName: String, password: String) {
// 發(fā)送網絡請求掠河,并且執(zhí)行回調
UserModel().login(userName, password, object : HttpCallback<UserBean> {
override fun onSuccess(result: UserBean?) {
liveDataHolder.getLiveData(UserBean::class.java, EventType.ALIVE).postValue(result)
}
override fun onFailure(e: Exception?) {
liveDataHolder.getLiveData(UserBean::class.java, EventType.ALIVE)
.postValue(UserBean())
}
})
}
/**
* 監(jiān)聽登錄業(yè)務
*/
fun observerDoLogin(lifecycleOwner: LifecycleOwner, observer: Observer<UserBean>) {
liveDataHolder.getLiveData(UserBean::class.java, EventType.ALIVE)
.observe(lifecycleOwner, observer)
}
}
多個Fragment公用所在Activity的ViewModel
同一個Activity上,可能多個Fragment都會共用Activity的viewModel來達到數據同步的目的猛计。如果按照常規(guī)方式唠摹,我們只能進行類型強轉,然后調用 activity的viewModel的方法奉瘤。
但是這種寫法耦合性很高勾拉,不方便后期維護。
解決方案:使用泛型盗温,改變一下BaseFragment的泛型約束藕赞,改變之后如下:
之后,使用BaseFragment的地方就應該寫成如下這樣:
如果存在另外一個相同的Fragment卖局,也在使用父Activity的ViewModel斧蜕,他們就能夠達成數據同步,如下圖這樣:
效果:
MVVM優(yōu)缺點
優(yōu)點
-
屏幕旋轉時吼驶,界面數據不會重建
在ViewModel出現之前惩激,屏幕的旋轉店煞,會導致Activity/Fragment的完全重建,所牽涉到的數據也會重新創(chuàng)建风钻,如果頁面復雜顷蟀,可能會導致卡頓掉幀等問題。但是使用ViewModel作為數據模型層之后骡技,屏幕旋轉并不會重新創(chuàng)建數據鸣个,而是直接綁定已有ViewModel.
-
MVP的接口膨脹問題解決
MVVM是由MVP進化而來,進化的過程只做了一件事布朦,數據驅動囤萤。當然,在ViewModel結合DataBinding時代是趴,也就是LiveData出世之前涛舍,就已經形成了由數據驅動UI的機制,甚至可以進行反向驅動唆途,由UI變動來驅動數據變動富雅。在LiveData,LiveCycle出世之后肛搬,由數據驅動UI這一點仍然保持没佑,再也不用寫MVP那樣無限膨脹的P接口了。
-
杜絕內存泄漏
之前MVP的由P層來驅動UI温赔,以及調用業(yè)務蛤奢,P層必須持有Activity/Fragment的引用,可能會導致內存泄露陶贼,開發(fā)者必須在適當的時機釋放引用(比如本文使用LifeCycle注解接口啤贩,或者手段)。但是MVVM架構中骇窍,DataBinding和LiveData都集成了 lifeCycleOwner監(jiān)聽機制瓜晤,如果耗時任務執(zhí)行完畢之時,Activity/Fragment已經不處于STARTED狀態(tài)腹纳,并不會去驅動UI痢掠。
缺點
-
DataBinding時代混合代碼難看,難以調試
可能曾經一段時間DataBinding興起時嘲恍,數據的單向雙向綁定足画,確實讓開發(fā)體驗提升了不少。但是DataBinding所引起的負面效果佃牛,xml中的粘性代碼淹辞,AndroidStudio編譯偶爾會由于DataBinding出現故障,程序出錯之后調試也更加困難俘侠。但是有一說一象缀,使用LiveData之后蔬将,這個問題可以不用管,因為我根本就不會再去考慮DataBinding央星。
-
學習成本較高
就本文所提及的MVVM可能用到的4個組件ViewModel霞怀,LiveData,DataBinding莉给,LiveDataBus 而言毙石,要掌握完整,MVVM開發(fā)才會暢通無阻,而且后續(xù)還可能會存在版本變動颓遏,組件升級等徐矩。對于某些團隊而言,可能不會去花費這個時間去持續(xù)學習叁幢。而MVP模式滤灯,僅僅了解LifeCycle 以及 Contract(統(tǒng)籌MVP三層的思想),就能解決開發(fā)中的大部分問題, 在項目管理上維護成本也會更低曼玩,對開發(fā)團隊技術能力要求也更低力喷。
結語
Demo地址:https://github.com/18598925736/MvvmStandartDemo
本文講述了MVVM開發(fā)中可能用到的4個組件以及各自的核心原理,基本用法演训,并提供了基本的架構思路。時間有限贝咙,遇到真正的大型項目样悟,還是要繼續(xù)提煉基類,使用泛型庭猩,注解窟她,反射,甚至數據結構蔼水,設計模式震糖,去搭建一個完整項目架構。但是本文可以作為入門MVVM的完整攻略趴腋。
本人上一篇 漫談MVP吊说,結合 本文漫談MVVM, 目前主流的開發(fā)機構模式基礎知識應該完整了。
~開發(fā)工程師活到老學到老优炬,共勉颁井!