Retrofit simplifies network implementation by repackaging OkHttp.
1. Code usage (source code example SimpleService)
The workflow is simple in four steps:
- Create a Retrofit object
- Create the request interface proxy object
- Reflection generates an OkhttpCall object
- Request and parse ResponseBody returns
Retrofit retrofit =
new Retrofit.Builder()
.baseUrl(API_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
// Create an instance of our GitHub API interface.
GitHub github = retrofit.create(GitHub.class);
// Create a call instance for looking up Retrofit contributors.
Call<List<Contributor>> call = github.contributors("square"."retrofit");
List<Contributor> contributors = call.execute().body()
Copy the code
2. Create a Retrofit object
The Builder pattern creates Retrofit objects
fucntion | description |
---|---|
client | Specify the custom OkHttpClient to create |
callFactory | Specify the custom invocation factory to create |
baseUrl | Specifies that the base url to be created generally contains a protocol part, a domain name part, and a port part |
addConverterFactory | Add converter factories for object serialization and deserialization |
addCallAdapterFactory | Add a call adapter factory that supports service method return types |
callbackExecutor | Add a thread pool for call execution |
validateEagerly | Instantiate the interface object to immediately validate the configuration of all methods in the supplied interface |
3. Create a request interface proxy object
ValidateServiceInterface checks if the interface class and its parents are generic, and throws exceptions if they are. Using the Proxy request interface class, create an instance object of InvocationHandler, which is used to handle all request method calls of the Proxy class
public <T> T create(final Class<T> service) {
validateServiceInterface(service);
return(T) Proxy.newProxyInstance( service.getClassLoader(), new Class<? >[] {service}, new InvocationHandler() {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;
Platform platform = Platform.get(a);returnplatform.isDefaultMethod(method) ? platform.invokeDefaultMethod(method, service, proxy, args) : loadServiceMethod(method).invoke(args); }}); }Copy the code
The platform is android29, so the instance returned by the get method is Android24
private static final class Android24 extends Platform {
static boolean isSupported() {
return Build.VERSION.SDK_INT >= 24;
}
private @Nullable Constructor<Lookup> lookupConstructor;
@Override
Executor defaultCallbackExecutor() {
return MainThreadExecutor.INSTANCE;
}
@Override
List<? extends CallAdapter.Factory> createDefaultCallAdapterFactories(
@Nullable Executor callbackExecutor) {
returnasList( new CompletableFutureCallAdapterFactory(), new DefaultCallAdapterFactory(callbackExecutor)); }... }Copy the code
4. Generate OkHttpCall objects
- The reflection resolves the interface method annotation, and the parameter annotation generates the CallStuck
- The invoke method is called to generate the OkHttpCall
- Create the ExecutorCallbackCall agent OkHttpCall
4.1 Reflection parsing Interface method annotations Parameter annotations get the request type and URLpath, etc
Need to get the following information for the use of Retrofit method annotations array methodAnnotations [7], parameterTypes parameter type array, the array parameter annotation parameterAnnotationsArray
Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
this.methodAnnotations = method.getAnnotations();
this.parameterTypes = method.getGenericParameterTypes();
this.parameterAnnotationsArray = method.getParameterAnnotations();
}
Copy the code
[9-10] Parse the method annotation to get the request type httpMethod and url path relativeUrl
private void parseMethodAnnotation(Annotation annotation) {...else if (annotation instanceof GET) {
parseHttpMethodAndPath("GET", ((GET) annotation).value(), false); }... }Copy the code
[11] The re matches the URL to get a set of PATH strings assigned to relativeUrlParamNames
static Set<String> parsePathParameters(String path) {
Matcher m = PARAM_URL_REGEX.matcher(path);
Set<String> patterns = new LinkedHashSet<>();
while (m.find()) {
patterns.add(m.group(1));
}
return patterns;
}
Copy the code
[8] The generated method parameterHandler is then used to replace the path in the relativeUrl
RequestFactory build() { ... int parameterCount = parameterAnnotationsArray.length; parameterHandlers = new ParameterHandler<? >[parameterCount];for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) { parameterHandlers[p] = parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter); }...return new RequestFactory(this);
}
Copy the code
[12-22] Generate the Call Component based on the Platform.Android24 and retrofit. Build method. Create defaultCallAdapterFactory. The get () callAdapter object and GsonResponseBodyConverter responseBodyConverter GsonResponseBodyConverte The r object callFactory is created with okhttpClient by default
abstract class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> { static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations( Retrofit retrofit, Method method, RequestFactory requestFactory) { ... CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method, adapterType, annotations); . Converter<ResponseBody, ResponseT> responseConverter = createResponseConverter(retrofit, method, responseType); okhttp3.Call.Factory callFactory = retrofit.callFactory;if(! isKotlinSuspendFunction) {returnnew CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter); }... }}Copy the code
4.3 Invoking the Invoke method generates an OkHttpCall
static final class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {...@Override
protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
return callAdapter.adapt(call);
}
@Override
final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
returnadapt(call, args); }}Copy the code
4.3 Creating an ExecutorCallbackCall agent OkHttpCall
Platform. The Android24 created a MainThreadExecutor. The INSTANCE, so adapt generates ExecutorCallbackCall object
final class DefaultCallAdapterFactory extends CallAdapter.Factory {
private final @NullableExecutor callbackExecutor; .@Override
public @NullableCallAdapter<? ,? >get(
Type returnType, Annotation[] annotations, Retrofit retrofit) {
if(getRawType(returnType) ! = Call.class) {
return null;
}
if(! (returnType instanceof ParameterizedType)) {throw new IllegalArgumentException(
"Call return type must be parameterized as Call<Foo> or Call<? extends Foo>");
}
final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);
final Executor executor =
Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)?null
: callbackExecutor;
returnnew CallAdapter<Object, Call<? > > () {@Override
public Type responseType() {
return responseType;
}
@Override
public Call<Object> adapt(Call<Object> call) {
return executor == null? call : new ExecutorCallbackCall<>(executor, call); }}; }}Copy the code
Android normally performs UI operations on the UI thread, but OKHTTP does not have this capability, so the author uses static proxy mode to implement the enqueue callback from the child thread to the UI thread
static final class ExecutorCallbackCall<T> implements Call<T> {...@Override
public void enqueue(final Callback<T> callback) {
Objects.requireNonNull(callback, "callback == null");
delegate.enqueue(
new Callback<T>() {
@Override
public void onResponse(Call<T> call, final Response<T> response) {
callbackExecutor.execute(
() -> {
if (delegate.isCanceled()) {
callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
} else {
callback.onResponse(ExecutorCallbackCall.this, response); }}); }@Override
public void onFailure(Call<T> call, final Throwable t) {
callbackExecutor.execute(() -> callback.onFailure(ExecutorCallbackCall.this, t)); }}); }...@Override
public Response<T> execute() throws IOException {
returndelegate.execute(); }... }Copy the code
We currently store the following values in the RequestFactory in addition to generating the ExecutorCallbackCall object
Parameter names | Source way | value |
---|---|---|
httpMethod | Interface method annotation name | GET |
baseUrl | Custom incoming | api.github.com/ |
relativeUrl | Interface method annotation value | /repos/{owner}/{repo}/contributors |
ParameterHandler | RequestFactory method parseParameterAnnotation | Two ParameterHandler$Path name parameters, one owner and one repO |
5. Request and parse ResponseBody return
- Generate an okHttp3. Request object and call the OkhttpClient Request
- Parse the ResponseBody using the responseConverter generated earlier
5.1 Invoking the OkhttpClient Request, invoking the OkhttpClient request
final class OkHttpCall<T> implements Call<T> {...@Override
public Response<T> execute() throws IOException {
okhttp3.Call call;
synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
call = getRawCall();
}
if (canceled) {
call.cancel();
}
return parseResponse(call.execute());
}
private okhttp3.Call createRawCall() throws IOException {
okhttp3.Call call = callFactory.newCall(requestFactory.create(args));
if (call == null) {
throw new NullPointerException("Call.Factory returned null.");
}
returncall; }... }Copy the code
5.2 analytical ResponseBody Gson
final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final Gson gson;
private final TypeAdapter<T> adapter;
GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override
public T convert(ResponseBody value) throws IOException {
JsonReader jsonReader = gson.newJsonReader(value.charStream());
try {
T result = adapter.read(jsonReader);
if(jsonReader.peek() ! = JsonToken.END_DOCUMENT) {throw new JsonIOException("JSON document was not fully consumed.");
}
return result;
} finally{ value.close(); }}}Copy the code