Kotlin + 協程 + Retrofit + MVVM優雅的實現網絡請求


  前言

  最近一直在修煉Kotlin,說實話真香真好用,剛好公司准備交給我一個新項目,於是打算直接用Kotlin來構建項目。剛好整體架構搭建完畢了,於是把網絡請求這一部分先分享給大家。這次使用到的是 協程+ retrofit +mvvm的模式,我這兒直接用一個簡單的demo來看一下具體的實現方式吧。文章只是描述實現思路,需要demo的直接跳到文末。

  項目配置

  首先先引入所需要的依賴

  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的基本實現,看看需要哪些步驟

  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

  }

  ~~~

  3.發起請求

  ~~~

  val service = retrofit.create(RequestService::class.java)

  service.getDatas().enqueue(object : Callback {

  override fun onFailure(call: retrofit2.Call, t: Throwable) {

  TODO("not implemented") //To change body of created functions use File | Settings | File Templates.

  }

  override fun onResponse(call: retrofit2.Call, response: Response) {

  TODO("not implemented") //To change body of created functions use File | Settings | File Templates.

  }

  })

  ~~~

  這只是描述了一個retrofit的簡單請求方式,實際項目中基本上都會封裝之后再使用,也為了提高代碼的可讀性,降低各部分的耦合性, 通俗點來說,只有各司其職才能把工作干好嘛,接下來咱們就圍繞着各司其職來一個一個實現

  協程實現

  接下來把上面的請求換成協程的方式來實現

  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

  }

  ~~~

  因為我們后續會使用到協程,所以這兒將Call換成了Deferred

  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 by lazy { MutableLiveData().also { loadDatas() } }

  private val repository = ArticleRepository()

  fun getActicle(): LiveData {

  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的代碼,用來發起網絡請求獲取數據

  class ArticleRepository {

  suspend fun getDatas(): DataBean {

  return RetrofitClient.reqApi.getDatas().await()

  }

  }

  在Activity中代碼如下

  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 request(call: suspend () -> ResponseData): ResponseData {

  return withContext(Dispatchers.IO){ call.invoke()}

  }

  }

  通過在BaseRepository里面寫了一個專門的請求方法,這樣每次只需執行request就行了 請求參考如下

  class ArticleRepository : BaseRepository() {

  suspend fun getDatas(): ResponseData<list> {

  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> by lazy { MutableLiveData<list>().also { loadDatas() } }

  private val repository = ArticleRepository()

  fun getActicle(): LiveData<list> {

  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() }

  private val finally by lazy { MutableLiveData() }

  //運行在UI線程的協程

  fun launchUI( block: suspend CoroutineScope.() -> Unit) {

  uiScope.launch(Dispatchers.Main) {

  try {

  block()

  }catch (e:Exception){

  error.value = e

  }finally {

  finally.value = 200

  }

  鄭州不孕不育醫院:http://yyk.39.net/zz3/zonghe/1d427.html/鄭州不孕不育醫院哪家好:http://yyk.39.net/zz3/zonghe/1d427.html/鄭州不孕不育醫院排名:http://yyk.39.net/zz3/zonghe/1d427.html/

  }

  }

  override fun onCleared() {

  super.onCleared()

  viewModelJob.cancel()

  }

  /**

  * 請求失敗,出現異常

  */

  fun getError(): LiveData {

  return error

  }

  /**

  * 請求完成,在此處做一些關閉操作

  */

  fun getFinally(): LiveData {

  return finally

  }

  }

  結語

  上面只是描述了一些實現過程,具體使用還得參考demo,基本上能滿足大部分的需求,要是感興趣的小伙伴,可以下載demo參考,感覺不錯的話,順手點個贊就很滿足了。於所學不精,可能會有使用不當之處,希望各位大佬能指出不當的地方,深表感謝。

  **最后給大家分享一份移動架構大綱,包含了移動架構師需要掌握的所有的技術體系,大家可以對比一下自己不足或者欠缺的地方有方向的去學習提升;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM