一種封裝Retrofit的方法,可以自動解析Gson,回避Method return type must not include a type variable or wildcard: retrofit2.Call 的問題


封裝目的:屏蔽底層實現,提供統一接口,並支持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關鍵字。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM