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