携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情
前言
Retrofit源码系列文章:
- 挖一挖Retrofit源码(一)
- 挖一挖Retrofit源码(二)
- Retrofit之CallAdapter解析
- Retrofit之自定义CallAdapter
- Retrofit之Converter解析
在上一篇文章Retrofit之CallAdapter解析了解完CallAdapter的原理之后,在开发中想要利用CallAdapter来实现一些特定的需求也就容易很多了。举个栗子,假如说有一个接口方法服务器返回的是BaseResponse<DataBean>
结构的数据,而我有100个地方需要调用这个方法,并且我都需要将BaseResponse<DataBean>
转换成UserInfo
来使用,难道我要每调用一次就手动转换一次?!作为一个有追求的程序猿当然不能容忍!那我可以将这个转换类型的工作交给CallAdapter去做,将原始返回类型BaseResponse<DataBean>
封装出一个Retrofit的Call对象传入adapt方法,由Call对象去操控OkHttpCall发起请求,最后将请求结果进行适配并返回API方法所需的返回类型UserInfo
。然而Retrofit内置的CallAdapter中都不认识UserInfo
这东东啊?也好办,自己定义一个不就完事了!
在自定义CallAdapter之前,先搞清楚需要定义哪些什么类,这个CallAdapter需要实现什么功能:
- API方法getUserInfo的原始返回类型为
BaseResponse<DataBean>
,而我期望实际上拿到的数据为UserInfo
,那么CallAdapter需要能对返回类型进行适配并发起请求,在内部完成BaseResponse<DataBean>
->UserInfo
的转换并最终返回UserInfo
- 定义一个CallAdapter实现
CallAdapter<BaseResponse<DataBean>,UserInfo>
- 定义CallAdapter的工厂类
UserInfoCallAdapter类
class UserInfoCallAdapter(private var responseType: Type) :
CallAdapter<BaseResponse<DataBean>, UserInfo> {
override fun responseType(): Type {
return responseType
}
override fun adapt(call: Call<BaseResponse<DataBean>>): UserInfo {
val response = call.execute()
val userInfo: UserInfo
if (response.isSuccessful && response.code() == 200) {
userInfo = response.body()?.data?.toUserInfo() ?: UserInfo(..,..,..)
} else {
throw Exception("message:${response.message()};code:${response.code()}")
}
return userInfo
}
}
复制代码
UserInfoCallAdapter实现CallAdapter的两个方法,在adapt方法里发起网络请求,请求成功且返回成功的状态码则返回处理后的数据,否则用抛出异常来处理错误。
UserInfoCallAdapterFactory类
class UserInfoCallAdapterFactory : CallAdapter.Factory() {
override fun get(
returnType: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
if (getRawType(returnType) != UserInfo::class.java) {
return null
}
val type = object : MyTypeToken<BaseResponse<DataBean>>() {}.type as ParameterizedType
val responseType = type.actualTypeArguments[0]
return UserInfoCallAdapter(responseType)
}
}
复制代码
UserInfoCallAdapterFactory里需要检测returnType,如果返回类型不是UserInfo则不用UserInfoCallAdapter,检测完返回类型后就是获取responseType,并创建返回一个UserInfoCallAdapter对象。另外MyTypeToken这个类是用来构建ParameterizedType的,来自Me_And_Roid的方案:
abstract class MyTypeToken<T> {
val type: Type? = this::class.java.genericSuperclass
}
复制代码
获得BaseResponse<DataBean>
的ParameterizedType之后通过getActualTypeArguments拿到index为0的类型作为responseType,也就是DataBean,当然这里也可以直接把responseType写死成DataBean。
使用方式
在API里添加一个返回类型为UserInfo的方法getUserInfo:
interface TestApi {
@GET("/.../...")
fun getDataA(): Call<DataBean>
@GET("/.../...")
fun getDataB(): Observable<DataBean>
@GET("/.../...")
fun getUserInfo(): UserInfo
}
复制代码
初始化Retrofit时配置UserInfoCallAdapterFactory:
val retrofit = Retrofit.Builder()
.baseUrl("https://test.com")
.addCallAdapterFactory(UserInfoCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.build()
复制代码
配置好后就可以正常调用了:
fun getUserInfo(): UserInfo {
return try {
testApi.getUserInfo()
} catch (e: Exception) {
e.printStackTrace()
UserInfo(...,...,...)
}
}
复制代码
最后
最后Retrofit之CallAdapter解析仅代表个人对CallAdapter的理解,并简单尝试了一下自定义CallAdapter,并不代表最优雅的实现方案,另外原本是期望自定义的CallAdapter能适配Result<UserInfo>
的返回类型的,这样就能更方便地处理成功和错误的返回信息,然而由于kt里的Result是value class,在测试的时候报错显示returnType不能为Object,导致创建callAdapter失败,于是改为通过抛出异常来处理错误。对于自定义CallAdapter如果有更好更优雅的方式欢迎大佬们不吝赐教,对CallAdapter理解有偏差的地方也欢迎大佬们指出。