Android Network Request 4--Parsing Retrofit Source Code

Articles Catalogue

1. Introduction to Retrofit

Retrofit is another effort of Square, which follows Restful design style and is based on OkHttp for the framework of Android network requests.

He compares other frameworks

  • Best performance
  • High encapsulation degree and poor expandability
  • The introduction is easy to use and the code is simple.
  • Complete decoupling
  • It's very convenient to connect with RxJava

2. Retrofit usage (asynchronous)

2.1 Adding Dependencies

You can find the latest version number in the Retrofit Github library page. How to import the latest version when I write this blog

Add it to build.gradle of your app project
implementation 'com.squareup.retrofit2:retrofit:2.6.1'

At the same time, if you need a matching data converter, you need to import the following dependencies

  • 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
  • Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars

2.2 Adding Network Permissions

Add it to your APP Android Manifest. XML
<uses-permission android:name="android.permission.INTERNET"/>

2.3 Create a class for receiving server to return data

Reception.java

puublic class Reception {
    // Definition based on the format of the returned data and the way the data is parsed
    ...
}

2.4 Create an interface for describing network requests

GetRequest_Interface.interface

public interface GetRequest_Interface {
    @GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car")
    Call<Translation>  getCall();
    // @ The role of GET annotations: sending network requests using Get method
 
    // getCall() = Method of receiving network request data
    // Where the return type is Call <*>,* is the class that receives the data (that is, the Translation class defined above)
    // If you want to get the content in Responsebody directly, you can define the network request return value as Call < ResponseBody >
}

2.5 Create Retrofit instances

 Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://fanyi.youdao.com/") // Setting the Url address of the network request
                .addConverterFactory(GsonConverterFactory.create()) // Setting up a data parser
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // Support for Rx Java Platform
                .build();

2.6 Creating an Example of Network Request Interface

// Create an instance of a network request interface
GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);

//Encapsulation of sending requests
Call<Reception> call = request.getCall();

2.7 Send Network Request

//Sending network requests (asynchronous)
call.enqueue(new Callback<Translation>() {
    //Callback on successful request
    @Override
    public void onResponse(Call<Translation> call, Response<Translation> response) {
        //Request processing, output results
        response.body().show();
    }

    //Callback in case of request failure
    @Override
    public void onFailure(Call<Translation> call, Throwable throwable) {
        System.out.println("connection failed");
    }
});

2.8 Processing Return Data

//Sending network requests (asynchronous)
call.enqueue(new Callback<Translation>() {
    //Callback on successful request
    @Override
    public void onResponse(Call<Translation> call, Response<Translation> response) {
        // Processing returned data
        response.body().show();
    }

    //Callback in case of request failure
    @Override
    public void onFailure(Call<Translation> call, Throwable throwable) {
        System.out.println("connection failed");
    }
});

As the core of this article is not about usage, so I do not say much about usage. If you want to know more, you can see this blog: This is a very detailed Retrofit 2.0 usage tutorial (with examples)

3. Retrofit source code

3.1 Retrofit object construction source code

The source code for a framework begins with where it is used. Let's first look at the creation code for Retrofit:

 Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://fanyi.youdao.com/")
        .addConverterFactory(GsonConverterFactory.create())
        .build();

We can divide it into five parts to analyze the source code:

Let's start with the first step.

Step 1: Retrofit class

public final class Retrofit {
    private final Map<Method, ServiceMethod<?>> serviceMethodCache = new ConcurrentHashMap<>();
  
    // Network Request Factory
    final okhttp3.Call.Factory callFactory;
    // The Url address of the request
    final HttpUrl baseUrl;
    // Collection of data converter factories
    final List<Converter.Factory> converterFactories;
    // Collection of network request adapter factories
    final List<CallAdapter.Factory> callAdapterFactories;
    // Callback
    final @Nullable Executor callbackExecutor;
    // Are annotations in the business interface validated in advance?
    final boolean validateEagerly;

    Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl,
        List<Converter.Factory> converterFactories, List<CallAdapter.Factory> callAdapterFactories,
        @Nullable Executor callbackExecutor, boolean validateEagerly) {
        this.callFactory = callFactory;
        this.baseUrl = baseUrl;
        this.converterFactories = converterFactories; // Copy+unmodifiable at call site.
        this.callAdapterFactories = callAdapterFactories; // Copy+unmodifiable at call site.
        this.callbackExecutor = callbackExecutor;
        this.validateEagerly = validateEagerly;
    }
    ...// Omit the following code
}

As you can see, the construction of Retrofit requires one-time preparation of all the required data.
And there are many factories in this area, which use factory mode. Let's talk about this later. Next, let's look at step 2:

Step 2: Builder() Method

Let's first look at the source code of the Builder() method:

public Builder() {
    this(Platform.get());
}

The Rlatform.get() method is called here:

static Platform get() {
    return PLATFORM;
}

The definition of PLATFORM is as follows:

private static final Platform PLATFORM = findPlatform();

Let's look again at the findPlatform() method:

private static Platform findPlatform() {
    try {
        Class.forName("android.os.Build");
        if (Build.VERSION.SDK_INT != 0) {
            return new Android();
        }
    } catch (ClassNotFoundException ignored) {
    }
    return new Platform(true);
}

This creates an Android object and then returns the Platform object created by the incoming true. Let's look at the Android class again:

static final class Android extends Platform {
    Android() {
        super(Build.VERSION.SDK_INT >= 24);
    }

    @Override public Executor defaultCallbackExecutor() {
        // Returns a default callback method executor
        // The function of the executor is to switch threads (sub-> main threads) and execute callback methods in the main threads (UI threads).
        return new MainThreadExecutor();
    }

    static class MainThreadExecutor implements Executor {
        // Get the Handler bound to the Android main thread 
        private final Handler handler = new Handler(Looper.getMainLooper());

        @Override public void execute(Runnable r) {
            // The Handler is the Handler acquired above that binds to the Android main thread 
            // In the UI thread, the network request is returned to the data processing and other operations.
            handler.post(r);
        }
    }
}

Step 3: baseUrl() method

public Builder baseUrl(String baseUrl) {
    Objects.requireNonNull(baseUrl, "baseUrl == null");
    return baseUrl(HttpUrl.get(baseUrl));
}

Finally, a baseUrl() method is called. Let's take a look at the baseUrl() method:

public Builder baseUrl(HttpUrl baseUrl) {
    Objects.requireNonNull(baseUrl, "baseUrl == null");
    // Partition of URL parameters into several path fragments
    List<String> pathSegments = baseUrl.pathSegments();
    // Check the last fragment to see if the URL parameter ends with "/" or throws an exception	
    if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
        throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
    }
    this.baseUrl = baseUrl;
    return this;
}

Step 3 is used to set up Url for network requests

Step 4: addConverterFactory() method

Let's first look at the GsonConverterFactory.create() method:

public static GsonConverterFactory create() {
    return create(new Gson());
}

Another create() method is called:

public static GsonConverterFactory create(Gson gson) {
    if (gson == null) throw new NullPointerException("gson == null");
    return new GsonConverterFactory(gson);
}

If the incoming gson is empty, an exception is reported, and if it is not empty, the construction method of GsonConverterFactory is called:

private GsonConverterFactory(Gson gson) {
    this.gson = gson;
}

So the essence of this method is to return a gson object to the addConverterFactory() method, so let's look at this method again:

/** Add a converter factory for serialization and deserialization of objects. */
public Builder addConverterFactory(Converter.Factory factory) {
    converterFactories.add(Objects.requireNonNull(factory, "factory == null"));
    return this;
}

Step 4 creates a GsonConverterFactory object with a Gson instance and puts it in the ConverterFactory.

3.1.5 Step 5: build() Method

public Retrofit build() {
    if (baseUrl == null) {
        throw new IllegalStateException("Base URL required.");
    }
    // Configure the network request executor
    okhttp3.Call.Factory callFactory = this.callFactory;
    // If no callFactory is specified, create OkHttpClient
    if (callFactory == null) {
        callFactory = new OkHttpClient();
    }
    // Configure callback actuator
    Executor callbackExecutor = this.callbackExecutor;
    if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
    }

    // Configure the Network Request Adapter Factory
    List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
    callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));

    // Configuration Data Converter Factory
    List<Converter.Factory> converterFactories = new ArrayList<>(
        1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());

    // First add the built-in converter factory. This prevents overwriting its behavior, but also ensures that it behaves correctly when using all types of converters.
    converterFactories.add(new BuiltInConverters());
    converterFactories.addAll(this.converterFactories);
    converterFactories.addAll(platform.defaultConverterFactories());

    return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
        unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
}

According to the variables previously configured, all the variables of Retrofit are configured, and finally the creation of Retrofit instance is completed.

This is the end of the Creation of Retrofit.
Retrofit creates a Retrofit instance through the builder pattern:

  • Request factory callFactory: OkHttpClient by default
  • Data Converter Factories
  • CallAdapter Factories: The default is Executor CallAdapter Factory
  • Callback Executor

3.2 Example of Creating Network Request Interface

The general method of creation is as follows:

<!-- Reception.java -->
puublic class Reception {
    // Definition based on the format of the returned data and the way the data is parsed
    ...
}

<!-- GetRequest_Interface.interface -->
public interface GetRequest_Interface {
    @GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car")
    Call<Translation>  getCall();
    // @ The role of GET annotations: sending network requests using Get method
 
    // getCall() = Method of receiving network request data
    // Where the return type is Call <*>,* is the class that receives the data (that is, the Translation class defined above)
    // If you want to get the content in Responsebody directly, you can define the network request return value as Call < ResponseBody >
}

<!-- MainActivity.java -->
// Create an instance of a network request interface
GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);
//Encapsulation of sending requests
Call<Reception> call = request.getCall();

Let's first look at what retrofit.create() did:

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 an object, it is deferred to the normal call.
                if (method.getDeclaringClass() == Object.class) {
                    return method.invoke(this, args);
                }
                if (platform.isDefaultMethod(method)) {
                    return platform.invokeDefaultMethod(method, service, proxy, args);
                }
                return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
            }
        });
}

First, we call the validateServiceInterface() method. Let's look at this method:

private void validateServiceInterface(Class<?> service) {
    // Throw an exception if it's not an interface
    if (!service.isInterface()) {
        throw new IllegalArgumentException("API declarations must be interfaces.");
    }

    // An empty two-way queue with a capacity lower limit of 1 is constructed to store data.
    Deque<Class<?>> check = new ArrayDeque<>(1);
    // Add to the queue
    check.add(service);
    while (!check.isEmpty()) {
        // Remove the first forward element of the queue
        Class<?> candidate = check.removeFirst();
        // The purpose of getTypeParameters() is to get the generic type and enter it if the number of generic types is not zero.
        if (candidate.getTypeParameters().length != 0) {
            StringBuilder message = new StringBuilder("Type parameters are unsupported on ")
                .append(candidate.getName());
            if (candidate != service) {
                message.append(" which is an interface of ")
                    .append(service.getName());
            }
            throw new IllegalArgumentException(message.toString());
        }
        Collections.addAll(check, candidate.getInterfaces());
    }

    if (validateEagerly) {
        Platform platform = Platform.get();
        for (Method method : service.getDeclaredMethods()) {
            if (!platform.isDefaultMethod(method) && !Modifier.isStatic(method.getModifiers())) {
                loadServiceMethod(method);
            }
        }
    }
}

Get Platform, then traverse the methods in the interface, calling the loadService Method () method:

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;
}

This method mainly stores methods converted to Service Method type through service MethodCache.

Let's look back at InvocationHandler's invoke() method.

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 an object, it is deferred to the normal call.
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(this, args);
        }
        if (platform.isDefaultMethod(method)) {
            return platform.invokeDefaultMethod(method, service, proxy, args);
        }
        return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
    }
});

Looking at the final return, you can see that he calls a loadServiceMethod() method, which, as I said earlier, basically returns a ServiceMethod. Then let's look at the invoke() method:
After pressing command + left mouse button to enter, the display is:

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);
}

This invoke() is an abstract method at all. Then I went back to see if any class inherited from ServiceMethod implemented this method. I couldn't find it. Finally, I thought that Android Studio could command + left-click to find out where to implement this class. So I clicked on this class and found the HttpServiceMethod class successfully, which is the only subset of ServiceMethod that implements the invoke() method:

@Override final @Nullable ReturnT invoke(Object[] args) {
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    return adapt(call, args);
}

As you can see, he created an OkHttpCall object, which is not detailed here. You can see a blog about OkHttp: Android Network Request 3 - Parsing OkHttp Source . Then we call the adapt method. Let's look at this method:

protected abstract @Nullable ReturnT adapt(Call<ResponseT> call, Object[] args);

This is another Abstract method. But the good thing is that we have found his realization in the following way:

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);
    }
}

But he returned to the adaptation () method of an implementation class of the CallAdapter interface.

So, I don't understand this piece. I don't know how he actually implemented this method. I read Android Advanced Light and many other blogs. They are all different from the old version of Retroofit, create() method, and call adapt() method.

3.3 Network Request

The Retrofit of web requests is actually implemented with OkHttp, so I won't say much about it. You can read my blog before: Android Network Request 3 - Parsing OkHttp Source.

Keywords: Retrofit network Android OkHttp

Added by Alanmoss on Tue, 06 Aug 2019 12:53:25 +0300