内容简介:这次的实战篇,是这个系列的最后一篇。本文综合前几篇的内容,以伪代码为主,帮大家理解Google所推崇的MVVM。相信有耐心看到这的小伙伴,完全足以通过伪代码,感受出来以下代码的设计思路。Go~上代码之前,我们思考一个小问题。我们平时的业务,很重的一个部分是从一个地方获取数据,然后在UI上展示出来。因此,本节实战部分的背景:从网络获取一批数据,如果网络请求成功,便更新到RecycleView上;如果网络请求不成功,加载本地已有缓存,然后更新到RecycleView上。
这次的实战篇,是这个系列的最后一篇。本文综合前几篇的内容,以伪代码为主,帮大家理解Google所推崇的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<Resource<MusicResp>> { return object : NetworkBoundResource<MusicResp, MusicResp>( appExecutors ) { override fun saveCallResult(item: MusicResp) { // 网络请求成功,先存入缓存模块 musicDao.saveDB(item) } override fun shouldFetch(data: MusicResp?): Boolean { return 自己的是否请求网络策略 } override fun loadFromDb(): LiveData<MusicResp> { return musicDao.getCacheMusicResp(parameter.categoryId) } override fun createCall(): LiveData<ApiResponse<MusicResp>> { // 调用网络模块的请求实现 return service.querySongs(parameter) } }.asLiveData() } } 复制代码
接下来咱们挨个展开上述代码中用到的类,MusicDao一个负责我们的Cache的实现类:
MusicDao
object 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<MusicResp> { val cacheSongLiveData = MutableLiveData<MusicResp>() cacheSongLiveData.value = musicStoreSongs[categoryId] return cacheSongLiveData } } 复制代码
这里仅仅是实现了一套内存缓存。基于此我们还可以实现自己的数据库缓存,或者内存+数据库的二级缓存。而这一切的实现并不会对外边的逻辑产生影响,做到了实现的隔离。
接下来,咱们来看看网络请求的实现类:MusicApiService
这里涉及了 协程 的内容,建议没有相关基础的小伙伴,可以看一看我之前写过的文章。
MusicApiService
object MusicApiService { override fun querySongs(parameter : Parameter): LiveData<ApiResponse<MusicResp>> { val liveData = MutableLiveData<ApiResponse<MusicResp>>() 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 ?: RuntimeException("unknown_error")) ) } } return liveData } } 复制代码
有了Repository之后,我们则需要考虑一下ViewModel了。就叫 MusicViewModel
2、ViewModel
class MusicViewModel :ViewModel(){ // Parameter 伪码 var parameter = MutableLiveData<Parameter>() val data : LiveData<Resource<MusicResp>> = 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层了,一点逻辑操作都没有。
当然这是理想状态,毕竟PM拥有无穷的想象力,什么样的需求都会存在。
2.3、存在问题
我猜理解清楚这套设计的小伙伴,一定会之处问题所在。那就是:
1、性能问题
adapter.setData(musicResp)
,这就意味着,每次数据回调回来RecycleView都会更新,这样就产生了很多无用的刷新。 而且这里是监听这个数据对象,如果想进行局部刷新,那么Activity/Fragment中势必要做很多额外的逻辑操作...
没错!这是一个严重的问题,但实际上Google早在很久之前就提供了一个类 DiffUtil
,这个类可以说完美的帮我们在这套设计里,搞定了RecycleView空刷的性能消耗。
如果有必要,下篇文章可以聊一聊 DiffUtil
和Immutable、Mutable的理念
2、额外的业务逻辑
毕竟有些时候,我们没办法这么直来直去的加载数据。更多的时候,我们需要在业务回来时进行一系列的额外代码:比如 数据的变换 、 逻辑的判断 ...
-
数据变换:这类操作,可以使用 函数式编程 的思想,很方便的在ViewModel中完成并通过LiveData通知给observe方。
-
逻辑的判断:这部分内容,并不属于MVVM(数据驱动)的部分。所以至于它还需要仁者见仁智者见智的封装...
想了很久,还是觉得在此就停下实战篇的内容。因为我以为这已经够了,如果能消化这整个系列的内容,我相信该怎么使用JetPack,小伙伴们心中已经有了自己的想法~
当然,小伙伴们如果有什么更骚的操作,欢迎留言交流呦~
尾声
JetPack系列的文章,到此便告一段落了。不知道一路追过来的朋友们是否有收获。
下一个长篇系列会是什么内容,暂时还没有想好。大家有啥感兴趣的,可以留言给点建议~
我是一个应届生,最近和朋友们维护了一个公众号,内容是我们在从应届生过渡到开发这一路所踩过的坑,以及我们一步步学习的记录,如果感兴趣的朋友可以关注一下,一同加油~
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 「Flask实战」鱼书项目实战一
- 「Flask实战」鱼书项目实战三
- 「Flask实战」鱼书项目实战四
- 「Flask实战」鱼书项目实战六
- RocketMQ实战系列从理论到实战
- 「Flask实战」flask鱼书项目实战二
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Language Implementation Patterns
Terence Parr / Pragmatic Bookshelf / 2010-1-10 / USD 34.95
Knowing how to create domain-specific languages (DSLs) can give you a huge productivity boost. Instead of writing code in a general-purpose programming language, you can first build a custom language ......一起来看看 《Language Implementation Patterns》 这本书的介绍吧!