范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

Retrofit协程封装,如何优雅的去掉trycatch?

  作者:ChengTao
  Retrofit 2.6.0 版本后对  suspend    方法进行了支持,对使用 kotlin 的开发者来说简直是福音, 但是执行 suspend 方法的时候异常处理仍然是件繁琐的事情,必须显示的执行 try catch   , 或者使用 kotlin 自带的异常处理类 CoroutineExceptionHandler    进行处理,但是不管哪种方式, 代码都很挫,不够优雅。一、优雅的代码val service = retrofit.create(WanAndroidService::class.java).proxyRetrofit() // 执行 test service.test()     .onSuccess { println("execute test success ==> $it") }     .onFailure { println("execute test() failure ==> $it") }     // 执行 userInfo     .onFailureThen { service.userInfo() }     ?.onSuccess { println("execute userInfo success ==> $it") }     ?.onFailure { println("execute userInfo() failure ==> $it") }     // 执行 banner     ?.onFailureThen { service.banner() }     ?.onSuccess { println("execute banner() success ==> $it") }     ?.onFailure { println("execute banner() failure ==> $it") }
  没有任何的 try catch !!!
  运行结果如下: execute test() failure ==> Failure(code=-1, message=HTTP 404 Not Found) execute userInfo() failure ==> Failure(code=-1001, message=请先登录!) execute banner() success ==> [{"desc":"一起来做个App吧","id":10,"imagePath":"https://www.wanandroid.com/blogimgs/50c115c2-cf6c-4802-aa7b-a4334de444cd.png","isVisible":1,"order":1,"title":"一起来做个App吧","type":0,"url":"https://www.wanandroid.com/blog/show/2"},{"desc":"","id":6,"imagePath":"https://www.wanandroid.com/blogimgs/62c1bd68-b5f3-4a3c-a649-7ca8c7dfabe6.png","isVisible":1,"order":1,"title":"我们新增了一个常用导航Tab~","type":1,"url":"https://www.wanandroid.com/navi"},{"desc":"","id":20,"imagePath":"https://www.wanandroid.com/blogimgs/90c6cc12-742e-4c9f-b318-b912f163b8d0.png","isVisible":1,"order":2,"title":"flutter 中文社区 ","type":1,"url":"https://flutter.cn/"}]
  如果读到这里,你觉得这个代码不是你想象中的优雅的代码,那么你可以关掉当前网页了。 二、实现原理
  从上面的例子,主要起作用的是  proxyRetrofit    方法,其他的 onSuccess   , onFailure    和onFailureThen    不过是扩展方法而已,那我们就一起来看一下 proxyRetrofit    的实现原理。
  2.1 Retrofit 处理 suspend 方法原理
  在了解如何去掉  try catch    前我们需要先了解一下 retrofit 是如何处理 suspend    方法的, 具体实现原理可以参考:
  Retrofit 如何处理协程,建议一定要看一下!
  这里我们注重看下面这段代码: suspend fun  Call.await(): T {   return suspendCancellableCoroutine { continuation ->     continuation.invokeOnCancellation {       cancel()     }     enqueue(object : Callback {       override fun onResponse(call: Call, response: Response) {         if (response.isSuccessful) {           val body = response.body()           if (body == null) {             val invocation = call.request().tag(Invocation::class.java)!!             val method = invocation.method()             val e = KotlinNullPointerException("Response from " +                 method.declaringClass.name +                 "." +                 method.name +                 " was null but response body type was declared as non-null")             // 请求失败             continuation.resumeWithException(e)           } else {             // 请求成功             continuation.resume(body)           }         } else {           continuation.resumeWithException(HttpException(response))         }       }        override fun onFailure(call: Call, t: Throwable) {         // 请求失败         continuation.resumeWithException(t)       }     })   } }  // resume 方法会返回 success public inline fun  Continuation.resume(value: T): Unit =     resumeWith(Result.success(value))  // resumeWithException 方法会返回 failure public inline fun  Continuation.resumeWithException(exception: Throwable): Unit =     resumeWith(Result.failure(exception))
  从 retrofit 的源码可以得知,导致运行时会抛出异常的罪魁祸首是  resumeWithException    导致的,那么如果我们能拦截 resumeWithException    方法则可以避免异常的抛出。
  2.2 拦截 resumeWithException 原理/** * ThrowableResolver 的作用是在运行时遇到异常后如果处理异常 */ interface ThrowableResolver {     // 处理异常,并返回一个处理后的数据     fun resolve(throwable: Throwable): T }  /** * `proxyRetrofit` 方法主要作用是重新对接口进行动态代理,这样就可以在 * `InvocationHandler#invoke` 中对异常进行拦截,这样调用方就不用显示地调用 * `try catch` 了。 */ inline fun  T.proxyRetrofit(): T {     // 获取原先的 retrofit 的代理对象的的 InvocationHandler     // 这样我就可以继续使用 retrofit 的能力进行网络请求     val retrofitHandler = Proxy.getInvocationHandler(this)     return Proxy.newProxyInstance(         T::class.java.classLoader, arrayOf(T::class.java)     ) { proxy, method, args ->         // 判断当前是为 suspend 方法         method.takeIf { it.isSuspendMethod }?.getSuspendReturnType()             // 通过方法的返回值获取一个 ThrowableResolver 处理异常             ?.let { FactoryRegistry.getThrowableResolver(it) }             ?.let { resolver ->                 // 替换原始的 Contiuation 对象,这样我们就可以对异常进行拦截                 args.updateAt(                     args.lastIndex,                     FakeSuccessContinuationWrapper(                         args.last() as Continuation,                         resolver as ThrowableResolver                     )                 )             }         retrofitHandler.invoke(proxy, method, args)     } as T }  /** * 给 Method 添加的一个扩展属性,判断当前方法是不是 suspend 方法 */ val Method.isSuspendMethod: Boolean     get() = genericParameterTypes.lastOrNull()         ?.let { it as? ParameterizedType }?.rawType == Continuation::class.java  /** * 给 Method 添加的扩展方法,获取当前 suspend 方法的返回值类型 */ fun Method.getSuspendReturnType(): Type? {     return genericParameterTypes.lastOrNull()         ?.let { it as? ParameterizedType }?.actualTypeArguments?.firstOrNull()         ?.let { it as? WildcardType }?.lowerBounds?.firstOrNull() }  /** * Array 的扩展方法,更新指定 index 的值 */ fun Array.updateAt(index: Int, updated: Any?) {     this[index] = updated }  /** * Continuation 包装类,通过返回一个假的 Success(里面包含异常信息)拦截异常抛出 */ class FakeSuccessContinuationWrapper(     private val original: Continuation,     private val throwableResolver: ThrowableResolver, ) : Continuation {      override val context: CoroutineContext = original.context      override fun resumeWith(result: Result) {         // 判断 result 是否为 success         result.onSuccess {             // 如果为 success 直接返回             original.resumeWith(result)         }.onFailure {             // 如果为 failure, 返回一个假的 success, 这样协程判断当前为 success             // 就不会抛出异常了,这样我们就达到了拦截异常的作用             val fakeSuccessResult = throwableResolver.resolve(it)             original.resumeWith(Result.success(fakeSuccessResult))         }     } }
  到这里我们就揭开了拦截协程异常的原理,通过包装原始的  Continuation   , 在 result 为 failure 的时候,返回一个假的 success, 则协程就不会抛出异常。三、更多例子
  更详细的代码例子可以看 one 这个项目,更具体可以看 OneTest 这个测试用例。
  https://github.com/ParadiseHell/one
  https://github.com/ParadiseHell/one/blob/main/src/test/kotlin/org/paradisehell/one/OneTest.kt
  one 除了展示了如果拦截 try catch, 还展示了如何统一处理不同的 Response 相应, 转换成一样的数据结构,具体参考:
  使用 Retrofit 如何丢弃烦人的 BaseResponse 小结
  通过替换  suspend    方法的 Continuation    可以完成 try catch    的拦截,再给项目中的 BaseResponse    添加一些扩展方法(建议仿照 Kotlin 的 Result API),则可以让我们网络请求变得无比简洁。
  https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/
  但是这一切的前提都是 Retrofit 以及它动态代理的思想,所以 Retrofit yyds。
  感谢 WanAndroid API。
  https://www.wanandroid.com/blog/show/2

为什么iPhone坚持不用曲面屏?仔细分析发现,苹果真明智三星带动了曲面屏的发展,加上三星是最顶级的OLED屏幕供应商,很多国产手机厂商这些年也将旗舰机从直屏换到曲屏,比如小米11系列一加9ProOPPOFindX3系列等等,曲屏可以带来重要证据!最新科学发现再次支持新冠病毒自然起源新冠病毒源于自然界一说,再次迎来新的科学证据。9月1日,中国科学院遗传与发育生物学研究所研究员钱文峰在接受中青报中青网记者采访时表示,他所在的研究团队近日发现,新冠病毒在疫情暴发前全世界,超万亿美元市值公司总共有几家?只有少数上市公司的市值达到了1万亿美元或以上,准确地说,全世界只有6家。苹果和微软是迄今为止仅有的两家打破了两万亿市值这一里程碑的公司,2018年,苹果也是第一个超越1万亿美元里程优质大哥大上线!AGMM7三防手机体验或许能给父母们更多的选择大哥大手机,在手机圈中可是一代传说,在那个年代下可以说是最为流行的潮物了。如今时过境迁,二十一世纪大哥大AGMM7正式上线,乃是AGMM系列手机的最新力作,当然这款手机并不会真的像电视选购的6个技巧,看完就知道怎么选可以说,电视是主要的家居电器之一。现如今,每个家庭都会有电视,电视是人们的娱乐途径之一。随着科技日益发展,现在市场上的电视更新换代频繁,所出售的牌子和种类都是比较多的,所以,怎么才便宜国货不等于劣质!创维S7E电视4K显示效果惊艳,游戏迷有福了上周,作为魂系列的忠实玩家,小编可是乐坏了。为什么这么说呢?那是因为IGN公布了艾尔登法环的最新情报。很多人可能不知道这是什么,其实,它是一款以正统黑暗奇幻世界为舞台的动作RPG游声姿丨小米电视6OLED,极致亲民的爆款选择报告显示,仅2021上半年全球OLED电视出货量就达到了270万台,同比增长133。3。这一数据接近持平2020年全年市场,并超过此前任何一年OLED电视的全球全年销量。OLED电等等党再度胜利!供应链放风屏下镜头手机6个月后下放千元机随着手机外观和配置的愈发同质化,很多发布之前宣传的震天响的新机发布之后给人的感觉大多是不过如此嘛。因为没有什么太大的创新,所以消费者购买新机的意愿也就越来越低,而这可急坏了各大厂商有没有什么软件可以同时给视频添加水印或去水印?固乔剪辑助手,可以辅助一键剪辑视频的,快速消重,小白也会操作。很多小伙伴在工作中经常会遇到在使用视频的时候有水印,没有办法直接使用,需要使用一些小工具将水印去除,但是在视频非常多的洗碗机市场迎来爆发式增长美的洗碗机以创新科技加速中国普及最近,洗碗机成为厨电市场的热销爆款,频频霸占热搜榜。随着消费的升级及健康观念的深入人心,洗碗机销量迎来高速增长期,尤其是以美的为代表的中式国产品牌洗碗机,备受消费者青睐。日前,央视好用不贵多重安全设计,国民好物aigo魔方插座评测对大多数朋友来说,插座并不陌生,但传统的插座基本都是直排式,如果电器多了插座设计不合理,不仅容易插头打架,降低利用率。同时随着智能设备越来越多,老式的插座没有USBAUSBC接口,
全包围才是好49元让你的桌面品味提升小米这款超大的鼠标垫采用高级PU材质,不仅摸起来非常的舒服,同时防水抗湿,轻便易卷曲。这款产品在京东商城售价只要49元,这款产品最大的特点就是大,80厘米的宽度可以包围你的笔记本电我国互联网普及率达73。0网民人均上网时长每周28。5小时来源人民网原创稿人民网北京2月25日电(申佳平)据中国互联网络信息中心官网消息,第49次中国互联网络发展状况统计报告(以下简称报告)今日在京发布。报告显示,截至2021年12月,我值得捡漏的四款骁龙888手机,12GB256GB均已跌至新低价众所周知,手机更新换代的速度非常快,目前安卓阵营性能最强的芯片,不再是骁龙888,而是骁龙8Gen1,由于市面上出现了好几款骁龙8Gen1手机,这让诸多骁龙888手机的价格再次出现为算法推荐发展树立法治路标来源人民网原标题为算法推荐发展树立法治路标2022年3月1日实施的互联网信息服务算法推荐管理规定(以下简称规定),作为第一个正式出台的规制算法推荐运用的部门规章,既是互联网信息服务谷歌这一更新相较鸿蒙哪个更胜一筹?2021年10月5日谷歌发布了Android12操作系统,Android12通过引入设计语言MaterialYou,用户将能够通过自定义调色板和重新设计的小工具来完全个性化自己的手数据结构表的基本操作创建一个单链表includeiostreamincludevectorusingnamespacestdstructListNodeintvalstructListNodenext锤子科技5000万股权被解冻,公司股权冻结信息已全部清零,罗永浩回应3月1日,雷峰网消息,近日,北京锤子数码科技有限公司新增股权解冻信息,股权冻结详情显示,股权数额为5000万元人民币,被执行人为北京锤子数码科技有限公司,冻结股权标的企业成都野望数大数据侵入了我们的生活?互联网上,只要点开看过,类似的内容就会不断推送网购时,只要想买,相关促销信息就会源源不断涌来大数据时代,算法技术广泛应用在各个终端上,总能投其所好,为用户提供个性化便捷体验,但与此北京市消协点名飞猪饿了么涉嫌大数据杀熟3月1日,北京市消协通报了互联网消费大数据杀熟问题调查结果。北京青年报记者了解到,部分平台存在新老用户账号同时购买同一商品或服务实际成交价不同现象。北京市消协体验调查选取了16个消东数西算带动互联网巨头绿色内卷!这些技术成风口数字技术是助力双碳目标实现的重要一环,但数字化转型也加速了信息通讯产业的能源需求和碳排放增长。日前,一项超级工程的落地实施,给被称为算力基础设施中的电老虎互联网数据中心(IDC)的京东副总裁蔡磊43岁患渐冻症,砸千万研制特效药,如今仍在抗争蔡先生,检查报告出来了,你得的,是渐冻症。什么?出于本能问出这句话的蔡磊,还是觉得难以置信,是,和霍金先生一样的那个病,渐冻症?可是我才四十岁。经过我们对六次检验结果和化验单的反复