一种封装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)
}

终于达到了目的。

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

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

  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关键字。

原文地址:https://www.cnblogs.com/lizhenxin/p/12579076.html