JetPack有提供規(guī)范的架構(gòu)模式旦委,我們使用JetPack,必須要遵循它的規(guī)范雏亚,接下來我們將利用JetPack實(shí)現(xiàn)MVVM的架構(gòu)模式缨硝。
MVC和MVVM介紹
MVC
我們目前的代碼主要邏輯和數(shù)據(jù)都在Activity/Fragment中,有人定義為MVC架構(gòu)评凝,有人卻不這么認(rèn)為追葡。因?yàn)?strong>Activity/Fragment和View又是很難完全區(qū)分開來,和Java后臺(tái)開發(fā)中完全的MVC模式有差別奕短。我們暫且把這中模式定義為MVC模式吧宜肉。
咱們畫個(gè)簡單的示意圖:
通過示意圖我們可以看出,Activity/Fragment作為Controller和View的組合體翎碑,分擔(dān)的任務(wù)比較繁重谬返,這里面的代碼會(huì)非常的臃腫。
為了解決這個(gè)問題日杈,Google通過JetPack的架構(gòu)規(guī)范了MVVM的架構(gòu)模式遣铝。
MVVM
我們先通過Google官網(wǎng)的一張圖片來了解下他們規(guī)范的架構(gòu)模式:
這張圖片定義了Activity/Fragment如何獲取數(shù)據(jù)的分層模式佑刷。
- Activity/Fragment持有ViewModel,ViewModel是專門負(fù)責(zé)數(shù)據(jù)管理的類
- ViewModel管理LiveData中的數(shù)據(jù)
- LiveData的數(shù)據(jù)是從倉庫Repository獲得
- Repository又是從數(shù)據(jù)庫Room或者網(wǎng)絡(luò)webService獲得
細(xì)心的你可能發(fā)現(xiàn)了酿炸,這個(gè)分層非常詳細(xì)瘫絮,但是只是數(shù)據(jù)的單向獲取流程,獲取到的數(shù)據(jù)如何和UI的重繪聯(lián)系起來沒有體現(xiàn)出來填硕。
接下來我用一個(gè)詳盡的示意圖解釋下:
這個(gè)示例圖增加了數(shù)據(jù)回流的過程麦萤,數(shù)據(jù)從數(shù)據(jù)庫或者網(wǎng)絡(luò)服務(wù)器中獲取后,通過CallBack或者LiveData反向回流到ViewModel數(shù)據(jù)管理類扁眯。
注意了壮莹,這時(shí)候ViewModel中的LiveData數(shù)據(jù)直接驅(qū)動(dòng)了界面的重繪,無需經(jīng)過Activity/Fragment的轉(zhuǎn)發(fā)姻檀。
此外命满,Jetpack還提供了數(shù)據(jù)綁定,使用數(shù)據(jù)綁定DataBinding后Activity/Fragment不需要持有View引用绣版,當(dāng)然也不會(huì)有View的事件驅(qū)動(dòng)胶台,流程如下所示:
到此為止,我們已經(jīng)了解到了JetPack的架構(gòu)結(jié)構(gòu)僵娃,是時(shí)候應(yīng)用到我們的項(xiàng)目中來啦概作。
修改歌單頁面
接著上個(gè)教程的結(jié)尾,我們給歌單列表頁面添加一個(gè)PlayListFragmentViewModel的ViewModel對象默怨,來作為這個(gè)頁面的數(shù)據(jù)管理類讯榕。
- 加入依賴庫
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
通過引入的庫我們可以看到ViewModel存在于lifecycle庫,也就是說它能感知生命周期的變化匙睹。
- 新建PlayListViewModel文件
我們新建一個(gè)ViewModel的子類PlayListViewModel愚屁,作為歌單列表的ViewModel類,類中的代碼如下所示:
class PlayListViewModel: ViewModel() {
// 1
private val _playList = MutableLiveData<List<PlayItem>>()
// 2
val playList: LiveData<List<PlayItem>>
get() = _playList
// 3
var type: String = ""
// 4
fun fetchData() {
// 5
viewModelScope.launch {
when (type) {
"推薦" -> {
// 6
val response = PlaylistRepository.getRecommendPlaylist(30, 0)
// 7
_playList.value = response.playlists
}
"精品" -> {
val response = PlaylistRepository.getHighQualityPlaylist(30, 0)
_playList.value = response.playlists
}
"官方" -> {
val response = PlaylistRepository.getOrgPlaylist(30, 0)
_playList.value = response.playlists
}
else -> {
val response = PlaylistRepository.getPlaylistByCat(30, 0, type)
_playList.value = response.playlists
}
}
}
}
}
我們來分步驟解釋下代碼的含義:
- 定義一個(gè)值為
List<PlayItem>
的MutableLiveData變量_playList
,這里MutableLiveData就是值可以改變的LiveData - 定義一個(gè)值為
List<PlayItem>
的LiveData變量playList
, 定義這個(gè)變量的意義是把_playList
封裝起來痕檬,只能內(nèi)部修改它的值霎槐,提供給外部是的不能修改的值 - 這個(gè)變量是傳入的不同的歌單類型
-
fetchData
這個(gè)方法是請求數(shù)據(jù)的方法 - viewModelScope這個(gè)是和ViewModel關(guān)聯(lián)的協(xié)程作用域,這個(gè)作用域的生命周期和ViewModel一致梦谜,超過這個(gè)作用域協(xié)程會(huì)被取消丘跌。
協(xié)程和協(xié)程作用域的相關(guān)知識請參考前面的教程
- 通過PlaylistRepository對象去請求數(shù)據(jù),這個(gè)類后面介紹
- 將請求得到的結(jié)果賦值給
_playList
- PlaylistRepository文件
實(shí)際上可以直接用MusicApiService進(jìn)行請求唁桩,為什么會(huì)多一個(gè)Repository層闭树。其實(shí)是為了模塊化的方便,因?yàn)橐粋€(gè)大型項(xiàng)目會(huì)有很多的功能塊荒澡,請求也會(huì)非常的的多报辱,這樣建立多個(gè)Repository*進(jìn)行模塊分組,是個(gè)非常不錯(cuò)的實(shí)踐单山。
PlaylistRepository的代碼如下所示碍现,
object PlaylistRepository {
/* 獲取推薦歌單列表 */
suspend fun getRecommendPlaylist(limit: Int, offset: Int) : PlayListResponse {
return MusicApiService.create().getRecommendPlaylist(limit, offset)
}
/* 獲取金品歌單列表 */
suspend fun getHighQualityPlaylist(limit: Int, offset: Int) : PlayListResponse {
return MusicApiService.create().getHighQualityPlaylist(limit, offset)
}
/* 獲取官方歌單列表 */
suspend fun getOrgPlaylist(limit: Int, offset: Int): PlayListResponse {
return MusicApiService.create().getCatPlaylist(limit, offset, "new", null)
}
/* 根據(jù)類別獲取歌單列表 */
suspend fun getPlaylistByCat(limit: Int, offset: Int, cat: String): PlayListResponse {
return MusicApiService.create().getCatPlaylist(limit, offset, null, cat)
}
}
這段代碼很好理解幅疼,但是需要說明一點(diǎn),suspend函數(shù)必須在suspend函數(shù)中或者協(xié)程中調(diào)用昼接,所以這個(gè)文件的方法也都是設(shè)計(jì)成suspend函數(shù)才能調(diào)用MusicApiService的suspend函數(shù)爽篷。
- 改造PlayListFragment使用ViewModel
class PlayListFragment : Fragment() {
// 1
private val viewModel by viewModels<PlayListViewModel>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
...
// 2
arguments?.getString(QueryKey)?.let {
viewModel.type = it
}
// 3
viewModel.playList.observe(viewLifecycleOwner, Observer {
// 4
playAdapter.submitList(it)
})
// 5
viewModel.playList.value ?: viewModel.fetchData()
}
}
代碼解釋如下:
- 通過by委托模式生成PlayListViewModel對象,PlayListFragment只留下這個(gè)viewModel屬性慢睡。
- 將歌單類型賦值給ViewModel的type
- 這個(gè)viewModel.playList是LiveData狼忱,這句代碼就含義是LiveData調(diào)用
observe
方法。那這段話代表什么呢一睁?
官網(wǎng)解釋:LiveData 是一種可觀察的數(shù)據(jù)存儲(chǔ)器類。與常規(guī)的可觀察類不同佃却,LiveData 具有生命周期感知能力者吁,意指它遵循其他應(yīng)用組件(如 Activity、Fragment 或 Service)的生命周期饲帅。
用過RxJava的同學(xué)對LiveData 是一種可觀察的數(shù)據(jù)存儲(chǔ)器類這句話有比較好的理解复凳,就是說LiveData對象數(shù)據(jù)的變化可以及時(shí)通知觀察者,觀察者可以通過根據(jù)數(shù)據(jù)進(jìn)行相關(guān)的操作灶泵。
這里的
observe
方法就是添加觀察者育八,第一個(gè)參數(shù)決定了觀察的生命周期,第二個(gè)lambda參數(shù)就是觀測到數(shù)據(jù)變化后的操作
- 將數(shù)據(jù)提交個(gè)Adapter
細(xì)心的讀者可能會(huì)發(fā)現(xiàn)不是使用adapter.notifyDataSetChanged赦邻,這是因?yàn)檫@個(gè)方法性能更好髓棋,可以通過DiffUtil.ItemCallback對比數(shù)據(jù)的差異,只對更新的數(shù)據(jù)進(jìn)行動(dòng)畫和刷新惶洲,而不是一股腦的所有界面都刷新按声。
- 調(diào)用
viewModel.fetchData()
方法
這里遺留了一個(gè)問題,為什么先判斷value存不存在恬吕,存在就不請求了呢签则?不是應(yīng)該
onViewCreated
這時(shí)候value肯定是不存在的嗎?
遺留問題1 - DiffUtil.ItemCallback怎么使用
// 1
class PlaylistItemAdapter:
ListAdapter<PlayItem, PlaylistItemAdapter.PlaylistItemHolder>(DiffCallback) {
// 2
object DiffCallback: DiffUtil.ItemCallback<PlayItem>() {
override fun areItemsTheSame(oldItem: PlayItem, newItem: PlayItem): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: PlayItem, newItem: PlayItem): Boolean {
return oldItem.name == newItem.name && oldItem.coverImgUrl == newItem.coverImgUrl
}
}
}
- DiffUtil.ItemCallback是在PlaylistItemAdapter構(gòu)造函數(shù)中傳入的铐料。
- DiffUtil.ItemCallback需要實(shí)現(xiàn)兩個(gè)方法渐裂,areItemsTheSame是判斷兩個(gè)Item是否是同一個(gè)Item,areContentsTheSame是判斷兩個(gè)Item是否內(nèi)容相同钠惩。
遺留問題2 - 為什么先判斷LiveData的value存不存在柒凉?
我們先來看一個(gè)現(xiàn)象:
常規(guī)寫法:
切換橫豎屏,F(xiàn)ragment會(huì)重新創(chuàng)建妻柒,所以切換完成后會(huì)重新請求數(shù)據(jù)扛拨。
用ViewModel的寫法:
切換橫豎屏,F(xiàn)ragment會(huì)重新創(chuàng)建举塔,切換完成后并沒有請求數(shù)據(jù)绑警,但是還能正常顯示列表求泰。
為什么呢?先看一張官網(wǎng)的圖和對ViewModel生命周期的解釋
ViewModel 對象存在的時(shí)間范圍是獲取 ViewModel 時(shí)傳遞給 ViewModelProvider 的 Lifecycle计盒。ViewModel 將一直留在內(nèi)存中渴频,直到限定其存在時(shí)間范圍的 Lifecycle 永久消失:對于 Activity,是在 Activity 完成時(shí)北启;而對于 Fragment卜朗,是在 Fragment 分離時(shí)。
相信看到這里咕村,你就理解了為什么onViewCreated的時(shí)候LiveData的value有可能存在了场钉。
結(jié)語
目前我們已經(jīng)將歌單頁面改造成了MVVM的架構(gòu)了,接下來我們將繼續(xù)上一節(jié)的內(nèi)容懈涛,利用Pageing和LiveData實(shí)現(xiàn)加載更多逛万。