一种封装Retrofit的方法,可以自动解析Gson,回避Method return type must not include a type variable or wildcard: retrofit2.Call<T>的问题
封装目的:屏蔽底层实现,提供统一接口,并支持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)
}
终于达到了目的。
以上代码经过测试,可以运行。
相比于第一次封装,这次的改动是:
- 封装思路为直接返回String,不使用Retrofit的Gson解析器。(代码第5行)
- 由于不使用Retrofit的Gson解析器,所以需要手动使用Gson完成Json解析。在这个过程中,会使用泛型作为返回值类型,但由于泛型的不确定性,无法作为返回值,于是使用inline+reified的方式将返回值确定化,得到解析Json的函数parse()(代码32行)
- 由于封装后的函数postData()中含有inline函数,所以postData()也必须设置为inline函数,并且使用reified修饰泛型(代码34行)
- 由于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>的问题的更多相关文章
- 几种封装javaBean的方法
开发框架时,经常需要使用java对象(javaBean)的属性来封装程序的数据,封装javaBean的方法有很多,比如反射,内省,以及使用工具类.下面从反射开始介绍. 1.javaBean介绍: 简介 ...
- 关于四种语言中substring()方法参数值的解析
1.关于substring(a,b)Js var str="bdqn"; var result=str.substring(1,2); alert(result); 第一个参数:开 ...
- python自动解析301、302重定向链接
使用模块requests 方式代码如下: import requests url_string="http://******" r = requests.head(url_stri ...
- ABP+AdminLTE+Bootstrap Table权限管理系统第六节--abp控制器扩展及json封装以及6种处理时间格式化的方法
返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期 一,控制器AbpController 说完了Swagger ui 我们再来说一下abp对控制器的处理和json的封 ...
- 第九节: 利用RemoteScheduler实现Sheduler的远程控制 第八节: Quartz.Net五大构件之SimpleThreadPool及其四种配置方案 第六节: 六类Calander处理六种不同的时间场景 第五节: Quartz.Net五大构件之Trigger的四大触发类 第三节: Quartz.Net五大构件之Scheduler(创建、封装、基本方法等)和Job(创建、关联
第九节: 利用RemoteScheduler实现Sheduler的远程控制 一. RemoteScheduler远程控制 1. 背景: 在A服务器上部署了一个Scheduler,我们想在B服务器上 ...
- 总结Allegro元件封装(焊盘)制作方法[修整]
总结Allegro元件封装(焊盘)制作方法 在Allegro系统中,建立一个零件(Symbol)之前,必须先建立零件的管脚(Pin).元件封装大体上分两种,表贴和直插.针对不同的封装,需要制作不同的P ...
- javascript四种类型识别的方法
× 目录 [1]typeof [2]instanceof [3]constructor[4]toString 前面的话 javascript有复杂的类型系统,类型识别则是基本的功能.javascrip ...
- 干货:结合Scikit-learn介绍几种常用的特征选择方法
原文 http://dataunion.org/14072.html 主题 特征选择 scikit-learn 作者: Edwin Jarvis 特征选择(排序)对于数据科学家.机器学习从业者来说非 ...
- 结合Scikit-learn介绍几种常用的特征选择方法
特征选择(排序)对于数据科学家.机器学习从业者来说非常重要.好的特征选择能够提升模型的性能,更能帮助我们理解数据的特点.底层结构,这对进一步改善模型.算法都有着重要作用. 特征选择主要有两个功能: 减 ...
随机推荐
- StringIO和BytesIO的用法
数据读写有两种方式: 1.直接读写.案例 import openpyxl def write_excel(): f = openpyxl.Workbook() # 创建工作簿 # sheet1 = f ...
- OOP-Klass模型 简略
class Model { public static int a = 1; public int b; public Model(int b) { this.b = b; } } public st ...
- Hadoop 3.1.1 - Yarn - 使用 FPGA
在 Yarn 上使用 FPGA 前提 YARN 目前只支持通过 IntelFpgaOpenclPlugin 发布的 FPGA 资源 YARN NodeManager 所在的机器上必须预先安装供应商的驱 ...
- 【LeetCode】81. 搜索旋转排序数组 II
81. 搜索旋转排序数组 II 知识点:数组,二分查找: 题目描述 已知存在一个按非降序排列的整数数组 nums ,数组中的值不必互不相同. 在传递给函数之前,nums 在预先未知的某个下标 k(0 ...
- html拖动元素排序(插件版)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 < ...
- Git点赞82K!字节跳动保姆级Android学习指南,干货满满
这是一份全面详细的<Android学习指南>,如果你是新手,那么下面的内容可以帮助你找到学习的线路:如果你是老手,这篇文章列出的内容也可以帮助你查漏补缺.如果各位有什么其他的建议,欢迎留言 ...
- 阿里面试官:Android中binder机制的实现原理及过程?
Binder 是 Android 系统中非常重要的组成部分.Android 系统中的许多功能建立在 Binder 机制之上.在这篇文章中,我们会对 Android 中的 Binder 在系统架构中的作 ...
- 【LeetCode】738. 单调递增的数字
738. 单调递增的数字 知识点:字符串:贪心 题目描述 给定一个非负整数 N,找出小于或等于 N 的最大的整数,同时这个整数需要满足其各个位数上的数字是单调递增. (当且仅当每个相邻位数上的数字 x ...
- C++STL——vector类
vector容器 1.1 vector容器的基本概念 Array 是静态空间,一旦配置了就不能改变,要换大一点或者小一 点的空间,可以,一切琐碎得由自己来,首先配置一块新的空间,然后将旧空间的 数据搬 ...
- Spring Security项目的搭建以及Spring Security的BCrypt加密
.personSunflowerP { background: rgba(51, 153, 0, 0.66); border-bottom: 1px solid rgba(0, 102, 0, 1); ...