构建
retrofit
是采用建造者模式进行构建的,传入的参数分别有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 这样的话,在使用注解传入参数时就不用带key了,它会作为一个默认的调用
String value();//请求网址
}
//POST.java
@Target(ElementType.METHOD)//用于描述方法
@Retention(RetentionPolicy.RUNTIME)
public @interface POST {
String value();//请求网址
}
//Query.java
@Target(ElementType.PARAMETER)//用于描述方法参数
@Retention(RetentionPolicy.RUNTIME)
public @interface Query {
String value();
}
//Field.java
@Target(ElementType.PARAMETER)//用于描述方法参数
@Retention(RetentionPolicy.RUNTIME)
public @interface Field {
String value();//POST表格的参数
}
create方法
构建好retrofit
后,需要创建create
方法,create
传入的参数是一个接口类,需要用到动态代理,返回的是接口类,这样就能进行调用接口里的方法。当调用接口方法的时候会对之前自定义的注解进行解析,获取到请求的类型Get
或Post
,并获取到请求url
//Retrofit.kt
fun create(service: Class): T {
return Proxy.newProxyInstance(service.classLoader, arrayOf(service), object : InvocationHandler {
override fun invoke(proxy: Any?, method: Method?, args: Array?): Any {
//获取方法的注解,GET或POST
val annotations = method!!.annotations
for (annotation in annotations) {
if (annotation is GET) {//注解为GET时
//完整url
val url = baseUrl + annotation.value
//解析方法
return parseGet(url, method, args!!)!!
} else if (annotation is POST) {//注解为POST时
val url = baseUrl + annotation.value
return parsePost(url, method, args!!)!!
}
}
return Any()
}
}) as T
}
Get方法解析
解析请求参数,Get方法参数用的时Query注解,所以解析完后与之前的url进行连接即可。当返回类型为okHttp
的Call
对象时,直接返回okHttpClient!!.newCall(request)
,最终调用enqueue
方法,得到Response
数据。
这里如果需要直接解析成实体对象的话则需要自定义一个解析器NetCallAdapter
,NetCallAdapter
实现NetCall
接口,所以当返回类型为NetCall
时返回一个NetCallAdapter
//Retrofit.kt
private fun parseGet(baseUrl: String, method: Method, args: Array): Any? {
var url = baseUrl
//解析参数,Query注解,并将参数连接到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) {
//第一个参数用?号连接
url += "?$key=$value"
} else {
//后面的参数用&连接
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(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的实现
NetCall
为NetCallAdapter
的接口,NetCallback
是调用execute
方法后的回调,最终直接返回一个实体对象
NetCallAdapter
的构建方法里传入了三个参数,
Call :okHttp
的Call
对象
Gson:Gson
实例,在创建retrofit时已传入
Type:最终返回的实体类型
调用传入的Call
对象的enqueue
方法,在onResponse
回调中使用Gson
把Response
转换为实体对象,并调用netCallback.onSuccess
回调
//NetCall.kt
interface NetCall {
fun execute(netCallback: NetCallback)
}
//NetCallback.kt
interface NetCallback {
fun onFailure(e: Exception)
fun onSuccess(t: T)
}
//NetCallAdapter.kt
open class NetCallAdapter(private val call: Call, private val gson: Gson, private val type: Type) : NetCall {
override fun execute(netCallback: NetCallback) {
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(response.body?.string(), type)
netCallback.onSuccess(t)
}
})
}
}
调用Get请求
首先构建一个接口类,里面定义好GET请求的方法,返回的参数为okHttpClient的Call类。然后在Activity构建一个retrofit,并调用接口的方法,返回一个okhttp的Call对象,再调用Call的enqueue方法。最终成功调用onResponse
回调
//WeatherBean.kt
data class WeatherBean(
@SerializedName("HeWeather6")
val heWeather6: List = 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
}
//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{
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方法类似,只是添加参数的方式不一样,post是提交表格
//Retrofit.kt
private fun parsePost(url: String, method: Method, args: Array): 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(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
}
//MainActivity.kt
retrofit.create(ApiService::class.java).getWeather2("11e895a6b3854f0fb49508eea65df6ca","北京").execute(object : NetCallback{
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))])