最近一直閉關修煉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的基本實現,看看須要哪些步驟git
~~~
val retrofit = Retrofit.Builder()
.baseUrl(RetrofitClient.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()
~~~
複製代碼
~~~
interface RequestService {
@GET("wxarticle/chapters/json")
fun getDatas() : Call<DataBean>
}
~~~
複製代碼
~~~
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的簡單請求方式,實際項目中基本上都會封裝以後再使用,也爲了提升代碼的可讀性,下降各部分的耦合性, 通俗點來講,只有各司其職才能把工做幹好嘛,接下來我們就圍繞着各司其職來一個一個實現github
接下來把上面的請求換成協程的方式來實現json
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)
}
}
~~~
複製代碼
~~~
interface RequestService {
@GET("wxarticle/chapters/json")
fun getDatas() : Deferred<DataBean>
}
~~~
複製代碼
由於咱們後續會使用到協程,因此這兒將Call換成了Deferred設計模式
~~~
GlobalScope.launch(Dispatchers.Main) {
withContext(Dispatchers.IO){
val dataBean = RetrofitClient.reqApi.getDatas().await()
}
//更新ui
}
~~~
複製代碼
上面用到了協程,這兒只講述他的應用了,具體的移步官方文檔進一步瞭解。 網絡請求在協程中,而且在IO調度單元,因此不用擔會阻塞主線程bash
上面也只是簡單的實現,只不過是換成了協程,在項目中,還能夠進一步封裝,方便使用前面也提到了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的代碼,用來發起網絡請求獲取數據架構
class ArticleRepository {
suspend fun getDatas(): DataBean {
return RetrofitClient.reqApi.getDatas().await()
}
}
複製代碼
在Activity中代碼以下async
private fun initData() {
model.getActicle().observe(this, Observer{
//獲取到數據
toolbar.setBackgroundColor(Color.RED)
})
}
複製代碼
結和了各位大佬們的意見,將使用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,可是我在引入該包的時候,會報錯,因爲最近比較忙暫時還沒來得急解決,後續問題有時間我也會繼續修改,還望各位大佬能幫忙指點
先看下以前的請求代碼
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
}
}
複製代碼
看了以前評論區大佬的建議,將Retrofit更新到了2.6.0,同時此次更新了viewModelScope 來管理協程, 項目換成了androidx,這樣一來看起來就很賞心悅目了,下面貼一點修改的地方,看一下
//運行在UI線程的協程
fun launchUI(block: suspend CoroutineScope.() -> Unit) = viewModelScope.launch {
try {
block()
} catch (e: Exception) {
error.value = e
} finally {
finally.value = 200
}
}
複製代碼
修改成viewModelScope後就不須要像以前同樣,在onCleared取消協程了,由於這一些列操做,他已經幫咱們完成了
interface RequestService {
@GET("wxarticle/chapters/json")
suspend fun getDatas() : ResponseData<List<Data>>
}
suspend fun getDatas(): ResponseData<List<Data>> = request {
RetrofitClient.reqApi.getDatas()
}
複製代碼
請求接口聲明能夠直接申明爲suspend 同時取消掉返回Deferred,請求方法中也能夠去掉await()由於retrofit2.6.0內部能夠支持協程,也就不須要咱們再處理了
上面只是描述了一些實現過程,具體使用還得參考demo,基本上能知足大部分的需求,要是感興趣的小夥伴,能夠下載demo參考,感受不錯的話,順手點個贊就很知足了。於所學不精,可能會有使用不當之處,但願各位大佬能指出不當的地方,深表感謝。