封装目的:屏蔽底层实现,提供统一接口,并支持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. 我的第一个HarmonyOS 应用

    第一步:去开发者官网下载IDE:https://developer.harmonyos.com/cn/develop   并根据文档安装 DevEco Studio 第二步.启动IDE并创建自己的第一 ...

  2. 选择排序(selection_sort)——Python实现

      # 选择排序 # 作用:对给出的n个顺序不定的数进行排序 # 输入:任意数组A # 输出:按顺序排列的数组A # 时间复杂度 (n(n-1))/2 # 选择排序 # 第一趟:选择第一个元素,依次与 ...

  3. (opencv5)轮廓检测函数

    (opencv5)轮廓检测函数 contours, hierarchy = cv2.findContours(img, mode, method,[offset) 注意 : 1.输入为二值图像,黑色为 ...

  4. css--实现一个文字少时居中,文字换行时居左的样式

    前言 最近群里的小伙伴去面试,遇到这样一个问题,面试官问:"用 css 对一行文字进行布局,当文字不够换行的时候,这行文字要居中显示,当文字出现换行的时候,这行文字要靠左显示.", ...

  5. 防止因提供的sql脚本有问题导致版本bvt失败技巧

    发版本时,可能会由于测试库和开发库表结构不一样而导致数据库脚本在测试那边执行时出错,导致版本BVT失败,以下技巧可解决此问题. 步骤:备份目标库,在备份库中执行将要提供的sql脚本看有无问题,若没问题 ...

  6. Hadoop 3.1.1 - Yarn 服务 - 快速开始

    快速开始 本文描述了如何用 Yarn 服务框架在 Yarn 上部署服务. 配置和启动 HDFS 和 Yarn 组件 首先启动 HDFS 和 Yarn 的各个组件.为启用 Yarn 服务框架,添加以下参 ...

  7. "virtualbox for mac安装器遇到了一个错误, 导致安装失败. 请联系软件制造商以获得帮助"的解决办法

    virtualbox下载地址: https://www.virtualbox.org/wiki/Downloads 原因:"Mac OS 10.13.4 会阻止外部内核扩展的安装" ...

  8. Linux-shell编程经验记录

    Linux-shell编程经验总结 1.接收用户输入 #读取用户输入并且将输入保存到input变量中 read -p "请输入:" input #也可以先输出信息再进行读取,这里的 ...

  9. 腾讯开源:Kotlin 高性能特效动画组件!

    先看一下效果展示: 1. VAP VAP(Video Animation Player)是企鹅电竞开发,用于播放酷炫动画的实现方案. 相比Webp, Apng动图方案,具有高压缩率(素材更小).硬件解 ...

  10. Spring IOC容器核心流程源码分析

    简单介绍 Spring IOC的核心方法就在于refresh方法,这个方法里面完成了Spring的初始化.准备bean.实例化bean和扩展功能的实现. 这个方法的作用是什么? 它是如何完成这些功能的 ...