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

最初封装:
  1. //请求方法
  2. interface RequestListener {
  3. interface PostListener {
  4. @POST
  5. fun <T>call(@Url url: String, @Body t:Any) : Call<T>
  6. }
  7. }
  8. //封装请求
  9. class NetUtils private constructor(retrofit: Retrofit){
  10. private val mRetrofit = retrofit
  11. companion object {
  12. /**
  13. * 为支持多个单例,使用Map<url, NetUtils>记录所有已经创建的NetUtils
  14. */
  15. private val instanceMap = mutableMapOf<String, NetUtils>()
  16. fun getInstance(baseUrl: String) : NetUtils {
  17. StringUtils.isBlank(baseUrl)
  18. if (instanceMap.containsKey(baseUrl)) return instanceMap[baseUrl]!!
  19. val retrofit : Retrofit = Retrofit.Builder()
  20. .baseUrl(baseUrl)
  21. .addConverterFactory(GsonConverterFactory.create())
  22. .build()
  23. val netUtils = NetUtils(retrofit)
  24. instanceMap[baseUrl] = netUtils
  25. return netUtils
  26. }
  27. }
  28. fun <T>postData(url: String, data: Any, result: RequestResult<T>) {
  29. val api = mRetrofit.create(RequestListener.PostListener::class.java)
  30. val task : Call<T> = api.call(url, data)
  31. task.enqueue(object : Callback<T>{
  32. override fun onFailure(call: Call<T>, t: Throwable) {
  33. }
  34. override fun onResponse(call: Call<T>, response: Response<T>) {
  35. if (response.code() == 200) {
  36. result.onSucceded(response.body()!!)
  37. }
  38. }
  39. })
  40. }
  41. }
  42. //结果回调
  43. interface RequestResult<T> {
  44. fun onSucceded(result: T)
  45. fun onFailed(code: Int, msg: String)
  46. }

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

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

报错位置在代码第36行。

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

再次尝试

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

  1. 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帮我把代码改成了这样:

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

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

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

  1. //请求方法
  2. interface RequestListener {
  3. interface PostListener {
  4. @POST
  5. fun call(@Url url: String, @Body t:Any) : Call<ResponseBody>
  6. }
  7. }
  8. //封装请求
  9. class NetUtils private constructor(retrofit: Retrofit){
  10. protected val mRetrofit = retrofit
  11. companion object {
  12. /**
  13. * 为支持多个单例,使用Map<url, NetUtils>记录所有已经创建的NetUtils
  14. */
  15. private val instanceMap = mutableMapOf<String, NetUtils>()
  16. fun getInstance(baseUrl: String) : NetUtils {
  17. StringUtils.isBlank(baseUrl)
  18. if (instanceMap.containsKey(baseUrl)) return instanceMap[baseUrl]!!
  19. val retrofit : Retrofit = Retrofit.Builder()
  20. .baseUrl(baseUrl)
  21. .build()
  22. val netUtils = NetUtils(retrofit)
  23. instanceMap[baseUrl] = netUtils
  24. return netUtils
  25. }
  26. }
  27. inline fun <reified T>parse(data: String) = Gson().fromJson(data, T::class.java)
  28. inline fun <reified T>postData(url: String, data: Any, result: RequestResult<T>) {
  29. val api = mRetrofit.create(RequestListener.PostListener::class.java)
  30. val task : Call<ResponseBody> = api.call(url, data)
  31. task.enqueue(object : Callback<ResponseBody>{
  32. override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
  33. }
  34. override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
  35. if (response.code() == 200) {
  36. result.onSucceded(parse<T>(response.body()!!.string()))
  37. }
  38. }
  39. })
  40. }
  41. }
  42. //结果回调
  43. interface RequestResult<T> {
  44. fun onSucceded(result: T)
  45. fun onFailed(code: Int, msg: String)
  46. }

终于达到了目的。

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

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

  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. DRF使用JWT进行用户认证

    1. 首先需要安装第三方依赖包 pip install djangorestframework-jwt 2. 在Django的settings文件中 配置全局的JWT认证类 REST_FRAMEWOR ...

  2. macOS下将可执行文件索引位置增添到PATH中

    一.shell中可执行文件的两种执行方式 (1)绝对路径 比如,打开电脑上安装的python3,使用绝对路径方式打开为: /usr/local/bin/python3 (2)使用PATH 将pytho ...

  3. 计算距离2020年圣诞节还有x天x时x分x秒

    //计算两者相差毫秒数 //创建当前时间和圣诞节时间的Date对象 var d1=new Date(); var d2=new Date('2020/12/25'); //计算相差的毫秒 var d= ...

  4. Mybatis-初见

    目录 介绍 示例 搭建环境 创建一个模块 CURD 万能Map 配置解析 环境配置 environments 属性 properties 类型别名 typeAliases 其他配置 映射器 mappe ...

  5. 探索HashMap源码 一行一行解析 jdk1.7版本

    今天我们来说一说,HashMap的源码到底是个什么? 面试大厂这方面一定会经常问到,很重要的.以jdk1.7 为标准    先带着大家过一遍 是由数组.链表组成 , 数组的优点是:每个元素有对应下标, ...

  6. 第1篇-关于JVM运行时,开篇说的简单些

    开讲Java运行时,这一篇讲一些简单的内容.我们写的主类中的main()方法是如何被Java虚拟机调用到的?在Java类中的一些方法会被由C/C++编写的HotSpot虚拟机的C/C++函数调用,不过 ...

  7. C语言自学第一天

    直接上代码 1 #include<stdio.h> 2 #include<math.h> 3 /*定义符号常量(预处理)注:可为各种类型*/ 4 #define STUDY & ...

  8. Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile

    一.问题由来 下午的时候,电脑用得好好的,突然一下死机,么办法只能够重新启动.再次打开IDEA的时候,之前打开的所有的项目 信息都不在了,我重新打开项目,然后就出现问题,所有的类都报红了,这让我很是意 ...

  9. 百度地图开发-引入地图SDK并配置 02

    百度地图开发-引入地图SDK并配置 02 通过上一篇文章的介绍,基本了解百度地图的基本信息,接下来就让我们一起来实际在项目中操作,显示出地图. 01 引入地图SDK 首先需要新建一个空白的Androi ...

  10. ATM取款机优化需求的用例设计

    案例设计需求 有一个ATM取款系统,现对于取款功能进行了如何需求变更:碑只能取面额是100元(如取500,输出5张100元),现在功能修改为,可以取面额是10元.50元和100元的,其余功能不变,用户 ...