封装目的:屏蔽底层实现,提供统一接口,并支持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. 关于 .NET 与 JAVA 在 JIT 编译上的一些差异

    最近因为公司的一些原因,我也开始学习一些 JAVA 的知识.虽然我一直是以 .NET 语言为主的程序员,但是我并不排斥任何其它语言.在此并不讨论 JAVA .NET 的好坏,仅仅是对 .NET 跟 J ...

  2. At 、Crontabl定时任务

    之前笔者是在本地写的博客,然后用 windows 定时任务启动写的脚本上传到 Github 上,现在又遇到了 Linux 上的定时任务,项目还要用到 Quartz 定时任务框架 1. 一次性定时任务 ...

  3. 构建后端第3篇之---springb @Alias注解使用

    write by 张艳涛 in 202210210,after today i will  write aritles by english,because english is so diffent ...

  4. []*T *[]T *[]*T 傻傻分不清楚

    前言 作为一个 Go 语言新手,看到一切"诡异"的代码都会感到好奇:比如我最近看到的几个方法:伪代码如下: func FindA() ([]*T,error) { } func F ...

  5. 云平台制作(1)-OPC Client取数模块的制作

    近来由于工程需要,基于OPC DA 2.0搭建通用的取数模块,与远程webscoket服务端连接,并传输数据.在网上找了些资料,修改相应网友公开的源代码,基本达到要求,特供大家参考. 1.实体类 us ...

  6. C++ //构造函数调用规则 //1.创建一个类,C++编译器会给每个类添加至少3个函数 //默认构造(空实现) //析构函数(空实现) //拷贝函数(值拷贝) //2.如果我们写了有参构造函数 编译器就不会提供默认构造函数 但是会提供拷贝构造函数 //3.如果我们写了拷贝函数 编译器就不再提供 默认 有参 构造函数

    //构造函数调用规则 #include <iostream> using namespace std; //1.创建一个类,C++编译器会给每个类添加至少3个函数 //默认构造(空实现) ...

  7. Thunder DLL Hijacking

    简记 原理基础啥的俺也不写了 1.寻找DLL 生成恶意dll文件 拿calc测试 2.放入 3.打开

  8. Android开发没有一技之长就废了吗?

    写在前面的话 不知你发现没有,人到中年之后,时间流逝的速度仿佛越来越快? 还记得毕业那会儿,我们怀着新鲜和好奇踏进了职场,那个时候每天都是满满的挑战和需要学习的东西,感觉时间过得真慢啊: 不知道从什么 ...

  9. MySQL Replication Thread States

    1.主节点线程状态(Replication Master Thread States): Finished reading one binlog; switching to next binlog 线 ...

  10. SpringCloud-技术专区-从源码层面让你认识Feign工作流程和运作机制

    Feign工作流程源码解析 什么是feign:一款基于注解和动态代理的声明式restful http客户端. 原理 Feign发送请求实现原理 微服务启动类上标记@EnableFeignClients ...