需求分析
当我们按照Retrofit 的官方示例来创建API接口时,我们可以在接口定义上非常清晰地看到:请求地址,请求方法,它有哪些参数,各个参数是以何种方式传递的。所以在做扩展功能时我们希望延续这种一目了然,因此具体需求为:通过在已有的Retrofit API接口方法上,以增加自定义注解的方式来为请求增加固定值的参数
参考来源
核心原理
Retrofit 在构建OkHttp.Request
时,将被调用的方法使用tag方法保存在了构建好的Request
实例中,因此只需要使用拦截器从Request
实例中获取到该方法即可读取上面的注解,并添加参数
参考代码
自定义注解,这里定义了两个注解来适应需要单个参数和多个参数的情况
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class StaticQuery(
val key: String,
val value: String,
)
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class StaticQueries(
val value: Array<StaticQuery>,
)
拦截器,从Request
实例中获取方法并遍历方法上的注解
class StaticQueryInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
// 获取retrofit创建Request时在tag中保存的被调用的方法, 如果没获取到则直接返回
val method = request.tag(Invocation::class.java)?.method() ?: return chain.proceed(request)
val urlBuilder = request.url.newBuilder()
// 遍历方法上的注解, 处理其中的自定义注解
for (annotation in method.annotations) {
if (annotation is StaticQuery) {
urlBuilder.addQueryParameter(annotation.key, annotation.value)
}
if (annotation is StaticQueries) {
for (staticParam in annotation.value) {
urlBuilder.addQueryParameter(staticParam.key, staticParam.value)
}
}
}
val newRequest = request.newBuilder().url(urlBuilder.build()).build()
return chain.proceed(newRequest)
}
}
表单参数的情况类似,注解:
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class StaticField(
val key: String,
val value: String,
)
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class StaticFields(
val value: Array<StaticField>,
)
拦截器
class StaticFieldInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
// 获取retrofit创建Request时在tag中保存的被调用的方法, 如果没获取到则直接返回
val method = request.tag(Invocation::class.java)?.method() ?: return chain.proceed(request)
// 获取请求体,如果不存在则直接返回
val requestBody = request.body ?: return chain.proceed(request)
val newRequestBuilder = request.newBuilder()
if (requestBody is FormBody) {
// 复制原表单数据
val formBuilder = FormBody.Builder()
for (i in 0 until requestBody.size) {
formBuilder.addEncoded(requestBody.encodedName(i), requestBody.encodedValue(i))
}
// 添加注解上的静态参数
val annotations = findAnnotations(method.annotations)
for (staticField in annotations) {
formBuilder.add(staticField.key, staticField.value)
}
newRequestBuilder.post(formBuilder.build())
}
return chain.proceed(newRequestBuilder.build())
}
companion object {
@JvmStatic
fun findAnnotations(annotations: Array<Annotation>): List<StaticField> {
val list = ArrayList<StaticField>()
for (annotation in annotations) {
if (annotation is StaticField) {
list.add(annotation)
}
if (annotation is StaticFields) {
list.addAll(annotation.value.toList())
}
}
return list
}
}
}
使用示例
@FormUrlEncoded
@POST("ajax/novels/bookmarks/delete")
@StaticField(key = "del", value = "1")
fun postNovelDel(@Field("book_id") bookmarkId: Long): Call<PixivResponse<Void>>
@GET("ranking.php")
@StaticQuery(key = "format", value = "json")
fun getIllustrationRanking(
@Query("p") page: Int? = 1,
@Query("mode") mode: RankingMode? = null,
@Query("content") content: RankingContent? = null,
@Query("date") date: String? = null,
): Call<IllustrationRankingBody>