zoukankan      html  css  js  c++  java
  • 一种封装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关键字。

  • 相关阅读:
    由Photoshop高反差保留算法原理联想到的一些图像增强算法。
    一种具有细节保留功能的磨皮算法。
    图像抠图算法学习
    一年去雾算法研究的总结。
    关于《半反去雾算法》一文的四宗罪。
    自己编码使用去色、曲线、色阶算法实现照片怀旧特效。
    基于中值滤波或双边滤波方式的图像去雾效果的研讨。
    24位真彩色图像转换为16位高彩色图像的实现方法及效果改进
    对比度保留之彩色图像去色算法---基础算法也可以上档次。
    Tone Mapping算法系列一:基于Fast Bilateral Filtering 算法的 High-Dynamic Range(HDR) 图像显示技术。
  • 原文地址:https://www.cnblogs.com/lizhenxin/p/12579076.html
Copyright © 2011-2022 走看看