• 售前

  • 售后

热门帖子
入门百科

Kotlin 协程+Retrofit 最优雅的网络请求使用

[复制链接]
缥缈的绽放山 显示全部楼层 发表于 2022-1-13 10:57:31 |阅读模式 打印 上一主题 下一主题
1.简介

Retrofit对协程的支持非常的简陋。在kotlin中使用不符合kotlin的优雅
  1. interface TestServer {
  2.     @GET("banner/json")
  3.     suspend fun banner(): ApiResponse<List<Banner>>
  4. }
  5. //实现并行捕获异常的网络请求
  6. fun oldBanner(){
  7.         viewModelScope.launch {
  8.             //传统模式使用retrofit需要try catch
  9.             val bannerAsync1 = async {
  10.                 var result : ApiResponse<List<Banner>>? = null
  11.                 kotlin.runCatching {
  12.                    service.banner()
  13.                 }.onFailure {
  14.                     Log.e("banner",it.toString())
  15.                 }.onSuccess {
  16.                     result = it
  17.                 }
  18.                 result
  19.             }
  20.             val bannerAsync2 = async {
  21.                 var result : ApiResponse<List<Banner>>? = null
  22.                 kotlin.runCatching {
  23.                     service.banner()
  24.                 }.onFailure {
  25.                     Log.e("banner",it.toString())
  26.                 }.onSuccess {
  27.                     result = it
  28.                 }
  29.                 result
  30.             }
  31.             bannerAsync1.await()
  32.             bannerAsync2.await()
  33.         }
  34.     }
复制代码
一层嵌套一层,属实无法忍受。kotlin应该一行代码解决问题,才符合kotlin的优雅
使用本框架后
  1. interface TestServer {
  2.     @GET("banner/json")
  3.     suspend fun awaitBanner(): Await<List<Banner>>
  4. }
  5.    //实现并行捕获异常的网络请求
  6. fun parallel(){
  7.      viewModelScope.launch {
  8.      val awaitBanner1 = service.awaitBanner().tryAsync(this)
  9.      val awaitBanner2 = service.awaitBanner().tryAsync(this)
  10.       //两个接口一起调用
  11.       awaitBanner1.await()
  12.       awaitBanner2.await()
  13.    }
  14. }
复制代码
2.源码地址

GitHub
3.查看Retrofit源码

先看Retrofit create方法
  1. public <T> T create(final Class<T> service) {
  2.     validateServiceInterface(service);
  3.     return (T)
  4.         Proxy.newProxyInstance(
  5.             service.getClassLoader(),
  6.             new Class<?>[] {service},
  7.             new InvocationHandler() {
  8.               private final Platform platform = Platform.get();
  9.               private final Object[] emptyArgs = new Object[0];
  10.               @Override
  11.               public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
  12.                   throws Throwable {
  13.                 // If the method is a method from Object then defer to normal invocation.
  14.                 if (method.getDeclaringClass() == Object.class) {
  15.                   return method.invoke(this, args);
  16.                 }
  17.                 args = args != null ? args : emptyArgs;
  18.                 return platform.isDefaultMethod(method)
  19.                     ? platform.invokeDefaultMethod(method, service, proxy, args)
  20.                     : loadServiceMethod(method).invoke(args);//具体调用
  21.               }
  22.             });
  23.   }
复制代码
loadServiceMethod(method).invoke(args)进入这个方法看具体调用


我们查看suspenForResponse中的adapt
  1. @Override
  2.     protected Object adapt(Call<ResponseT> call, Object[] args) {
  3.       call = callAdapter.adapt(call);//如果用户不设置callAdapterFactory就使用DefaultCallAdapterFactory
  4.       //noinspection unchecked Checked by reflection inside RequestFactory.
  5.       Continuation<Response<ResponseT>> continuation =
  6.           (Continuation<Response<ResponseT>>) args[args.length - 1];
  7.       // See SuspendForBody for explanation about this try/catch.
  8.       try {
  9.         return KotlinExtensions.awaitResponse(call, continuation);
  10.       } catch (Exception e) {
  11.         return KotlinExtensions.suspendAndThrow(e, continuation);
  12.       }
  13.     }
  14.   }
复制代码
后面直接交给协程去调用call。具体的okhttp调用在DefaultCallAdapterFactory。或者用户自定义的callAdapterFactory中
因此我们这边可以自定义CallAdapterFactory在调用后不进行网络请求的访问,在用户调用具体方法时候再进行网络请求访问。
4.自定义CallAdapterFactory

Retrofit在调用后直接进行了网络请求,因此很不好操作。我们把网络请求的控制权放在我们手里,就能随意操作。
  1. class ApiResultCallAdapterFactory : CallAdapter.Factory() {
  2.     override fun get(returnType: Type, annotations: Array<Annotation>, retrofit: Retrofit): CallAdapter<*, *>? {
  3.         //检查returnType是否是Call<T>类型的
  4.         if (getRawType(returnType) != Call::class.java) return null
  5.         check(returnType is ParameterizedType) { "$returnType must be parameterized. Raw types are not supported" }
  6.         //取出Call<T> 里的T,检查是否是Await<T>
  7.         val apiResultType = getParameterUpperBound(0, returnType)
  8.         // 如果不是 Await 则不由本 CallAdapter.Factory 处理 兼容正常模式
  9.         if (getRawType(apiResultType) != Await::class.java) return null
  10.         check(apiResultType is ParameterizedType) { "$apiResultType must be parameterized. Raw types are not supported" }
  11.         //取出Await<T>中的T 也就是API返回数据对应的数据类型
  12. //        val dataType = getParameterUpperBound(0, apiResultType)
  13.         return ApiResultCallAdapter<Any>(apiResultType)
  14.     }
  15. }
  16. class ApiResultCallAdapter<T>(private val type: Type) : CallAdapter<T, Call<Await<T>>> {
  17.     override fun responseType(): Type = type
  18.     override fun adapt(call: Call<T>): Call<Await<T>> {
  19.         return ApiResultCall(call)
  20.     }
  21. }
  22. class ApiResultCall<T>(private val delegate: Call<T>) : Call<Await<T>> {
  23.     /**
  24.      * 该方法会被Retrofit处理suspend方法的代码调用,并传进来一个callback,如果你回调了callback.onResponse,那么suspend方法就会成功返回
  25.      * 如果你回调了callback.onFailure那么suspend方法就会抛异常
  26.      *
  27.      * 所以我们这里的实现是回调callback.onResponse,将okhttp的call delegate
  28.      */
  29.     override fun enqueue(callback: Callback<Await<T>>) {
  30.         //将okhttp call放入AwaitImpl直接返回,不做网络请求。在调用AwaitImpl的await时才真正开始网络请求
  31.         callback.onResponse(this@ApiResultCall, Response.success(delegate.toResponse()))
  32.     }
  33. }
  34. internal class AwaitImpl<T>(
  35.     private val call : Call<T>,
  36. ) : Await<T> {
  37.     override suspend fun await(): T {
  38.         return try {
  39.             call.await()
  40.         } catch (t: Throwable) {
  41.             throw t
  42.         }
  43.     }
  44. }
复制代码
通过上面自定义callAdapter后,我们延迟了网络请求,在调用Retrofit后并不会请求网络,只会将网络请求所需要的call的放入await中。
  1.    @GET("banner/json")
  2.     suspend fun awaitBanner(): Await<List<Banner>>
复制代码
我们拿到的Await并没有做网络请求。在这个实体类中包含了okHttp的call。
这时候我们可以定义如下方法就能捕获异常
  1. suspend fun <T> Await<T>.tryAsync(
  2.     scope: CoroutineScope,
  3.     onCatch: ((Throwable) -> Unit)? = null,
  4.     context: CoroutineContext = SupervisorJob(scope.coroutineContext[Job]),
  5.     start: CoroutineStart = CoroutineStart.DEFAULT
  6. ): Deferred<T?> = scope.async(context, start) {
  7.     try {
  8.         await()
  9.     } catch (e: Throwable) {
  10.         onCatch?.invoke(e)
  11.         null
  12.     }
  13. }
复制代码
同样并行捕获异常的请求,就可以通过如下方式调用,优雅简洁了很多
  1.    /**
  2.      * 并行 async
  3.      */
  4.     fun parallel(){
  5.         viewModelScope.launch {
  6.             val awaitBanner1 = service.awaitBanner().tryAsync(this)
  7.             val awaitBanner2 = service.awaitBanner().tryAsync(this)
  8.             //两个接口一起调用
  9.             awaitBanner1.await()
  10.             awaitBanner2.await()
  11.         }
  12.     }
复制代码
这时候我们发现网络请求成功了,解析数据失败。因为我们在数据外面套了一层await。肯定无法解析成功。
本着哪里错误解决哪里的思路,我们自定义Gson解析
5.自定义Gson解析

  1. class GsonConverterFactory private constructor(private var responseCz : Class<*>,var responseConverter : GsonResponseBodyConverter, private val gson: Gson) : Converter.Factory() {
  2.     override fun responseBodyConverter(
  3.         type: Type, annotations: Array<Annotation>,
  4.         retrofit: Retrofit
  5.     ): Converter<ResponseBody, *> {
  6.         var adapter : TypeAdapter<*>? = null
  7.         //检查是否是Await<T>
  8.         if (Utils.getRawType(type) == Await::class.java && type is ParameterizedType){
  9.             //取出Await<T>中的T
  10.             val awaitType =  Utils.getParameterUpperBound(0, type)
  11.             if(awaitType != null){
  12.                 adapter = gson.getAdapter(TypeToken.get(ParameterizedTypeImpl[responseCz,awaitType]))
  13.             }
  14.         }
  15.         //不是awiat正常解析,兼容正常模式
  16.         if(adapter == null){
  17.             adapter= gson.getAdapter(TypeToken.get(ParameterizedTypeImpl[responseCz,type]))
  18.         }
  19.         return responseConverter.init(gson, adapter!!)
  20.     }
  21. }
  22. class MyGsonResponseBodyConverter : GsonResponseBodyConverter() {
  23.     override fun convert(value: ResponseBody): Any {
  24.         val jsonReader = gson.newJsonReader(value.charStream())
  25.         val data = adapter.read(jsonReader) as ApiResponse<*>
  26.         val t = data.data
  27.         val listData = t as? ApiPagerResponse<*>
  28.         if (listData != null) {
  29.             //如果返回值值列表封装类,且是第一页并且空数据 那么给空异常 让界面显示空
  30.             if (listData.isRefresh() && listData.isEmpty()) {
  31.                 throw ParseException(NetConstant.EMPTY_CODE, data.errorMsg)
  32.             }
  33.         }
  34.         // errCode 不等于 SUCCESS_CODE,抛出异常
  35.         if (data.errorCode != NetConstant.SUCCESS_CODE) {
  36.             throw ParseException(data.errorCode, data.errorMsg)
  37.         }
  38.         return t!!
  39.     }
  40. }
复制代码
6.本框架使用

添加依赖

  1. implementation "io.github.cnoke.ktnet:api:?"
复制代码
写一个网络请求数据基类
  1. open class ApiResponse<T>(
  2.     var data: T? = null,
  3.     var errorCode: String = "",
  4.     var errorMsg: String = ""
  5. )
复制代码
实现com.cnoke.net.factory.GsonResponseBodyConverter
  1. class MyGsonResponseBodyConverter : GsonResponseBodyConverter() {
  2.     override fun convert(value: ResponseBody): Any {
  3.         val jsonReader = gson.newJsonReader(value.charStream())
  4.         val data = adapter.read(jsonReader) as ApiResponse<*>
  5.         val t = data.data
  6.         val listData = t as? ApiPagerResponse<*>
  7.         if (listData != null) {
  8.             //如果返回值值列表封装类,且是第一页并且空数据 那么给空异常 让界面显示空
  9.             if (listData.isRefresh() && listData.isEmpty()) {
  10.                 throw ParseException(NetConstant.EMPTY_CODE, data.errorMsg)
  11.             }
  12.         }
  13.         // errCode 不等于 SUCCESS_CODE,抛出异常
  14.         if (data.errorCode != NetConstant.SUCCESS_CODE) {
  15.             throw ParseException(data.errorCode, data.errorMsg)
  16.         }
  17.         return t!!
  18.     }
  19. }
复制代码
进行网络请求
  1. interface TestServer {    @GET("banner/json")
  2.     suspend fun awaitBanner(): Await<List<Banner>>}val okHttpClient = OkHttpClient.Builder()            .addInterceptor(HeadInterceptor())            .addInterceptor(LogInterceptor())            .build()val retrofit = Retrofit.Builder()            .client(okHttpClient)            .baseUrl("https://www.wanandroid.com/")            .addCallAdapterFactory(ApiResultCallAdapterFactory())            .addConverterFactory(GsonConverterFactory.create(ApiResponse::class.java,MyGsonResponseBodyConverter()))            .build()val service: TestServer = retrofit.create(TestServer::class.java)lifecycleScope.launch {       val banner = service.awaitBanner().await()}
复制代码
异步请求同步请求,异常捕获参考如下try开头的会捕获异常,非try开头不会捕获。
  1. fun banner(){
  2.     lifecycleScope.launch {
  3.         //单独处理异常 tryAwait会处理异常,如果异常返回空
  4.         val awaitBanner = service.awaitBanner().tryAwait()
  5.         awaitBanner?.let {
  6.             for(banner in it){
  7.                 Log.e("awaitBanner",banner.title)
  8.             }
  9.         }
  10.         /**
  11.          * 不处理异常 异常会直接抛出,统一处理
  12.          */
  13.         val awaitBannerError = service.awaitBanner().await()
  14.     }
  15. }
  16. /**
  17. * 串行 await
  18. */
  19. fun serial(){
  20.     lifecycleScope.launch {
  21.         //先调用第一个接口await
  22.         val awaitBanner1 = service.awaitBanner().await()
  23.         //第一个接口完成后调用第二个接口
  24.         val awaitBanner2 = service.awaitBanner().await()
  25.     }
  26. }
  27. /**
  28. * 并行 async
  29. */
  30. fun parallel(){
  31.     lifecycleScope.launch {
  32.         val awaitBanner1 = service.awaitBanner().async(this)
  33.         val awaitBanner2 = service.awaitBanner().async(this)
  34.         //两个接口一起调用
  35.         awaitBanner1.await()
  36.         awaitBanner2.await()
  37.     }
  38. }
复制代码
来源:https://blog.caogenba.net/nufuli123/article/details/122463127
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作