一、retrofit使用解析
请看图:
1.1 引用retrofit
implementation('com.squareup.retrofit2:retrofit:2.9.0')
retrofit框架请求库是retrofit自家的okhttp,仓库依赖的是okhttp3.x,所以如果你想用okhttp4.x则需要重新引用okhttp4.x。okhttp也是依赖自家的okio。
implementation("com.squareup.okhttp3:okhttp:4.11.0")
1.2 创建Retrofit实例
1.2.1 Retrofit
Retrofit( okhttp3.Call.Factory callFactory, HttpUrl baseUrl, List<Converter.Factory> converterFactories, List<CallAdapter.Factory> callAdapterFactories, @Nullable Executor callbackExecutor, boolean validateEagerly)
这是Retrofit的构造方法,同时Retrofit也提供了Builder来帮助构造,那么我们直接看Builder.build()方法,可以知道baseUrl是必填项,其余都是可选项。
/**
* Create the {@link Retrofit} instance using the configured values.
*
* <p>Note: If neither {@link #client} nor {@link #callFactory} is called a default {@link
* OkHttpClient} will be created and used.
*/
public Retrofit build() {
if (baseUrl == null) {
throw new IllegalStateException("Base URL required.");
}
okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
callFactory = new OkHttpClient();
}
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
callbackExecutor = platform.defaultCallbackExecutor();
}
// Make a defensive copy of the adapters and add the default Call adapter.
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
// Make a defensive copy of the converters.
List<Converter.Factory> converterFactories =
new ArrayList<>(
1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());
// Add the built-in converter factory first. This prevents overriding its behavior but also
// ensures correct behavior when using converters that consume all types.
converterFactories.add(new BuiltInConverters());
converterFactories.addAll(this.converterFactories);
converterFactories.addAll(platform.defaultConverterFactories());
return new Retrofit(
callFactory,
baseUrl,
unmodifiableList(converterFactories),
unmodifiableList(callAdapterFactories),
callbackExecutor,
validateEagerly);
}
所以最简单的创建实例方法即是以下代码:
Retrofit.Builder() .baseUrl("https://api.github.com/") .build()
1.2.2 OkHttpClient
为了代码复用性,比如统一请求头处理、统一加解密处理、统一日志处理等都要求我们配置新的OkHttpClient,因为这样我们才能用okhttp的拦截器来实现统一处理,然后通过Retrofit.Builder().client(OkHttpClient)传入配置。当然如果有更深入的改造甚至可以写一个callFactory传给Builder,retrofit或者okhttp在扩展性方面是做的非常好的。Retrofit默认使用OkHttpClient无参构造方法,如果要添加诸多配置需要使用OkHttpClient.Builder传参构造。
implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")val okHttpClient = OkHttpClient.Builder() .addInterceptor(HeaderInterceptor()) .addInterceptor(HttpLoggingInterceptor()) .addInterceptor(EncryptInterceptor()) .addInterceptor(DecryptInterceptor()) .readTimeout(60, TimeUnit.SECONDS) .writeTimeout(60, TimeUnit.SECONDS) .build() Retrofit.Builder() .client(okHttpClient) .baseUrl("https://api.github.com/") .addConverterFactory(FastJsonConverterFactory.create()) .build()
注意:logging-interceptor和okhttp最好保持一致,否则可能出现No virtual method log(ILjava/lang/String;Ljava/lang/Throwable;)。
有没有注意到retrofit创建实例多了一个addConverterFactory?下面我们来看下它的作用。
1.2.3 Converter
public interface Converter<F, T> {
@Nullable
T convert(F value) throws IOException;
abstract class Factory {
public @Nullable Converter<ResponseBody, ?> responseBodyConverter(
Type type, Annotation[] annotations, Retrofit retrofit) {
return null;
}
public @Nullable Converter<?, RequestBody> requestBodyConverter(
Type type,
Annotation[] parameterAnnotations,
Annotation[] methodAnnotations,
Retrofit retrofit) {
return null;
}
}
}
为什么需要这个Converter呢?从上面的方法中我们可以看出这就是RequestBody或者ResponseBody的转换。
首先我们应该知道,retrofit用的是okhttp发起请求的,那么okhttp的请求类是什么呢?
class Request internal constructor( @get:JvmName("url") val url: HttpUrl, @get:JvmName("method") val method: String, @get:JvmName("headers") val headers: Headers, @get:JvmName("body") val body: RequestBody?, internal val tags: Map<Class<*>, Any> )
当我们的请求body不为空的时候,创建Request需要传入RequestBody,而okhttp是retrofit持有的,也就是说RequestBody是由retrofit创建的,这时候我们应该想到Body注解,Body注解可以让我们传入自己定义的数据结构,但是这个数据结构retrofit不一定能识别出来,怎么办呢?Converter说一切有我,这就是Converter.requestBodyConverter方法实现的这一步骤。常用的数据结构retrofit已经提供了相应的Converter:
- Gson:
com.squareup.retrofit2:converter-gson
- Jackson:
com.squareup.retrofit2:converter-jackson
- Moshi:
com.squareup.retrofit2:converter-moshi
- Protobuf:
com.squareup.retrofit2:converter-protobuf
- Wire:
com.squareup.retrofit2:converter-wire
- Simple XML:
com.squareup.retrofit2:converter-simplexml
- JAXB:
com.squareup.retrofit2:converter-jaxb
- Scalars (primitives, boxed, and String):
com.squareup.retrofit2:converter-scalars
国内fastjson也有相应的Converter,相信GitHub上总能找到你需要的。fastjson2出现了,我们大概只需要将converter-fastjson-android的类替换一下fastjson2的包名就行了。
implementation('org.ligboy.retrofit2:converter-fastjson-android:2.1.0')
ResponseBody也是同理,ResponseBody是okhttp提供的,我们通常需要将之转换成我们方便使用的数据结构,所以还是由Converter出手解决。
那么能不能添加多个Converter呢?我们先从retrofit的角度看看:
public <T> Converter<T, RequestBody> nextRequestBodyConverter(
@Nullable Converter.Factory skipPast,
Type type,
Annotation[] parameterAnnotations,
Annotation[] methodAnnotations) {
Objects.requireNonNull(type, "type == null");
Objects.requireNonNull(parameterAnnotations, "parameterAnnotations == null");
Objects.requireNonNull(methodAnnotations, "methodAnnotations == null");
int start = converterFactories.indexOf(skipPast) + 1;
for (int i = start, count = converterFactories.size(); i < count; i++) {
Converter.Factory factory = converterFactories.get(i);
Converter<?, RequestBody> converter =
factory.requestBodyConverter(type, parameterAnnotations, methodAnnotations, this);
if (converter != null) {
//noinspection unchecked
return (Converter<T, RequestBody>) converter;
}
}
......
}
答案是可以的,但是前面的Converter一旦匹配正确就return了,所以我们不要添加相同类型的Converter,这是无效的,当然我们也不会有这种需求。但是添加多个Converter还有一个前提就是前面的Converter在遇到自身不匹配的数据类型时requestBodyConverter/responseBodyConverter需要return null,否则也是无法再执行下一个Converter的,比如converter-fastjson就没有return null,导致无法在其后再添加Converter了。
1.2.4 CallAdapter
1.3 定义接口
retrofit定义了很多注解来快速定义接口,下面将简单介绍GET和POST的定义。
1.3.1 GET
1. 请求路径:baseUrl/xxx/xxx
@GET("xxx/xxx") suspend fun getPages(): ResponseBody
2. 请求路径:baseUrl/xxx/xxx/page
@GET("xxx/xxx/{page}") suspend fun getPages(@Path("page") page: String): ResponseBody
3. 请求路径:baseUrl/xxx/xxx?page=
@GET("xxx/xxx") suspend fun getPages(@Query("page") page: String): ResponseBody
4. 请求路径:baseUrl/xxx/xxx?page=page&pageSize=pageSize
@GET("xxx/xxx") suspend fun getPages(@Query("page") page: String, @Query("pageSize") pageSize: String): ResponseBody @GET("xxx/xxx") suspend fun getPages(@QueryMap map: HashMap<String, String>): ResponseBody
5. 请求路径: baseUrl/xxx/xxx?urlencode(name)
注意:后台收到urlencode(name)后能否正常转码。
@GET("xxx/xxx") suspend fun getPages(@QueryName(encoded = true) name: String): ResponseBody
6. 下载:baseUrl/xxx
@Streaming
@GET
suspend fun download(@Url url: String): retrofit2.Response<ResponseBody>
1.3.2 POST
1. 请求路径:baseUrl/xxx
@FormUrlEncoded
@POST("xxx")
suspend fun getPages(@Field("page") page: String): ResponseBody
@FormUrlEncoded
@POST("xxx")
suspend fun getPages(@FieldMap map: HashMap<String, String>): ResponseBody
2. 请求路径:baseUrl/xxx
如果要传入@Body,需要提供转换器给retrofit,即addConverterFactory()。
@POST("xxx")
suspend fun getPages(@Body body: JSONObject): ResponseBody
3. 上传:baseUrl/xxx
@MultiPart
@POST("xxx")
suspend fun logs(@Part parts: List<MultipartBody.Part>): ResponseBody
1.4 调用接口
1.4.1 接口调用
mRetrofit.create(HttpService::class.java).getPages()
我们知道这个接口方法是通过注解是定义的,那么okhttp是如何开始请求的呢?首先我们注意retrofit.create方法:
public <T> T create(final Class<T> service) {
validateServiceInterface(service);
return (T)
Proxy.newProxyInstance(
service.getClassLoader(),
new Class<?>[] {service},
new InvocationHandler() {
private final Platform platform = Platform.get();
private final Object[] emptyArgs = new Object[0];
@Override
public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
args = args != null ? args : emptyArgs;
return platform.isDefaultMethod(method)
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args);
}
});
}
Retrofit.create主要创建HttpService的代理,通过代理执行方法。看loadServiceMethod:
ServiceMethod<?> loadServiceMethod(Method method) {
ServiceMethod<?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = ServiceMethod.parseAnnotations(this, method);
serviceMethodCache.put(method, result);
}
}
return result;
}
Service代理 invoke结果是由ServiceMethod.invoke提供,接着再看ServiceMethod:
abstract class ServiceMethod<T> {
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
Type returnType = method.getGenericReturnType();
if (Utils.hasUnresolvableType(returnType)) {
throw methodError(
method,
"Method return type must not include a type variable or wildcard: %s",
returnType);
}
if (returnType == void.class) {
throw methodError(method, "Service methods cannot return void.");
}
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}
abstract @Nullable T invoke(Object[] args);
}
这是一个抽象类,parseAnnotations是将注解解析处理,invoke是执行请求方法,这里有两个类需要注意:RequestFactory和HttpServiceMethod,以下是RequestFactory:
static RequestFactory parseAnnotations(Retrofit retrofit, Method method) { return new Builder(retrofit, method).build(); }RequestFactory(Builder builder) { method = builder.method; baseUrl = builder.retrofit.baseUrl; httpMethod = builder.httpMethod; relativeUrl = builder.relativeUrl; headers = builder.headers; contentType = builder.contentType; hasBody = builder.hasBody; isFormEncoded = builder.isFormEncoded; isMultipart = builder.isMultipart; parameterHandlers = builder.parameterHandlers; isKotlinSuspendFunction = builder.isKotlinSuspendFunction; }
从RequestFactory属性基本可以确认注解都在该类完成解析处理了。我们在RequestFactory类的Structure中看到create() 返回了okhttp3.Request,创建了Request就可以通过OkHttpClient.newCall(request).execute()发起请求了。
okhttp3.Request create(Object[] args) throws IOException {
@SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;
int argumentCount = args.length;
if (argumentCount != handlers.length) {
throw new IllegalArgumentException(
"Argument count ("
+ argumentCount
+ ") doesn't match expected count ("
+ handlers.length
+ ")");
}
RequestBuilder requestBuilder =
new RequestBuilder(
httpMethod,
baseUrl,
relativeUrl,
headers,
contentType,
hasBody,
isFormEncoded,
isMultipart);
if (isKotlinSuspendFunction) {
// The Continuation is the last parameter and the handlers array contains null at that index.
argumentCount--;
}
List<Object> argumentList = new ArrayList<>(argumentCount);
for (int p = 0; p < argumentCount; p++) {
argumentList.add(args[p]);
handlers[p].apply(requestBuilder, args[p]);
}
return requestBuilder.get().tag(Invocation.class, new Invocation(method, argumentList)).build();
}
而后RequestFactory作为参数传给了HttpServiceMethod.parseAnnotations(),同时HttpServiceMethod继承于ServiceMethod,那么invoke发起请求实现就在此类了,请求过程基本都可以在这里找到了,毕竟Service代理执行完invoke就结束了。
HttpServiceMethod(
RequestFactory requestFactory,
okhttp3.Call.Factory callFactory,
Converter<ResponseBody, ResponseT> responseConverter) {
this.requestFactory = requestFactory;
this.callFactory = callFactory;
this.responseConverter = responseConverter;
}
......
@Override
final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);
}
protected abstract @Nullable ReturnT adapt(Call<ResponseT> call, Object[] args);
static final class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {
private final CallAdapter<ResponseT, ReturnT> callAdapter;
CallAdapted(
RequestFactory requestFactory,
okhttp3.Call.Factory callFactory,
Converter<ResponseBody, ResponseT> responseConverter,
CallAdapter<ResponseT, ReturnT> callAdapter) {
super(requestFactory, callFactory, responseConverter);
this.callAdapter = callAdapter;
}
@Override
protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
return callAdapter.adapt(call);
}
}
adapt是一个抽象方法,那就有实现类,有三个:CallAdapted、SuspendForResponse、SuspendForBody,根据不同类型使用不同的adapt,最终发起okhttp请求,返回响应数据,再将响应数据转换成我们想要的类型。
1.4.2 OkHttpClient请求执行
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
override fun execute(): Response {
check(executed.compareAndSet(false, true)) { "Already Executed" }
timeout.enter()
callStart()
try {
client.dispatcher.executed(this)
return getResponseWithInterceptorChain()
} finally {
client.dispatcher.finished(this)
}
}
internal fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
val interceptors = mutableListOf<Interceptor>()
interceptors += client.interceptors
interceptors += RetryAndFollowUpInterceptor(client)
interceptors += BridgeInterceptor(client.cookieJar)
interceptors += CacheInterceptor(client.cache)
interceptors += ConnectInterceptor
if (!forWebSocket) {
interceptors += client.networkInterceptors
}
interceptors += CallServerInterceptor(forWebSocket)
val chain = RealInterceptorChain(
call = this,
interceptors = interceptors,
index = 0,
exchange = null,
request = originalRequest,
connectTimeoutMillis = client.connectTimeoutMillis,
readTimeoutMillis = client.readTimeoutMillis,
writeTimeoutMillis = client.writeTimeoutMillis
)
var calledNoMoreExchanges = false
try {
val response = chain.proceed(originalRequest)
if (isCanceled()) {
response.closeQuietly()
throw IOException("Canceled")
}
return response
} catch (e: IOException) {
calledNoMoreExchanges = true
throw noMoreExchanges(e) as Throwable
} finally {
if (!calledNoMoreExchanges) {
noMoreExchanges(null)
}
}
}
1.4.3 RealInterceptorChain拦截器责任链
override fun proceed(request: Request): Response {
check(index < interceptors.size)
calls++
if (exchange != null) {
check(exchange.finder.sameHostAndPort(request.url)) {
"network interceptor ${interceptors[index - 1]} must retain the same host and port"
}
check(calls == 1) {
"network interceptor ${interceptors[index - 1]} must call proceed() exactly once"
}
}
// Call the next interceptor in the chain.
val next = copy(index = index + 1, request = request)
val interceptor = interceptors[index]
@Suppress("USELESS_ELVIS")
val response = interceptor.intercept(next) ?: throw NullPointerException(
"interceptor $interceptor returned null")
if (exchange != null) {
check(index + 1 >= interceptors.size || next.calls == 1) {
"network interceptor $interceptor must call proceed() exactly once"
}
}
check(response.body != null) { "interceptor $interceptor returned a response with no body" }
return response
}
1.4.4 Interceptor拦截器
实现思路是通过Chain获取request或者response,然后对其进行重构再执行请求。
fun interface Interceptor {
@Throws(IOException::class)
fun intercept(chain: Chain): Response
companion object {
inline operator fun invoke(crossinline block: (chain: Chain) -> Response): Interceptor =
Interceptor { block(it) }
}
}
1.4.5 HeaderInterceptor
class HeaderInterceptor: Interceptor { override fun intercept(chain: Interceptor.Chain): Response { var request = chain.request() request = request.newBuilder() .addHeader("key", "value") .build() return chain.proceed(request) } }
1.4.6 EncryptInterceptor
class EncryptInterceptor: Interceptor { override fun intercept(chain: Interceptor.Chain): Response { var request = chain.request() var charset = Charset.forName("UTF-8") val reqBody = request.body reqBody?.run { try { contentType()?.let { charset = it.charset(charset) } val buffer = Buffer() writeTo(buffer) // 请求参数 val reqData = URLDecoder.decode(buffer.readString(charset).trim(), "utf-8") val resData = 加密 request = request.newBuilder() .post(resData.trim().toRequestBody(contentType())) .build() } catch (e: Exception) { e.printStackTrace() } } return chain.proceed(request) } }
1.4.7 DecryptInterceptor
class DecryptInterceptor: Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() var response = chain.proceed(request) val resBody = response.body resBody?.run { try { val source = source() source.request(Long.MAX_VALUE) val buffer = source.buffer var charset = Charset.forName("UTF-8") contentType()?.let { charset = it.charset(charset) } val resData = buffer.clone().readString(charset) if (TextUtils.isEmpty(resData)) { return response } // 解密 val decryptStr = 解密 response = response.newBuilder() .body(decryptStr.toResponseBody(contentType())) .build() } catch (e: Exception) { e.printStackTrace() } } return response } }
OKhttp网络请求结果是由责任链执行返回的,而责任链是由拦截器列表组成,所以添加拦截器可以在请求过程中的进行统一处理。