構建
retrofit是采用建造者模式進行構建的,傳入的參數(shù)分別有baseUrl、okHttpClient、gson
class Retrofit {
private var okHttpClient: OkHttpClient? = null
private var gson: Gson? = null
private var baseUrl: String? = null;
private constructor()
private constructor(baseUrl: String, okHttpClient: OkHttpClient, gson: Gson) {
this.baseUrl = baseUrl
this.okHttpClient = okHttpClient
this.gson = gson
}
class Builder {
private var okHttpClient: OkHttpClient? = null
private var gson: Gson? = null
private var baseUrl: String? = null;
fun client(okHttpClient: OkHttpClient): Builder {
this.okHttpClient = okHttpClient
return this
}
fun gson(gson: Gson): Builder {
this.gson = gson;
return this
}
fun baseUrl(baseUrl: String): Builder {
this.baseUrl = baseUrl
return this;
}
fun build(): Retrofit {
if (baseUrl == null) {
throw Exception("必須傳入一個baseUrl")
}
if (okHttpClient == null) {
okHttpClient = OkHttpClient()
}
if (gson == null) {
gson = Gson()
}
return Retrofit(baseUrl!!, okHttpClient!!, gson!!)
}
}
}
接口
retrofit是通過create一個接口進行定義請求的。這里需要自定義幾個注解,這里只寫了四個常用的get、post、query、field注解
//GET.java
@Target(ElementType.METHOD)//用于描述方法
@Retention(RetentionPolicy.RUNTIME)
public @interface GET {
//注解中 方法名寫成value 這樣的話,在使用注解傳入?yún)?shù)時就不用帶key了,它會作為一個默認的調用
String value();//請求網(wǎng)址
}
//POST.java
@Target(ElementType.METHOD)//用于描述方法
@Retention(RetentionPolicy.RUNTIME)
public @interface POST {
String value();//請求網(wǎng)址
}
//Query.java
@Target(ElementType.PARAMETER)//用于描述方法參數(shù)
@Retention(RetentionPolicy.RUNTIME)
public @interface Query {
String value();
}
//Field.java
@Target(ElementType.PARAMETER)//用于描述方法參數(shù)
@Retention(RetentionPolicy.RUNTIME)
public @interface Field {
String value();//POST表格的參數(shù)
}
create方法
構建好retrofit后,需要創(chuàng)建create方法,create傳入的參數(shù)是一個接口類,需要用到動態(tài)代理,返回的是接口類,這樣就能進行調用接口里的方法。當調用接口方法的時候會對之前自定義的注解進行解析,獲取到請求的類型Get或Post,并獲取到請求url
//Retrofit.kt
fun <T> create(service: Class<T>): T {
return Proxy.newProxyInstance(service.classLoader, arrayOf(service), object : InvocationHandler {
override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any {
//獲取方法的注解,GET或POST
val annotations = method!!.annotations
for (annotation in annotations) {
if (annotation is GET) {//注解為GET時
//完整url
val url = baseUrl + annotation.value
//解析方法
return parseGet<T>(url, method, args!!)!!
} else if (annotation is POST) {//注解為POST時
val url = baseUrl + annotation.value
return parsePost<T>(url, method, args!!)!!
}
}
return Any()
}
}) as T
}
Get方法解析
解析請求參數(shù),Get方法參數(shù)用的時Query注解,所以解析完后與之前的url進行連接即可。當返回類型為okHttp的Call對象時,直接返回okHttpClient!!.newCall(request),最終調用enqueue方法,得到Response數(shù)據(jù)。
這里如果需要直接解析成實體對象的話則需要自定義一個解析器NetCallAdapter,NetCallAdapter實現(xiàn)NetCall接口,所以當返回類型為NetCall時返回一個NetCallAdapter
//Retrofit.kt
private fun<T> parseGet(baseUrl: String, method: Method, args: Array<out Any>): Any? {
var url = baseUrl
//解析參數(shù),Query注解,并將參數(shù)連接到url中
val parameterAnnotations = method.parameterAnnotations
for (i in parameterAnnotations.indices) {
for (parameterAnnotation in parameterAnnotations[i]) {
if (parameterAnnotation is Query) {
//key跟value
val key = parameterAnnotation.value
val value = args[i].toString()
if (url.indexOf("?") == -1) {
//第一個參數(shù)用?號連接
url += "?$key=$value"
} else {
//后面的參數(shù)用&連接
url += "&$key=$value"
}
}
}
}
val request = Request.Builder()
.url(url)
.build()
//獲取返回類型
val genericReturnType = method.genericReturnType
val rawType = getRawType(genericReturnType)
if (rawType == Call::class.java) {
//當為okHttp的Call類型,直接返回okHttpClient!!.newCall(request)
return okHttpClient!!.newCall(request)
}else if (rawType == NetCall::class.java){
//當為自定義的解析器,則返回NetCallAdapter對象
val parameterizedType = genericReturnType as ParameterizedType
val type = parameterizedType.actualTypeArguments[0]
return NetCallAdapter<T>(okHttpClient!!.newCall(request),gson!!,type)
}
return null
}
//獲取返回類型
private fun getRawType(type: Type): Class<*> {
if (type is Class<*>) {
// Type is a normal class.
return type
}
if (type is ParameterizedType) {
val parameterizedType = type as ParameterizedType
// I'm not exactly sure why getRawType() returns Type instead of Class. Neal isn't either but
// suspects some pathological case related to nested classes exists.
val rawType = parameterizedType.rawType
if (rawType !is Class<*>) throw IllegalArgumentException()
return rawType as Class<*>
}
if (type is GenericArrayType) {
val componentType = (type as GenericArrayType).genericComponentType
return java.lang.reflect.Array.newInstance(getRawType(componentType), 0).javaClass
}
if (type is TypeVariable<*>) {
// We could use the variable's bounds, but that won't work if there are multiple. Having a raw
// type that's more general than necessary is okay.
return Any::class.java
}
if (type is WildcardType) {
return getRawType((type as WildcardType).upperBounds[0])
}
throw IllegalArgumentException(
"Expected a Class, ParameterizedType, or "
+ "GenericArrayType, but <" + type + "> is of type " + type.javaClass.name
)
}
NetCallAdapter的實現(xiàn)
NetCall為NetCallAdapter的接口,NetCallback是調用execute方法后的回調,最終直接返回一個實體對象
NetCallAdapter的構建方法里傳入了三個參數(shù),
Call :okHttp的Call對象
Gson:Gson實例,在創(chuàng)建retrofit時已傳入
Type:最終返回的實體類型
調用傳入的Call對象的enqueue方法,在onResponse回調中使用Gson把Response轉換為實體對象,并調用netCallback.onSuccess回調
//NetCall.kt
interface NetCall<T> {
fun execute(netCallback: NetCallback<T>)
}
//NetCallback.kt
interface NetCallback<T> {
fun onFailure(e: Exception)
fun onSuccess(t: T)
}
//NetCallAdapter.kt
open class NetCallAdapter<T>(private val call: Call, private val gson: Gson, private val type: Type) : NetCall<T> {
override fun execute(netCallback: NetCallback<T>) {
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
netCallback.onFailure(e)
}
override fun onResponse(call: Call, response: Response) {
val t = gson.fromJson<T>(response.body?.string(), type)
netCallback.onSuccess(t)
}
})
}
}
調用Get請求
首先構建一個接口類,里面定義好GET請求的方法,返回的參數(shù)為okHttpClient的Call類。然后在Activity構建一個retrofit,并調用接口的方法,返回一個okhttp的Call對象,再調用Call的enqueue方法。最終成功調用onResponse回調
//WeatherBean.kt
data class WeatherBean(
@SerializedName("HeWeather6")
val heWeather6: List<HeWeather6> = listOf()
){
data class HeWeather6(
@SerializedName("basic")
val basic: Basic = Basic(),
@SerializedName("now")
val now: Now = Now(),
@SerializedName("status")
val status: String = "",
@SerializedName("update")
val update: Update = Update()
)
data class Update(
@SerializedName("loc")
val loc: String = "",
@SerializedName("utc")
val utc: String = ""
)
data class Basic(
@SerializedName("admin_area")
val adminArea: String = "",
@SerializedName("cid")
val cid: String = "",
@SerializedName("cnty")
val cnty: String = "",
@SerializedName("lat")
val lat: String = "",
@SerializedName("location")
val location: String = "",
@SerializedName("lon")
val lon: String = "",
@SerializedName("parent_city")
val parentCity: String = "",
@SerializedName("tz")
val tz: String = ""
)
data class Now(
@SerializedName("cloud")
val cloud: String = "",
@SerializedName("cond_code")
val condCode: String = "",
@SerializedName("cond_txt")
val condTxt: String = "",
@SerializedName("fl")
val fl: String = "",
@SerializedName("hum")
val hum: String = "",
@SerializedName("pcpn")
val pcpn: String = "",
@SerializedName("pres")
val pres: String = "",
@SerializedName("tmp")
val tmp: String = "",
@SerializedName("vis")
val vis: String = "",
@SerializedName("wind_deg")
val windDeg: String = "",
@SerializedName("wind_dir")
val windDir: String = "",
@SerializedName("wind_sc")
val windSc: String = "",
@SerializedName("wind_spd")
val windSpd: String = ""
)
}
//ApiService.kt
interface ApiService {
@GET("s6/weather/now")
fun getWeather(@Query("key")key:String,@Query("location")city:String):NetCall<WeatherBean>
}
//MainActivity.kt
val retrofit = Retrofit.Builder()
.baseUrl("https://free-api.heweather.com/")
.client(OkHttpClient())
.gson(Gson())
.build()
retrofit.create(ApiService::class.java).getWeather("101010100").execute(object : NetCallback<WeatherBean>{
override fun onFailure(e: Exception) {
e.printStackTrace()
}
override fun onSuccess(t: WeatherBean) {
Log.e("onSuccess",t.toString())
}
})
//Log
2020-01-03 16:27:45.848 10420-10448/com.tuohaicare.customretrofit E/onSuccess: WeatherBean(heWeather6=[HeWeather6(basic=Basic(adminArea=北京, cid=CN101010100, cnty=中國, lat=39.90498734, location=北京, lon=116.4052887, parentCity=北京, tz=+8.00), now=Now(cloud=5, condCode=100, condTxt=晴, fl=3, hum=23, pcpn=0.0, pres=1023, tmp=6, vis=16, windDeg=253, windDir=西南風, windSc=2, windSpd=7), status=ok, update=Update(loc=2020-01-03 16:14, utc=2020-01-03 08:14))])
Post方法解析
和Get方法類似,只是添加參數(shù)的方式不一樣,post是提交表格
//Retrofit.kt
private fun <T> parsePost(url: String, method: Method, args: Array<out Any>): Any? {
val parameterAnnotations = method.parameterAnnotations
//form表單
val formBody = FormBody.Builder().apply {
for (i in parameterAnnotations.indices) {
for (annotation in parameterAnnotations[i]) {
if (annotation is Field) {
//key跟value
add(annotation.value, args[i].toString())
}
}
}
}
val request = Request.Builder()
.url(url)
.post(formBody.build())
.build()
//獲取返回類型
val genericReturnType = method.genericReturnType
val rawType = getRawType(genericReturnType)
if (rawType == Call::class.java) {
return okHttpClient!!.newCall(request)
} else if (rawType == NetCall::class.java) {
//獲取泛型類型
val parameterizedType = genericReturnType as ParameterizedType
val type = parameterizedType.actualTypeArguments[0]
return NetCallAdapter<T>(okHttpClient!!.newCall(request), gson!!, type)
}
return null
}
調用Post請求
//ApiService.kt
interface ApiService {
@POST("s6/weather/now")
fun getWeather2(@Field("key")key:String,@Field("location")city:String):NetCall<WeatherBean>
}
//MainActivity.kt
retrofit.create(ApiService::class.java).getWeather2("11e895a6b3854f0fb49508eea65df6ca","北京").execute(object : NetCallback<WeatherBean>{
override fun onFailure(e: Exception) {
e.printStackTrace()
}
override fun onSuccess(t: WeatherBean) {
Log.e("onSuccess",t.toString())
}
})
//Log
2020-01-03 16:27:45.848 10420-10448/com.tuohaicare.customretrofit E/onSuccess: WeatherBean(heWeather6=[HeWeather6(basic=Basic(adminArea=北京, cid=CN101010100, cnty=中國, lat=39.90498734, location=北京, lon=116.4052887, parentCity=北京, tz=+8.00), now=Now(cloud=5, condCode=100, condTxt=晴, fl=3, hum=23, pcpn=0.0, pres=1023, tmp=6, vis=16, windDeg=253, windDir=西南風, windSc=2, windSpd=7), status=ok, update=Update(loc=2020-01-03 16:14, utc=2020-01-03 08:14))])