手写一个简易的Retrofit

构建

retrofit是采用建造者模式进行构建的,传入的参数分别有baseUrlokHttpClientgson

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一个接口进行定义请求的。这里需要自定义几个注解,这里只写了四个常用的getpostqueryfield注解

//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传入的参数是一个接口类,需要用到动态代理,返回的是接口类,这样就能进行调用接口里的方法。当调用接口方法的时候会对之前自定义的注解进行解析,获取到请求的类型GetPost,并获取到请求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进行连接即可。当返回类型为okHttpCall对象时,直接返回okHttpClient!!.newCall(request),最终调用enqueue方法,得到Response数据。

这里如果需要直接解析成实体对象的话则需要自定义一个解析器NetCallAdapterNetCallAdapter实现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的实现

NetCallNetCallAdapter的接口,NetCallback是调用execute方法后的回调,最终直接返回一个实体对象

NetCallAdapter的构建方法里传入了三个参数,
Call :okHttpCall对象
Gson:Gson实例,在创建retrofit时已传入
Type:最终返回的实体类型

调用传入的Call对象的enqueue方法,在onResponse回调中使用GsonResponse转换为实体对象,并调用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))])

你可能感兴趣的:(手写一个简易的Retrofit)