Retrofit
Retrofit最新版引入
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
}
除了Retrofit之外不引入其他依赖。
网络请求:
interface ApiService {
@GET("getUserData")
fun getUserData():Call<ResponseBody>
}
fun netRequest(){
// 网络请求地址格式要写对,否则crash
// 需要网络权限否则crash
val retrofit = Retrofit.Builder()
.baseUrl("https://mockapi.eolinker.com/9IiwI82f58c23411240ed608ceca204b2f185014507cbe3/")
.build()
val service = retrofit.create(ApiService::class.java)
val call = service.getUserData()
call.enqueue(object :Callback<ResponseBody>{
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
Log.d(TAG, "onFailure: $t")
}
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
val userBean = response.body()?.string()
Log.d(TAG, "onResponse: $userBean")
}
})
}
Retrofit是建立在OkHttp只上的一个网络请求封装库,内部依靠OkHttp来完成网络请求。API通过interface来声明,配置API路径、请求方式、请求参数、返回值类型等配置。getUserData()请求结果是一个json格式的字符串,返回类型定义为Call<ResponseBody>,okhttp3.ResponseBody,retrofit2.Call是Retrofit对okhttp3.Call包装。
converter-gson
上面返回的是json格式,我们希望返回的是Bean对象;使用库进行反序列化;
库引入:
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
请求逻辑:
interface ApiService {
@GET("getUserData")
fun getUserData():Call<UserBean>
}
data class UserBean(val status:String,val msg:String,val data:Data)
data class Data(val userName:String,val userAge:String)
fun netRequest(){
// 网络请求地址格式要写对,否则crash
// 需要网络权限否则crash
val retrofit = Retrofit.Builder()
.baseUrl("https://mockapi.eolinker.com/9IiwI82f58c23411240ed608ceca204b2f185014507cbe3/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val service = retrofit.create(ApiService::class.java)
val call = service.getUserData()
call.enqueue(object :Callback<UserBean>{
override fun onFailure(call: Call<UserBean>, t: Throwable) {
Log.d(TAG, "onFailure: $t")
}
override fun onResponse(call: Call<UserBean>, response: Response<UserBean>) {
val userBean = response.body()
Log.d(TAG, "onResponse: $userBean")
}
})
}
adapter-rxjava2
如果不想看到Call<UserBean>,通过RxJava方式进行网络请求,使用此库Observable。
依赖库:
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
代码逻辑:注意需要在自线程执行,否则线程异常:android.os.NetworkOnMainThreadException
interface ApiService {
@GET("getUserData")
fun getUserData():Observable<UserBean>
}
fun netRequest() {
Thread(object :Runnable{
override fun run() {
// 网络请求地址格式要写对,否则crash
// 需要网络权限否则crash
val retrofit = Retrofit.Builder()
.baseUrl("https://mockapi.eolinker.com/9IiwI82f58c23411240ed608ceca204b2f185014507cbe3/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
val service = retrofit.create(ApiService::class.java)
val call = service.getUserData()
// 需要自线程执行否则报:accept(t: Throwable?)--》android.os.NetworkOnMainThreadException
call.subscribe(object : Consumer<UserBean> {
override fun accept(userBean: UserBean?) {
Log.d(TAG, "accept-userBean: $userBean")
}
}, object : Consumer<Throwable> {
override fun accept(t: Throwable?) {
Log.d(TAG, "accept-error: $t")
}
})
}
}).start()
}
思路
不论是Call还是Observable类型,只需要添加不同的CallAdapterFactory即可,LiveData类型也是可以的。不管需要ResponseBody还是Bean对象类型,也是添加不同的ConverterFactory即可,API返回XML格式也是可以解析的。
Retrofit.create()
Proxy.newProxyInstance实现动态代理模式。通过动态代理,将ApiService调用操作转发给InvocationHandler来完成。Retrofit后续会通过反射拿到我们声明的getUserData()时标注各个配置项,如:API路径、请求方式、请求参数、返回值类型等信息,然后拼接为OkHttp的一个原始网络请求。当我们调用了call.enqueue时,这个操作会触发InvocationHandler去发起OkHttp网络请求。
Retrofit会根据method是否是默认方法来决定如何调用,loadServiceMethod(method)方法:
将每个代表接口方法的method转化为ServiceMethod对象,包含了API的具体信息
因为API可能会被多次调用,将构造出来的ServiceMethod对象缓存到se rviceMethodCache中实现复用。
ServiceMethod
loadServiceMethod(method)返回的是一个ServiceMethod对象,每个ServiceMethod对象对应一个API接口方法,内部包含对API解析结果。loadServiceMethod(method).invoke(args)调用API方法并传递参数的过程,对应:val call = service.getUserData()这个过程。
ServiceMethod是一个抽象类,包含抽象的invoke(Object[] args)方法。
ServiceMethod使用了工厂模式,由于API的最终请求方式可能是多样化的。可能通过线程池来执行,也可以通过kotlin协程来执行,使用工厂模式的意义可以将这种差异都隐藏在不同的ServiceMethod实现类中,外部统一通过parseAnnotations方法来获取ServiceMethod实现类。
parseAnnotations方法返回的ServiceMethod实际上是HttpServiceMethod,通过HttpServiceMethod.parseAnnotations返回的HttpServiceMethod实现。
HttpServiceMethod
是ServiceMethod直接唯一字类。HttpServiceMethod也是一个抽象类,包含两个泛型声明,ResponseT表示API方法返回值的外层包装类型,ReturnT是实际需要的数据类型。例如fun getUserData():Call<UserBean>方法,ResponseT对应的是Call,ReturnT对应的是UserBean。此外,HttpServiceMethod也实现了父类invoke方法,并转交给另一个抽象方法adapt来完成,API网络请求具体看adapt实现。
Retrofit目前已经支持Kotlin协程方式进行调用了。
parseAnnotations主要逻辑:
1、先通过createCallAdapter(retrofit, method, adapterType, annotations)方法拿到CallAdapter对象,实现API方法的不同返回值包装类处理逻辑。例如:getUserData()方法返回的值包装类类型Call,那返回CallAdapter对象对应DefaultCallAdapterFactory包含的Adapter;如果是Observable,那么返回的就是RxJava2CallAdapterFactory包含的Adapter。
2、通过createResponseConverter(retrofit, method, responseType)方法拿到Converter对象,Converter就用于实现API方法的不同返回值处理逻辑。例如:getUserData()返回类型ResponseBody,那么Converter对象就对应BuiltInConverters;如果是UserBean,那么对应GsonConverterFactory
根据上面两步,构造出一个CallAdapted对象并返回。
CallAdapter是HttpServiceMethod的子类,在InvocationHandler中通过loadServiceMethod(method).invoke(args)发起调用链,会先创建出一个OkHttpCall对象,并最后调用callAdapter.adapt(call)方法
OkHttpCall
是实际发起OkHttp请求的地方。当调用getUserData():Call<ResponseBody>方法时,返回的Call对象实际上是OkHttpCall类型,而当我们调用call.enqueue(callback)方法时,enqueue方法会发起一个OkHttp请求,传入的Callback对象会由okhttp3.callback本身回调进行中转调用。
总结
通过retrofit.create(ApiService::class.java)得到ApiService动态实现类,通过Java原生提供的Proxy.newProxyInstance代表的动态代理功能来实现的。拿到ApiService实现类,可以直接调用ApiService中声明的方法。
当我们调用了service.getUserData()方法,Retrofit会将每个API方法都抽象封装为一个ServiceMethod并缓存起来,操作会转交给ServiceMethod来完成,由ServiceMethod来负责返回我们的目标类型,对应的是ServiceMethod.invoke(Object[] args)方法,args代表的是我们调用的API方法时需要传递参数,对应本例是空数组。
ServiceMethod只具有唯一的子类HttpServiceMethod, 而HttpServiceMethod会invoke方法构建出一个OkHttpCall对象,然后调用其抽象方法adapt
对于不同的请求方式,ServiceMethod.parseAnnotations方法最终会返回不同的HttpServiceMethod子类。本例,最终会返回CallAdapter对象
Kotlin 协程方式来调用
依赖库:
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9'
}
通过runBlocking来启动一个协程,避免请求还未结束main线程就停止的情况。实际开发中要避免runBlocking这样使用协程。
interface ApiService {
@GET("getUserData")
suspend fun getUserData():UserBean
}
fun test2() {
val retrofit = Retrofit.Builder().baseUrl("https://mockapi.eolinker.com/9IiwI82f58c23411240ed608ceca204b2f185014507cbe3/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val service = retrofit.create(ApiService::class.java)
runBlocking {
Log.d(TAG, "test2: isMain-1-:${Looper.myLooper() == Looper.getMainLooper()}")
val job: Job = launch {
Log.d(TAG, "test2: isMain-2-:${Looper.myLooper() == Looper.getMainLooper()}")
try {
val userBean = service.getUserData()
Log.d(TAG, "test2: userBean:$userBean")
} catch (e: Throwable) {
Log.d(TAG, "test2: onFailure:$e")
}
}
}
}
getUserData()不需要任何包装类了,直接声明目标数据类型就可以,使用更简洁。
每个方法用suspend关键字进行修饰,标明其只能在协程中调用。
Retrofit是以Java语言实现的,但suspend关键字只能用于Kotlin,两者存在“沟通障碍”,但只要调用方也属于JVM语言,那Retrofit就是可以使用的,通过IDEA将ApiService反编译为Java类,看下suspend函数在Retrofit的角度来看是怎样实现的
public interface ApiService {
@GET("getUserData")
@Nullable
Object getUserData1(@NotNull Continuation var1);
方法返回值类型变为Object,方法参数列表中添加了一个kotlin.coroutines.Continuation参数。
在RequestFactory类中包含了一个isKotlinSuspendFunction的boolen类型的变量,当前解析的Method是否是suspend函数。RequestFactory的build()方法中,对API方法每个参数进行解析,包含了检测当前解析参数是否属于最后一个参数的逻辑。
如果检测到最后一个参数类型是Continuation.class,那isKotlinSuspendFunction就会变为true。
API最后一个参数强转为Continuation<ResponseT>类型,调用KotlinExtensions.await(call,continuation)这个Kotlin的扩展函数
await()以suspendCancellableCoroutine这个支持cancel的CoroutineScope作为作用域,以Call.enqueue的方式发起OkHttp请求,拿到responseBody后就透传出来,完成整个调用流程。
Retrofit对Android平台
Retrofit并不需要依赖于Android平台,可以用于任意的Java客户端,Retrofit只是对Android平台进行了特殊实现。
在构建Retrofit对象时候,可以选择传递一个Platform对象用于标记调用方所处的平台
1、判断是否支持Java8,是否支持调用interface的默认方法,判断是否支持Optional和CompletableFuture要用到。因为Android应用如果支持Java8,需要Gradle文件进行主动配置,Java8在Android平台目前也支持不彻底。
2、实现main线程回调的Executor。Android平台不允许在main线程上执行耗时任务,UI操作都需要切换到main线程来完成。对于Android平台,Retroft回调网络请求结果,通过main线程执行的Executor来进行线程切换。