封装目的:屏蔽底层实现,提供统一接口,并支持Gson自动转化

最初封装:
//请求方法
interface RequestListener {
interface PostListener {
@POST
fun <T>call(@Url url: String, @Body t:Any) : Call<T>
}
}
//封装请求
class NetUtils private constructor(retrofit: Retrofit){ private val mRetrofit = retrofit companion object {
/**
* 为支持多个单例,使用Map<url, NetUtils>记录所有已经创建的NetUtils
*/
private val instanceMap = mutableMapOf<String, NetUtils>() fun getInstance(baseUrl: String) : NetUtils {
StringUtils.isBlank(baseUrl) if (instanceMap.containsKey(baseUrl)) return instanceMap[baseUrl]!! val retrofit : Retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
val netUtils = NetUtils(retrofit)
instanceMap[baseUrl] = netUtils
return netUtils
}
} fun <T>postData(url: String, data: Any, result: RequestResult<T>) {
val api = mRetrofit.create(RequestListener.PostListener::class.java)
val task : Call<T> = api.call(url, data)
task.enqueue(object : Callback<T>{
override fun onFailure(call: Call<T>, t: Throwable) { } override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.code() == 200) {
result.onSucceded(response.body()!!)
}
}
})
}
}
//结果回调
interface RequestResult<T> {
fun onSucceded(result: T)
fun onFailed(code: Int, msg: String)
}

这种封装直接利用了Retrofit自带的Gson解析器,用泛型为返回结果的类型。但是在运行后报错:

java.lang.IllegalArgumentException: Method return type must not include a type variable or wildcard: retrofit2.Call<T>

报错位置在代码第36行。

很明显,报错的意思是api.call()方法的返回值必须是确定,但是我们将返回值设置为泛型,是不确定的。

再次尝试

为了解决这个问题,我用Any作为api.call()的返回值类型,在onResponse中将其强转为泛型。很明显也报错:

java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.example.retrofitdemo.PostWithParams
第三次尝试

这次的方案是:直接返回String,再手动使用Gson解析。

这就涉及到一个问题:使用Gson时同样不能用泛型作为返回值类型

ide会自动提示不能将"T"作为具体的参数类型。

我想到过包装类,但是之前尝试过,很麻烦,局限性很大,也未必成功。所以直接放弃这个方法。

本打算放弃自动解析Json的时候,天无绝人之路,我按了一下Ctrl+Enter自动修改,好巧不巧,ide帮我把代码改成了这样:

inline fun <reified T>parse(data: String) = Gson().fromJson(data, T::class.java)

而且还没有错误。。。。。。

于是顺着第三次封装的思路继续走下去:

//请求方法
interface RequestListener {
interface PostListener {
@POST
fun call(@Url url: String, @Body t:Any) : Call<ResponseBody>
}
}
//封装请求
class NetUtils private constructor(retrofit: Retrofit){
protected val mRetrofit = retrofit companion object {
/**
* 为支持多个单例,使用Map<url, NetUtils>记录所有已经创建的NetUtils
*/
private val instanceMap = mutableMapOf<String, NetUtils>() fun getInstance(baseUrl: String) : NetUtils {
StringUtils.isBlank(baseUrl) if (instanceMap.containsKey(baseUrl)) return instanceMap[baseUrl]!! val retrofit : Retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.build()
val netUtils = NetUtils(retrofit)
instanceMap[baseUrl] = netUtils
return netUtils
}
} inline fun <reified T>parse(data: String) = Gson().fromJson(data, T::class.java) inline fun <reified T>postData(url: String, data: Any, result: RequestResult<T>) {
val api = mRetrofit.create(RequestListener.PostListener::class.java)
val task : Call<ResponseBody> = api.call(url, data)
task.enqueue(object : Callback<ResponseBody>{
override fun onFailure(call: Call<ResponseBody>, t: Throwable) { } override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
if (response.code() == 200) {
result.onSucceded(parse<T>(response.body()!!.string()))
}
}
})
}
}
//结果回调
interface RequestResult<T> {
fun onSucceded(result: T)
fun onFailed(code: Int, msg: String)
}

终于达到了目的。

以上代码经过测试,可以运行。

相比于第一次封装,这次的改动是:

  1. 封装思路为直接返回String,不使用Retrofit的Gson解析器。(代码第5行)
  2. 由于不使用Retrofit的Gson解析器,所以需要手动使用Gson完成Json解析。在这个过程中,会使用泛型作为返回值类型,但由于泛型的不确定性,无法作为返回值,于是使用inline+reified的方式将返回值确定化,得到解析Json的函数parse()(代码32行)
  3. 由于封装后的函数postData()中含有inline函数,所以postData()也必须设置为inline函数,并且使用reified修饰泛型(代码34行)
  4. 由于inline的作用,NetUtil类的属性mRetrofit不能用private修饰,用public又范围太大,所以使用protect修饰,外部也就无法直接调用mRetrofit。得益于kotlin的机制,不用open修饰NetUtil类+private的构造器,NetUtil类无法被继承和实例化,也不存在子类滥用mRetrofit的现象,所以mRetrofit还是安全的

反思:

通过这次封装,发现了泛型的盲区,对泛型的理解还不够深刻,没有思考过擦除带来的后果,也就是Gson不支持泛型作为返回值类型的原因。同时对inline的也完全不理解,甚至没有见过reified关键字。

一种封装Retrofit的方法,可以自动解析Gson,回避Method return type must not include a type variable or wildcard: retrofit2.Call<T>的问题的更多相关文章

  1. 几种封装javaBean的方法

    开发框架时,经常需要使用java对象(javaBean)的属性来封装程序的数据,封装javaBean的方法有很多,比如反射,内省,以及使用工具类.下面从反射开始介绍. 1.javaBean介绍: 简介 ...

  2. 关于四种语言中substring()方法参数值的解析

    1.关于substring(a,b)Js var str="bdqn"; var result=str.substring(1,2); alert(result); 第一个参数:开 ...

  3. python自动解析301、302重定向链接

    使用模块requests 方式代码如下: import requests url_string="http://******" r = requests.head(url_stri ...

  4. ABP+AdminLTE+Bootstrap Table权限管理系统第六节--abp控制器扩展及json封装以及6种处理时间格式化的方法

    返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期 一,控制器AbpController 说完了Swagger ui 我们再来说一下abp对控制器的处理和json的封 ...

  5. 第九节: 利用RemoteScheduler实现Sheduler的远程控制 第八节: Quartz.Net五大构件之SimpleThreadPool及其四种配置方案 第六节: 六类Calander处理六种不同的时间场景 第五节: Quartz.Net五大构件之Trigger的四大触发类 第三节: Quartz.Net五大构件之Scheduler(创建、封装、基本方法等)和Job(创建、关联

    第九节: 利用RemoteScheduler实现Sheduler的远程控制   一. RemoteScheduler远程控制 1. 背景: 在A服务器上部署了一个Scheduler,我们想在B服务器上 ...

  6. 总结Allegro元件封装(焊盘)制作方法[修整]

    总结Allegro元件封装(焊盘)制作方法 在Allegro系统中,建立一个零件(Symbol)之前,必须先建立零件的管脚(Pin).元件封装大体上分两种,表贴和直插.针对不同的封装,需要制作不同的P ...

  7. javascript四种类型识别的方法

    × 目录 [1]typeof [2]instanceof [3]constructor[4]toString 前面的话 javascript有复杂的类型系统,类型识别则是基本的功能.javascrip ...

  8. 干货:结合Scikit-learn介绍几种常用的特征选择方法

    原文  http://dataunion.org/14072.html 主题 特征选择 scikit-learn 作者: Edwin Jarvis 特征选择(排序)对于数据科学家.机器学习从业者来说非 ...

  9. 结合Scikit-learn介绍几种常用的特征选择方法

    特征选择(排序)对于数据科学家.机器学习从业者来说非常重要.好的特征选择能够提升模型的性能,更能帮助我们理解数据的特点.底层结构,这对进一步改善模型.算法都有着重要作用. 特征选择主要有两个功能: 减 ...

随机推荐

  1. 轮播图 -- view, swiper

    效果图 制作步骤: 一.创建一个page 二.编写demo.wxml写界面元素 <!--miniprogram/pages/demo/demo.wxml--> <view class ...

  2. Mysql命令语句

    常用的管理命令 SHOW DATABASES;    //显示当前服务器下所有的数据库 USE 数据库名称;          //进入指定的数据 show tables;              ...

  3. 京东购物小程序 | Taro3 项目分包实践

    背景 京东购物小程序作为京东小程序业务流量的主要入口,承载着许多的活动和页面,而很多的活动在小程序开展的同时,也会在京东 APP 端进行同步的 H5 端页面的投放.这时候,一个相同的活动,需要同时开发 ...

  4. DOS命令--Windows操作系统之母

    DOS命令 DOS是什么 解释:Disk Operating System的缩写,意思是"磁盘操作系统" 系统:DOS就是人给机器下达命令的集合,是存储在操作系统中的命令集 基本用 ...

  5. Salesforce Integration 概览(四) Batch Data Synchronization(批量数据的同步)

    本篇参考:https://resources.docs.salesforce.com/sfdc/pdf/integration_patterns_and_practices.pdf 前两篇博客讲了一下 ...

  6. javascript 特殊字符 注意转义

  7. UE4点选源码分析

    在UE插件开发中,时常会用到场景预览窗口的功能,也经常会有点选场景里的物体而同步改变工具界面的需求,网上教程多为讲解如何打开一个预览界面.在最近的一次需求开发中,我粗读了关卡编辑器和蓝图编辑器的Vie ...

  8. (纯js)如何不刷新网页就能链接新的js文件

    如何不刷新网页就能链接新的js文件,其实在HTML语言中已经有相关的函数了,就是再添加一个<script src=.....></script>. 函数叫document.bo ...

  9. rancher v1.6.29部署K8s

    1. 前提:上一文中,已部署好单节点Rancher v1.6.29 2. 在Rancher中,添加环境模板 修改k8s设置参数 参数修改明细: Private Registry for Add-Ons ...

  10. 【笔记】scikit-learn中的PCA(真实数据集)

    sklearn中的PCA(真实的数据集) (在notebook中) 加载好需要的内容,手写数字数据集 import numpy as np import matplotlib.pyplot as pl ...