摘要:一點(diǎn)點(diǎn)入坑篇一點(diǎn)點(diǎn)入坑篇一點(diǎn)點(diǎn)入坑篇一點(diǎn)點(diǎn)入坑實(shí)戰(zhàn)前戲篇一點(diǎn)點(diǎn)入坑終章實(shí)戰(zhàn)相信有耐心看到這的小伙伴,完全足以通過(guò)偽代碼,感受出來(lái)以下代碼的設(shè)計(jì)思路。而這一切的實(shí)現(xiàn)并不會(huì)對(duì)外邊的邏輯產(chǎn)生影響,做到了實(shí)現(xiàn)的隔離。此外層在監(jiān)聽的結(jié)果,更新即可。
前言這次的實(shí)戰(zhàn)篇,是這個(gè)系列的最后一篇。本文綜合前幾篇的內(nèi)容,以偽代碼為主,幫大家理解Google所推崇的MVVM。
一點(diǎn)點(diǎn)入坑JetPack:ViewModel篇
一點(diǎn)點(diǎn)入坑JetPack:Lifecycle篇
一點(diǎn)點(diǎn)入坑JetPack:LiveData篇
一點(diǎn)點(diǎn)入坑JetPack:實(shí)戰(zhàn)前戲NetworkBoundResource篇
一點(diǎn)點(diǎn)入坑JetPack(終章):實(shí)戰(zhàn)MVVM
相信有耐心看到這的小伙伴,完全足以通過(guò)偽代碼,感受出來(lái)以下代碼的設(shè)計(jì)思路。Go~
正文 一、日常業(yè)務(wù)上代碼之前,我們思考一個(gè)小問(wèn)題。我們平時(shí)的業(yè)務(wù),很重的一個(gè)部分是從一個(gè)地方獲取數(shù)據(jù),然后在UI上展示出來(lái)。因此,本節(jié)實(shí)戰(zhàn)部分的背景:從網(wǎng)絡(luò)獲取一批數(shù)據(jù),如果網(wǎng)絡(luò)請(qǐng)求成功,便更新到RecycleView上;如果網(wǎng)絡(luò)請(qǐng)求不成功,加載本地已有緩存,然后更新到RecycleView上。
是不是很簡(jiǎn)單的需求,很多小伙伴可能順手就能寫出來(lái):
// 網(wǎng)絡(luò)請(qǐng)求
loadNetwork(參數(shù), Callback(){
// 請(qǐng)求成功,更新UI
success(data){
recyclerview.setData
}
// 請(qǐng)求失敗,讀取緩存
error(){
loadDB(參數(shù),Callback(){
// 緩存讀取成功,更新UI
success(data){
recyclerview.setData
}
})
}
})
非常直觀且易閱讀。我們?cè)谏钊胂胍幌?,如果其他?yè)面也有這樣的需求,是不是也要寫一份這個(gè)內(nèi)容?
這里肯定有小伙伴會(huì)指出,應(yīng)該進(jìn)行封裝!沒錯(cuò),還記得上一篇文章提到的NetworkBoundResource嗎?接下來(lái),就讓我們通過(guò)NetworkBoundResource,使用MVVM的思想去封裝這個(gè)業(yè)務(wù)。
二、走進(jìn)MVVM 2.1、走進(jìn)MVVM流程圖針對(duì)MVVM官方提供的一張比較清晰的流程圖:
2.2、走進(jìn)MVVM代碼
按照官方的推薦,我們需要一個(gè)Repository作為整個(gè)數(shù)據(jù)層的管理者。
例如,我們?cè)O(shè)計(jì)一個(gè)加載歌曲信息,然后更新到RecycleView上的需求。這個(gè)Repository咱們就叫,MusicRepository,表示音樂相關(guān)的數(shù)據(jù)獲取交由這個(gè)類去管理。
那么這個(gè)Repository是什么樣子的呢?
1、Repository MusicRepository// 這里的三個(gè)參數(shù),分別是:線程池,緩存模塊,網(wǎng)絡(luò)模塊
class MusicRepository(
val appExecutors: AppExecutors,
val musicDao: MusicDao, // 后文會(huì)展開這個(gè)類
val service: MusicApiService // 后文會(huì)展開這個(gè)類(具體的請(qǐng)求模塊)
) {
companion object {
val inst: MusicRepository by lazy {
// 這里傳入的內(nèi)容,當(dāng)然是業(yè)務(wù)方自己去實(shí)現(xiàn),比如這前業(yè)務(wù)已經(jīng)存在的DB/內(nèi)存緩存模塊;封裝好的網(wǎng)絡(luò)請(qǐng)求模塊,比如OkHttp/Retrofit等等
MusicRepository(xxx,xxx,xxx)
}
}
// Parameter會(huì)在后續(xù)中展開
fun querySongs(parameter : Parameter): LiveData> {
return object :
NetworkBoundResource(
appExecutors
) {
override fun saveCallResult(item: MusicResp) {
// 網(wǎng)絡(luò)請(qǐng)求成功,先存入緩存模塊
musicDao.saveDB(item)
}
override fun shouldFetch(data: MusicResp");: Boolean {
return 自己的是否請(qǐng)求網(wǎng)絡(luò)策略
}
override fun loadFromDb(): LiveData {
return musicDao.getCacheMusicResp(parameter.categoryId)
}
override fun createCall(): LiveData> {
// 調(diào)用網(wǎng)絡(luò)模塊的請(qǐng)求實(shí)現(xiàn)
return service.querySongs(parameter)
}
}.asLiveData()
}
}
接下來(lái)咱們挨個(gè)展開上述代碼中用到的類,MusicDao一個(gè)負(fù)責(zé)我們的Cache的實(shí)現(xiàn)類:
MusicDaoobject MusicDao {
private val musicStoreSongs: MutableMap<Long, MusicResp> by lazy {
mutableMapOf<Long, MusicResp>()
}
fun updateSongsCache(categoryId: Long, data: MusicResp) {
musicStoreSongs[categoryId] = data
}
fun querySongsCache(categoryId: Long): LiveData {
val cacheSongLiveData = MutableLiveData()
cacheSongLiveData.value = musicStoreSongs[categoryId]
return cacheSongLiveData
}
}
這里僅僅是實(shí)現(xiàn)了一套內(nèi)存緩存?;诖宋覀冞€可以實(shí)現(xiàn)自己的數(shù)據(jù)庫(kù)緩存,或者內(nèi)存+數(shù)據(jù)庫(kù)的二級(jí)緩存。而這一切的實(shí)現(xiàn)并不會(huì)對(duì)外邊的邏輯產(chǎn)生影響,做到了實(shí)現(xiàn)的隔離。
接下來(lái),咱們來(lái)看看網(wǎng)絡(luò)請(qǐng)求的實(shí)現(xiàn)類:MusicApiService
這里涉及了協(xié)程的內(nèi)容,建議沒有相關(guān)基礎(chǔ)的小伙伴,可以看一看我之前寫過(guò)的文章。
總是在聊線程Thread,試試協(xié)程吧!
MusicApiService
object MusicApiService {
override fun querySongs(parameter : Parameter): LiveData> {
val liveData = MutableLiveData>()
CoroutineScope(FastMain).launch {
val resp = resp = withContext(BuzzApiPool) {
// 這里對(duì)應(yīng)的是業(yè)務(wù)方自己的網(wǎng)絡(luò)實(shí)現(xiàn)封裝
val np = NetWorkManager.getInstance().networkProvider
val builder = Uri.parse("服務(wù)端的請(qǐng)求接口")
.buildUpon()
builder.appendQueryParameter("category_id", parameter.categoryId)
try {
// 自己封裝的get請(qǐng)求
val json = np.networkClient.get(builder.toString())
// 這里封裝的是Gson把String轉(zhuǎn)成JavaBean的方法
val data: MusicResp = fromServerResp(json)
data
} catch (e: Exception) {
MusicResp(e)
}
if (resp.isSuccess)) {
liveData.postValue(ApiSuccessResponse(resp))
} else {
liveData.postValue(
ApiErrorResponse(resp.exception ");"unknown_error"))
)
}
}
return liveData
}
}
有了Repository之后,我們則需要考慮一下ViewModel了。就叫MusicViewModel
2、ViewModelclass MusicViewModel :ViewModel(){
// Parameter 偽碼
var parameter = MutableLiveData()
val data : LiveData> = Transformations.switchMap(parameter) { parameter->
MusicRepository.inst.querySongs(parameter)
}
}
3、Activity/Fragment
ViewModel這樣就夠了,接下來(lái)就是我們的UI,這里就叫MusicActivity吧。
class MusicActivity : AppCompatActivity(){
private lateinit var musicViewModel: MusicViewModel
override fun onCreate(savedInstanceState: Bundle"); {
setContentView(R.layout.xxx)
musicViewModel = ViewModelProviders.of(this).get(MusicViewModel::class.java)
musicViewModel.data.observe(this, Observer { musicResp->
// 這里監(jiān)聽的數(shù)據(jù)就是MusicRepository返回的MusicResp
adapter.setData(musicResp)
}
// 通過(guò)LiveData通知MusicRepository進(jìn)行網(wǎng)絡(luò)請(qǐng)求
musicViewModel.parameter.value=Parameter(categoryId = xx) //本次請(qǐng)求的參數(shù)
}
}
到這里,我們最基本的使用,就完成了。
對(duì)于UI層來(lái)說(shuō):
它只需要在自己需要請(qǐng)求數(shù)據(jù)的時(shí)候通過(guò)MusicViewModel給“parameter”這個(gè)LiveData賦一個(gè)真正的請(qǐng)求參數(shù)就可以了。
Transformations.switchMap(參數(shù))會(huì)收到變換然后執(zhí)行MusicRepository.inst.querySongs(請(qǐng)求參數(shù)),之后的所有邏輯全部交由MusicRepository去處理。
至于怎么加載網(wǎng)絡(luò),怎么處理緩存,都是有各個(gè)獨(dú)立的模塊實(shí)現(xiàn)的。
此外UI層在監(jiān)聽musicViewModel.data的結(jié)果,更新UI即可。
這樣你會(huì)發(fā)現(xiàn),對(duì)于Activity/Fragment來(lái)說(shuō),它就只是View層了,一點(diǎn)邏輯操作都沒有。
2.3、存在問(wèn)題當(dāng)然這是理想狀態(tài),畢竟PM擁有無(wú)窮的想象力,什么樣的需求都會(huì)存在。
我猜理解清楚這套設(shè)計(jì)的小伙伴,一定會(huì)之處問(wèn)題所在。那就是:
1、性能問(wèn)題adapter.setData(musicResp),這就意味著,每次數(shù)據(jù)回調(diào)回來(lái)RecycleView都會(huì)更新,這樣就產(chǎn)生了很多無(wú)用的刷新。 而且這里是監(jiān)聽這個(gè)數(shù)據(jù)對(duì)象,如果想進(jìn)行局部刷新,那么Activity/Fragment中勢(shì)必要做很多額外的邏輯操作...
沒錯(cuò)!這是一個(gè)嚴(yán)重的問(wèn)題,但實(shí)際上Google早在很久之前就提供了一個(gè)類DiffUtil,這個(gè)類可以說(shuō)完美的幫我們?cè)谶@套設(shè)計(jì)里,搞定了RecycleView空刷的性能消耗。
2、額外的業(yè)務(wù)邏輯如果有必要,下篇文章可以聊一聊DiffUtil和Immutable、Mutable的理念
畢竟有些時(shí)候,我們沒辦法這么直來(lái)直去的加載數(shù)據(jù)。更多的時(shí)候,我們需要在業(yè)務(wù)回來(lái)時(shí)進(jìn)行一系列的額外代碼:比如數(shù)據(jù)的變換、邏輯的判斷...
數(shù)據(jù)變換:這類操作,可以使用函數(shù)式編程的思想,很方便的在ViewModel中完成并通過(guò)LiveData通知給observe方。
邏輯的判斷:這部分內(nèi)容,并不屬于MVVM(數(shù)據(jù)驅(qū)動(dòng))的部分。所以至于它還需要仁者見仁智者見智的封裝...
想了很久,還是覺得在此就停下實(shí)戰(zhàn)篇的內(nèi)容。因?yàn)槲乙詾檫@已經(jīng)夠了,如果能消化這整個(gè)系列的內(nèi)容,我相信該怎么使用JetPack,小伙伴們心中已經(jīng)有了自己的想法~
當(dāng)然,小伙伴們?nèi)绻惺裁锤}的操作,歡迎留言交流呦~
尾聲JetPack系列的文章,到此便告一段落了。不知道一路追過(guò)來(lái)的朋友們是否有收獲。
下一個(gè)長(zhǎng)篇系列會(huì)是什么內(nèi)容,暫時(shí)還沒有想好。大家有啥感興趣的,可以留言給點(diǎn)建議~
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/7372.html
摘要:由于長(zhǎng)期苦惱于第三方庫(kù)選擇的廣大開發(fā)者而言,這也是谷歌為我們提供的一盞明燈。手機(jī)淘寶構(gòu)架演化實(shí)踐淘寶相信都不陌生了從年開始,從萬(wàn)增長(zhǎng)到超過(guò)億,面臨的問(wèn)題包括研發(fā)支撐所需要解決的事情各不相同。 ...
摘要:?jiǎn)栴}為了防止銷毀時(shí)異步任務(wù)仍然在進(jìn)行所導(dǎo)致的內(nèi)存泄露,我們都會(huì)在方法中去取消異步任務(wù)。總結(jié)層可以天然自動(dòng)監(jiān)視銷毀,我一直在找尋如何優(yōu)雅的自動(dòng)取消異步任務(wù),在目前來(lái)看是最佳的方案。協(xié)程絕對(duì)是最先進(jìn)的,效率最高,最優(yōu)雅的技術(shù)棧組合。前提 在Android MVVM模式,我使用了Jetpack包中的ViewModel來(lái)實(shí)現(xiàn)業(yè)務(wù)層,當(dāng)然你也可以使用DataBinding,關(guān)于Android業(yè)務(wù)層架構(gòu)...
閱讀 3756·2021-11-24 10:46
閱讀 1720·2021-11-15 11:38
閱讀 3773·2021-11-15 11:37
閱讀 3500·2021-10-27 14:19
閱讀 1958·2021-09-03 10:36
閱讀 2005·2021-08-16 11:02
閱讀 3012·2019-08-30 15:55
閱讀 2269·2019-08-30 15:44