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