1 a brief introduction
A Type-safe HTTP client for Android and Java. Retrofit encapsulates RESTful HTTP network request frameworks. Its underlying implementation is still based on OkHttp, a modification of OkHttp used on Android.
// This is an example
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
GitHubService service = retrofit.create(GitHubService.class);
Call<List<Repo>> repos = service.listRepos("halloayu");
repos.enqueue(new Callback<List<Repo>>() {
@Override
public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
Log.e("Main", response.body().get(0).toString());
}
@Override
public void onFailure(Call<List<Repo>> call, Throwable t) {}});Copy the code
2 Why is Retrofit a framework
OkHttp is the official Web request framework for Android. It is very powerful, but it has a number of disadvantages, including the following.
- OkHtt can be tedious to configure network requests, especially when configuring complex network request bodies, headers, and parameters.
- The data parsing process requires the user to manually get ResponseBody for parsing, which is difficult to reuse.
- Thread switching cannot be done automatically.
- If we had nested network requests, we would be in callback hell.
So to address these shortcomings and make web requests easy to use for Android development, Retrofit was born, which is essentially a shell on top of OkHttp, with requests still implemented on OkHttp. The purpose of Retrofit is twofold.
- Request before
- Network request headers can be configured uniformly
- You can easily reuse a Request or create a Request
- After the request
- The child thread needs to switch to the UI thread for interface refresh
- The returned data can be parsed into Java beans for easy use
3 Main points of encapsulation
Retrofit encapsulates OkHttp in the following ways:
- Build mode creates the network request basic configuration
- Compose HTTP web requests with annotation classes
- provide
Gson
Parsed returnedjson
data - Executor completes the thread switch
The core of its request implementation is “annotation”, “dynamic proxy”, “reflection”.
4 annotations
Http Has eight types of network requests: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, and Http
Retrofit implements these requests through a combination of more than 20 annotations.
5 Dynamic Proxy
Anyone familiar with design patterns knows that the idea of the agency pattern is to have an “agent” through which the “user” can handle the “difficult” things. Create (GitHubService) service = retrofit.create(githubService.class), which is a dynamic code. So let’s see what we did.
public <T> T create(final Class<T> service) {
// Validate the interface
validateServiceInterface(service);
// Dynamic proxy
return (T)
Proxy.newProxyInstance(
service.getClassLoader(),
newClass<? >[] {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;
returnplatform.isDefaultMethod(method) ? platform.invokeDefaultMethod(method, service, proxy, args) : loadServiceMethod(method).invoke(args); }}); }Copy the code
As we can see from the code, it proxies all the interface methods in the incoming interface class. When we call service.listRepos(“halloayu”), we invoke the invoke method in InvocationHandler.
// Get from cache, not get, ServiceMethod for processingServiceMethod<? > 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;
}
Copy the code
ServiceMethod is like a central processing unit, passing in Retrofit objects and Method objects, calling various interfaces and parsers, and ultimately generating a Request, This includes the API’s domain name, path, HTTP request method, request header, whether there is a body, whether there is a multipart, and so on. Finally, a Call object is returned. The default implementation of the Call interface is OkHttpCall, which by default uses OkHttp3 as the underlying HTTP request client.
@Override
final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
// Use the adapter to convert the returned data
return adapt(call, args);
}
Copy the code
The purpose of using Java dynamic proxies is to intercept the Java method being called, then parse the annotations of that Java method, and finally generate a Request that OkHttp sends.
// asynchronous request source code
@Override
public void enqueue(final Callback<T> callback) {
Objects.requireNonNull(callback, "callback == null"); . call.enqueue(new okhttp3.Callback() {
@Override
public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
Response<T> response;
try {
// Parse the returned data with our configured GSON
response = parseResponse(rawResponse);
} catch (Throwable e) {
throwIfFatal(e);
callFailure(e);
return;
}
try {
// First we define the adapter to convert the data, then we go to the custom callback
callback.onResponse(OkHttpCall.this, response);
} catch (Throwable t) {
throwIfFatal(t);
t.printStackTrace(); // TODO this is not great}}@Override
public void onFailure(okhttp3.Call call, IOException e) {
callFailure(e);
}
private void callFailure(Throwable e) {
try {
callback.onFailure(OkHttpCall.this, e);
} catch (Throwable t) {
throwIfFatal(t);
t.printStackTrace(); // TODO this is not great}}}); }Copy the code
6 Thread Switching
Retrofit also implements subthread to main thread switching based on handlers.
static final class Android extends Platform {
Android() {
super(Build.VERSION.SDK_INT >= 24);
}
@Override
public Executor defaultCallbackExecutor(a) {
return new MainThreadExecutor();
}
@Nullable
@Override
Object invokeDefaultMethod( Method method, Class
declaringClass, Object object, Object... args) throws Throwable {
if (Build.VERSION.SDK_INT < 26) {
throw new UnsupportedOperationException(
"Calling default methods on API 24 and 25 is not supported");
}
return super.invokeDefaultMethod(method, declaringClass, object, args);
}
static final class MainThreadExecutor implements Executor {
// Handler
private final Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable r) { handler.post(r); }}}Copy the code
Request, go to the default thread switching adapter DefaultCallAdapterFactory, then go custom callback.
@Override
public void enqueue(final Callback<T> callback) {
Objects.requireNonNull(callback, "callback == null");
// Switch threads
delegate.enqueue(
new Callback<T>() {
@Override
public void onResponse(Call<T> call, final Response<T> response) {
callbackExecutor.execute(
() -> {
if (delegate.isCanceled()) {
// Emulate OkHttp's behavior of throwing/delivering an IOException on
// cancellation.
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)); }}); }Copy the code
7 summary
Retrofit’s source code is excellent, combining HTTP requests in annotated form, handling the logic of the request through a proxy interface, and finally executing the request. It also makes extensive use of reflection, factory methods, and abstract interfaces to achieve low coupling and high scalability, a very readable open source framework!