封装目的:屏蔽底层实现,提供统一接口,并支持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. CSS 格式 设置标签间距 和 input slot

    作者:张艳涛 日期:2020-07-29 CSS设置俩个标签的间距 及 Input Slots <div> <div class="m-b-20 ovf-hd"& ...

  2. winform制作简易屏幕保护工具

    效果如下: 具体实现代码如下: using System; using System.Collections.Generic; using System.ComponentModel; using S ...

  3. OpenFaaS实战之八:自制模板(maven+jdk8)

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  4. JavaScript实现,判断一个点是否在多边形内(简直nice,希望能帮到你)

    //定义点的结构体 function point(){ this.x=0; this.y=0; } //计算一个点是否在多边形里,参数:点,多边形数组 function PointInPoly(pt, ...

  5. 靶机DC-2 rbash绕过+git提权

    这个靶机和DC-1一样,一共5个flag.全部拿到通关. root@kali:/home/kali# nmap -sP 192.168.1.* 先扫一下靶机的IP地址,拿到靶机的地址为192.168. ...

  6. C++ //多态案例 -计算器类(普通写法 和 多态写法) //利用多态实现计算器 //多态好处: //1.组织结构清晰 //2.可读性强 //3.对于前期和后期扩展以及维护性高

    1 //多态案例 -计算器类(普通写法 和 多态写法) 2 3 #include <iostream> 4 #include <string> 5 using namespac ...

  7. Logback 快速入门 / 使用详解

    官方文档: http://logback.qos.ch/manual/index.html 一.简介 Java 开源日志框架,以继承改善 log4j 为目的而生,是 log4j 创始人 Ceki Gü ...

  8. openssl常用命令行汇总

    openssl常用命令行汇总 随机数 openssl rand -out rand.dat -base64 32 摘要 直接做摘要 openssl dgst -sha1 -out dgst.dat p ...

  9. MyBatis学习02(配置解析)

    配置解析 核心配置文件 mybatis-config.xml 系统核心配置文件 MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息. 能配置的内容如下: configur ...

  10. 自己动手实现Lua--实现TAILCALL指令

    最近在看<自己动手实现Lua-虚拟机.编译器和标准库>.这是本挺不错的书,通过学习此书能够对Lua语言有比较深刻的理解,此外还可以对如何自己实现一门脚本语言有直观的认识.对于想学习Lua的 ...