手寫一個簡易的Retrofit

構建

retrofit是采用建造者模式進行構建的,傳入的參數(shù)分別有baseUrlokHttpClient、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一個接口進行定義請求的。這里需要自定義幾個注解,這里只寫了四個常用的getpost、queryfield注解

//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)代理,返回的是接口類,這樣就能進行調用接口里的方法。當調用接口方法的時候會對之前自定義的注解進行解析,獲取到請求的類型GetPost,并獲取到請求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進行連接即可。當返回類型為okHttpCall對象時,直接返回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)

NetCallNetCallAdapter的接口,NetCallback是調用execute方法后的回調,最終直接返回一個實體對象

NetCallAdapter的構建方法里傳入了三個參數(shù),
Call :okHttpCall對象
Gson:Gson實例,在創(chuàng)建retrofit時已傳入
Type:最終返回的實體類型

調用傳入的Call對象的enqueue方法,在onResponse回調中使用GsonResponse轉換為實體對象,并調用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))])
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容