封裝目的:屏蔽底層實現,提供統一接口,並支持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)
}
終於達到了目的。
以上代碼經過測試,可以運行。
相比於第一次封裝,這次的改動是:
- 封裝思路為直接返回String,不使用Retrofit的Gson解析器。(代碼第5行)
- 由於不使用Retrofit的Gson解析器,所以需要手動使用Gson完成Json解析。在這個過程中,會使用泛型作為返回值類型,但由於泛型的不確定性,無法作為返回值,於是使用inline+reified的方式將返回值確定化,得到解析Json的函數parse()(代碼32行)
- 由於封裝后的函數postData()中含有inline函數,所以postData()也必須設置為inline函數,並且使用reified修飾泛型(代碼34行)
- 由於inline的作用,NetUtil類的屬性mRetrofit不能用private修飾,用public又范圍太大,所以使用protect修飾,外部也就無法直接調用mRetrofit。得益於kotlin的機制,不用open修飾NetUtil類+private的構造器,NetUtil類無法被繼承和實例化,也不存在子類濫用mRetrofit的現象,所以mRetrofit還是安全的
反思:
通過這次封裝,發現了泛型的盲區,對泛型的理解還不夠深刻,沒有思考過擦除帶來的后果,也就是Gson不支持泛型作為返回值類型的原因。同時對inline的也完全不理解,甚至沒有見過reified關鍵字。