Kotlin + 协程 + Retrofit + MVVM优雅的实现网络请求


 

前言

最近一直在修炼Kotlin,说实话真香真好用,恰好公司准备交给我一个新项目,因而打算直接用Kotlin来构建项目。恰好总体架构搭建完毕了,因而把网络请求这一部分先分享给你们。此次使用到的是 协程+ retrofit +mvvm的模式,我这儿直接用一个简单的demo来看一下具体的实现方式吧。文章只是描述实现思路,须要demo的直接跳到文末。java

项目配置

首先先引入所须要的依赖android

implementation 'android.arch.lifecycle:extensions:1.1.1' //协程 implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1' //retrofit + okHttp3 implementation 'com.squareup.retrofit2:retrofit:2.4.0' implementation 'com.squareup.retrofit2:converter-gson:2.4.0' implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' 

实现思路

无论设计模式这些,先来一个简单的网络请求,就retrofit的基本实现,看看须要哪些步骤json

1.建立retrofit

~~~
 val retrofit = Retrofit.Builder()
 .baseUrl(RetrofitClient.BASE_URL)
 .addConverterFactory(GsonConverterFactory.create())
 .addCallAdapterFactory(CoroutineCallAdapterFactory())
 .build()
~~~

2.建立service接口

~~~
 interface RequestService { @GET("wxarticle/chapters/json") fun getDatas() : Call<DataBean> } ~~~ 

3.发起请求

~~~
 val service = retrofit.create(RequestService::class.java) service.getDatas().enqueue(object : Callback<DataBean> { override fun onFailure(call: retrofit2.Call<DataBean>, t: Throwable) { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } override fun onResponse(call: retrofit2.Call<DataBean>, response: Response<DataBean>) { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } }) ~~~ 

这只是描述了一个retrofit的简单请求方式,实际项目中基本上都会封装以后再使用,也为了提升代码的可读性,下降各部分的耦合性, 通俗点来讲,只有各司其职才能把工做干好嘛,接下来我们就围绕着各司其职来一个一个实现设计模式

协程实现

接下来把上面的请求换成协程的方式来实现ruby

1.建立RetrofitClient

object为了使RetrofitClient 只能有一个实例
~~~
 object RetrofitClient {
 val BASE_URL = "https://wanandroid.com/" val reqApi by lazy { val retrofit = Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(CoroutineCallAdapterFactory()) .build() return@lazy retrofit.create(RequestService::class.java) } } ~~~ 

2.建立service接口类

~~~
interface RequestService { @GET("wxarticle/chapters/json") fun getDatas() : Deferred<DataBean> } ~~~ 

由于咱们后续会使用到协程,因此这儿将Call换成了Deferredbash

3.发起请求

~~~
 GlobalScope.launch(Dispatchers.Main) {
 withContext(Dispatchers.IO){
 val dataBean = RetrofitClient.reqApi.getDatas().await()
 }
 //更新ui } ~~~ 

上面用到了协程,这儿只讲述他的应用了,具体的移步官方文档进一步了解。 网络请求在协程中,而且在IO调度单元,因此不用担会阻塞主线程网络

协程 + ViewModel + LiveData实现

上面也只是简单的实现,只不过是换成了协程,在项目中,还能够进一步封装,方便使用前面也提到了MVVM,因此还用到了Android 新引入的组件架构之ViewModel和LiveData,先看ViewModel的实现架构

class ScrollingViewModel : ViewModel() { private val TAG = ScrollingViewModel::class.java.simpleName private val datas: MutableLiveData<DataBean> by lazy { MutableLiveData<DataBean>().also { loadDatas() } } private val repository = ArticleRepository() fun getActicle(): LiveData<DataBean> { return datas } private fun loadDatas() { GlobalScope.launch(Dispatchers.Main) { getData() } // Do an asynchronous operation to fetch users. } private suspend fun getData() { val result = withContext(Dispatchers.IO){ // delay(10000) repository.getDatas() } datas.value = result } } 

ViewModel将做为View与数据的中间人,Repository专职数据获取,下面看一下Repository的代码,用来发起网络请求获取数据async

class ArticleRepository { suspend fun getDatas(): DataBean { return RetrofitClient.reqApi.getDatas().await() } } 

在Activity中代码以下mvvm

private fun initData() { model.getActicle().observe(this, Observer{ //获取到数据 toolbar.setBackgroundColor(Color.RED) }) } 

后续优化

1.内存泄漏问题解决方案

结和了各位大佬们的意见,将使用GlobalScope可能会出现内存泄漏的问题进行了优化。由于在协程进行请求的过程当中,若此时ViewModel销毁,里面的协程正在请求的话,将没法销毁,出现内存泄漏,因此在ViewModel onCleared 里面,即便结束协程任务,参考代码以下。

open class BaseViewModel : ViewModel(), LifecycleObserver{ private val viewModelJob = SupervisorJob() private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) //运行在UI线程的协程 fun launchUI( block: suspend CoroutineScope.() -> Unit) { try { uiScope.launch(Dispatchers.Main) { block() } }catch (e:Exception){ e.printStackTrace() } } override fun onCleared() { super.onCleared() viewModelJob.cancel() } } 

固然,最好的方式是使用viewModelScope,可是我在引入该包的时候,会报错,因为最近比较忙暂时还没来得急解决,后续问题有时间我也会继续修改,还望各位大佬能帮忙指点

2.优化请求代码

先看下以前的请求代码

private suspend fun getData() { val result = withContext(Dispatchers.IO){ // delay(10000) repository.getDatas() } datas.value = result } 

每一次都须要写个withContext(),实际运用中,感受有点不方便,因而乎想了一下,怎么才能给他封进请求方法里面? 代码以下

open class BaseRepository {
 suspend fun <T : Any> request(call: suspend () -> ResponseData<T>): ResponseData<T> { return withContext(Dispatchers.IO){ call.invoke()} } } 

经过在BaseRepository里面写了一个专门的请求方法,这样每次只需执行request就好了 请求参考以下

class ArticleRepository : BaseRepository() { suspend fun getDatas(): ResponseData<List<Data>> { return request { delay(10000) Log.i(ScrollingViewModel::class.java.simpleName,"loadDatas1 run in ${Thread.currentThread().name}") RetrofitClient.reqApi.getDatas().await() } } } 

注:这个 delay(10000)只是我测试用的,意思是休眠当前协程,防止萌新在本身项目中加上了,仍是有必要说一下的

再看看ViewModel中就太简单了

class ScrollingViewModel : BaseViewModel() { private val TAG = ScrollingViewModel::class.java.simpleName private val datas: MutableLiveData<List<Data>> by lazy { MutableLiveData<List<Data>>().also { loadDatas() } } private val repository = ArticleRepository() fun getActicle(): LiveData<List<Data>> { return datas } private fun loadDatas() { launchUI { Log.i(TAG,"loadDatas1 run in ${Thread.currentThread().name}") val result = repository.getDatas() Log.i(TAG,"loadDatas3 run in ${Thread.currentThread().name}") datas.value = result.data } // Do an asynchronous operation to fetch users. } } 

注意看请求部分,就两句话,一句发起请求val result = repository.getDatas(),而后就是为咱们的LiveData赋值了,看起有没有同步代码的感受,这就是协程的魅力所在,为了验证咱们的请求没有阻塞主线程,我打印了日志

06-19 12:26:35.736 13648-13648/huaan.com.mvvmdemo I/ScrollingViewModel: loadDatas start run in main 06-19 12:26:45.743 13648-13684/huaan.com.mvvmdemo I/ScrollingViewModel: request run in DefaultDispatcher-worker-1 06-19 12:26:46.227 13648-13648/huaan.com.mvvmdemo I/ScrollingViewModel: loadDatas end run in main 

看到了吧,各司其职,效果很棒

异常处理

搞了半天才发现没有弄异常处理,当请求失败以后,项目就崩溃了,这不是是咱们想要的结果,因为好没有想到更好的处理方式,只能在外面套个tyr catch 顶一顶了,参考以下

open class BaseViewModel : ViewModel(), LifecycleObserver{ private val viewModelJob = SupervisorJob() private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) private val error by lazy { MutableLiveData<Exception>() } private val finally by lazy { MutableLiveData<Int>() } //运行在UI线程的协程 fun launchUI( block: suspend CoroutineScope.() -> Unit) { uiScope.launch(Dispatchers.Main) { try { block() }catch (e:Exception){ error.value = e }finally { finally.value = 200 } } } override fun onCleared() { super.onCleared() viewModelJob.cancel() } /** * 请求失败,出现异常 */ fun getError(): LiveData<Exception> { return error } /** * 请求完成,在此处作一些关闭操做 */ fun getFinally(): LiveData<Int> { return finally } } 

结语

上面只是描述了一些实现过程,具体使用还得参考demo,基本上能知足大部分的需求,要是感兴趣的小伙伴,能够下载demo参考,感受不错的话,顺手点个赞就很知足了。于所学不精,可能会有使用不当之处,但愿各位大佬能指出不当的地方,深表感谢。

最后

给你们分享一份移动架构大纲,包含了移动架构师须要掌握的全部的技术体系,你们能够对比一下本身不足或者欠缺的地方有方向的去学习提高;

 


 

须要高清架构图以及图中视频资料的能够查看我主页

相关文章
相关标签/搜索