资讯专栏INFORMATION COLUMN

一点点入坑JetPack(终章):实战MVVM

ZweiZhao / 576人阅读

摘要:一点点入坑篇一点点入坑篇一点点入坑篇一点点入坑实战前戏篇一点点入坑终章实战相信有耐心看到这的小伙伴,完全足以通过伪代码,感受出来以下代码的设计思路。而这一切的实现并不会对外边的逻辑产生影响,做到了实现的隔离。此外层在监听的结果,更新即可。

前言

这次的实战篇,是这个系列的最后一篇。本文综合前几篇的内容,以伪代码为主,帮大家理解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的实现类:

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 {
        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、ViewModel
class 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层了,一点逻辑操作都没有。

当然这是理想状态,毕竟PM拥有无穷的想象力,什么样的需求都会存在。

2.3、存在问题

我猜理解清楚这套设计的小伙伴,一定会之处问题所在。那就是:

1、性能问题

adapter.setData(musicResp),这就意味着,每次数据回调回来RecycleView都会更新,这样就产生了很多无用的刷新。 而且这里是监听这个数据对象,如果想进行局部刷新,那么Activity/Fragment中势必要做很多额外的逻辑操作...

没错!这是一个严重的问题,但实际上Google早在很久之前就提供了一个类DiffUtil,这个类可以说完美的帮我们在这套设计里,搞定了RecycleView空刷的性能消耗。

如果有必要,下篇文章可以聊一聊DiffUtil和Immutable、Mutable的理念

2、额外的业务逻辑

毕竟有些时候,我们没办法这么直来直去的加载数据。更多的时候,我们需要在业务回来时进行一系列的额外代码:比如数据的变换逻辑的判断...

数据变换:这类操作,可以使用函数式编程的思想,很方便的在ViewModel中完成并通过LiveData通知给observe方。

逻辑的判断:这部分内容,并不属于MVVM(数据驱动)的部分。所以至于它还需要仁者见仁智者见智的封装...

想了很久,还是觉得在此就停下实战篇的内容。因为我以为这已经够了,如果能消化这整个系列的内容,我相信该怎么使用JetPack,小伙伴们心中已经有了自己的想法~

当然,小伙伴们如果有什么更骚的操作,欢迎留言交流呦~

尾声

JetPack系列的文章,到此便告一段落了。不知道一路追过来的朋友们是否有收获。

下一个长篇系列会是什么内容,暂时还没有想好。大家有啥感兴趣的,可以留言给点建议~

我是一个应届生,最近和朋友们维护了一个公众号,内容是我们在从应届生过渡到开发这一路所踩过的坑,以及我们一步步学习的记录,如果感兴趣的朋友可以关注一下,一同加油~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/7372.html

相关文章

  • 新风向!成就了Android,热门框架排第一,你还是不够了解它!

    摘要:由于长期苦恼于第三方库选择的广大开发者而言,这也是谷歌为我们提供的一盏明灯。手机淘宝构架演化实践淘宝相信都不陌生了从年开始,从万增长到超过亿,面临的问题包括研发支撑所需要解决的事情各不相同。 ...

    sixgo 评论0 收藏0
  • 史上最优雅的在VM层取消Coroutine的方式

    摘要:问题为了防止销毁时异步任务仍然在进行所导致的内存泄露,我们都会在方法中去取消异步任务。总结层可以天然自动监视销毁,我一直在找寻如何优雅的自动取消异步任务,在目前来看是最佳的方案。协程绝对是最先进的,效率最高,最优雅的技术栈组合。前提 在Android MVVM模式,我使用了Jetpack包中的ViewModel来实现业务层,当然你也可以使用DataBinding,关于Android业务层架构...

    cuieney 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<