超简单-用协程简化你的网络请求吧,兼容你的老项目和旧的网络请求方式
前言
在Kotlin協程(后簡稱協程)出來之后,顛覆了我們很多工具類的封裝方式,大大簡化了我們很多api的調用,并且使異步操作邏輯更清晰了
其中一個很標志性的地方就屬網絡請求了,以前的網絡請求方式聲明很麻煩,請求和響應也很麻煩,總結一句話就是啰嗦且易出錯
ps:最終的使用方式示例:
使用網絡請求的進化過程:
1.從一開始的HttpURLConnection的一把梭,直接一個請求寫一串代碼(代碼太多就不寫了)
2.到后續使用三方封裝的網絡框架,如XUtils,Volly,OkHttp等,這些雖然也簡化了操作,但也是很復雜,比如封裝了兩層的OkHttp可能還需要以下方式聲明和調用:
然后在一個或多個回調中處理返回的數據并判斷是否是請求成功,解析成響應的數據并操作
3.后來Retrofit出現,解救了苦于網絡請求太啰嗦的萬千開發們,其使用動態代理加良好的封裝,使網絡請求聲明和請求都變得更簡單了
ps:Retrofit如何使用動態代理參考:模仿Retrofit封裝一個使用更簡單的網絡請求框架_lt的博客-CSDN博客_retrofit封裝網絡請求
pps:進一步簡化Retrofit的聲明參考:更易于使用的Retrofit(不用寫注解)_lt的博客-CSDN博客
聲明如下(其會在調用的時候自動解析成一個post請求的Call對象):
使用如下:
可以發現雖然聲明簡單了很多,但使用還是比較麻煩
這時候就輪到協程登場了
正文
如果是使用Retrofit配合協程簡化網絡請求參考我以前寫的文章第二條(封裝網絡請求):Kotlin協程在項目中的實際應用_lt的博客-CSDN博客_kotlin協程使用
否則可以看下面的:
接下來就是重頭戲:用協程兼容你的老項目和舊的網絡請求方式,并且請求方式就像這張圖一樣
首先我們可以觀察一下我們的舊方式的請求,發現可能是有一個統一的回調,將服務端返回的String返回來,可能類似情況1:
?也可能使用OkHttp,類似情況2:
也可能類似情況3:
?可以觀察到,他們都是基于回調,而協程可以將回調轉成同步的掛起函數,所以我們將第三種方式(其他情況也類似),改造代碼如下:
interface BaseHttp {//網絡請求成功后調用該方法fun onHttpSuccess(data: String?)//網絡請求失敗后調用該方法fun onHttpFailed(message: String?) }/*** creator: lt lt.dygzs@qq.com* effect : 使用協程封裝基于回調的網絡請求* warning:*/ abstract class CoroutineHttp<T : Any> : BaseHttp {private var continuation: CancellableContinuation<Result>? = null//協程對象,如果getX調用慢于回調用private var result: Result? = null//響應結果,如果getX先與回調用//網絡請求成功后調用該方法 //2*********************************override fun onHttpSuccess(data: String?) {//保證線程安全handler.post {val continuation = continuationif (continuation == null) {result = Result(data, true)return@post}continuation.resume(Result(data, true))}}//網絡請求失敗后調用該方法override fun onHttpFailed(message: String?) {handler.post {val continuation = continuationif (continuation == null) {result = Result(message, false)return@post}continuation.resume(Result(message, false))}}/*** 檢查協程網絡請求的返回值和當前狀態是否符合要求* 如果不符合要求,會回調失敗的接口,并返回null,不會取消當前協程* [errorListener]msg:服務端返回的消息或異常信息,如果不傳使用默認失敗策略(即彈toast)*/ //1*********************************suspend fun getOrNull(errorListener: (msg: String?) -> Unit = HttpErrorListener): T? {//保證線程安全val json = withContext(Dispatchers.Main) {val result = suspendCancellableCoroutine<Result> { continuation ->val result = resultif (result != null) { //4*********************************continuation.resume(result)return@suspendCancellableCoroutine}this@CoroutineHttp.continuation = continuation} //3*********************************return@withContext if (result.isSuccess) result.data else {errorListener(result.data)null}}return withContext(Dispatchers.Default) {Gson().fromJson(json,this@CoroutineHttp.getSuperClassTypes().first())}}/*** 檢查協程網絡請求的返回值和當前狀態是否符合要求* 如果不符合要求,會回調失敗的接口,并取消當前協程* [errorListener]msg:服務端返回的消息或異常信息,如果不傳使用默認失敗策略(即彈toast)*/suspend fun get(errorListener: (String?) -> Unit = HttpErrorListener): T =getOrNull(errorListener) ?: cancelAndThrow()//獲取協程數據,并自動顯示和隱藏彈窗,并自動轉換成響應類型,如果請求失敗或轉換失敗則返回nullsuspend fun getOrNullWithDialog(errorListener: (String?) -> Unit = HttpErrorListener): T? {val baseActive = coroutineContext[CoroutineElementWithBaseActive]?.baseActivebaseActive?.showWaitDialog()try {return getOrNull(errorListener)} finally {baseActive?.dismissWaitDialog()}}// 獲取協程數據,并自動顯示和隱藏彈窗,并自動轉換成響應類型,如果請求失敗或轉換失敗則取消協程suspend fun getWithDialog(errorListener: (String?) -> Unit = HttpErrorListener): T =getOrNullWithDialog(errorListener) ?: cancelAndThrow()class Result(val data: String?, val isSuccess: Boolean)private object HttpErrorListener : (String?) -> Unit {override fun invoke(p1: String?) {p1.showToast()}}/*** 取消并拋出異常*/suspend fun cancelAndThrow(): Nothing = coroutineContext.job.cancelAndThrow()/*** 獲取super的泛型的type列表*/ fun Any.getSuperClassTypes(): Array<Type> =this::class.java.genericSuperclass.asT<ParameterizedType>().actualTypeArguments }private val handler = Handler(Looper.getMainLooper())原理大致就是將協程內部的回調給保存下來(掛起),然后在適當的時間調用協程的resume使協程恢復執行
可以看代碼標識1處,其他的getxxx方法都會走到這個getOrNull方法中,其先調用suspendCancellableCoroutine方法將協程掛起并拿到內部的回調
然后再看代碼標識2處,網絡請求回調成功后會走這個onHttpSuccess方法,然后我們判斷是否已經掛起了協程(注冊了協程的回調):
? ? 1.如果掛起了,就調用resume恢復協程的運行,此時代碼會執行到標識3處,且將result傳了過去,然后后續就執行解析json并判斷是否成功等耦合項目的邏輯
? ? 2.如果沒有掛起,就將數據保存在類中,然后會在調用getxxx方法時檢測到有數據并直接恢復協程(標識4處)
然后比如網絡請求這樣聲明:
最后我們就可以使用協程請求網絡了(紅框的位置相當于getWithDialog方法的成功回調):
?也可以在方法后面傳入回調處理請求失敗的情況(紅框位置是成功的回調,黃框位置是失敗的回調):
?使用協程和gson解析需要導入以下的依賴:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'implementation 'com.google.code.gson:gson:2.8.7'擴展
1.CoroutineHttp為什么要聲明為抽象類?
因為jvm的泛型擦除,如果你不創建出新的Class,就無法獲取他設置的泛型的Type,而使用抽象類,你每new一個匿名內部類都會創建出一個Class,所以可以通過反射獲取其聲明的泛型的Type
當然open class也可以寫出匿名內部類,但是沒有了硬性規定容易出現遺漏導致出現bug
2.mainScope是什么
mainScope是一個自定義的協程作用域(CoroutineScope),創建它可以參考這一篇Kotlin-如何創建一個好用的協程作用域_lt的博客-CSDN博客
一般在activity中需要在onCreate中創建,在onDestroy中調用cancel()來取消它所有啟動的協程
如果在fragment中就在onCreate中創建,在onDestroyView中調用cancel()
如果有疑問可以評論區留言
end
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的超简单-用协程简化你的网络请求吧,兼容你的老项目和旧的网络请求方式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Compose 手势事件:防止重复点击,
- 下一篇: RecyclerView复杂适配器的终极