Retrofit2 basic use

summary

Retrofit It is currently the most popular Android network request library. To be exact, retro fit is a RESTful encapsulation of HTTP network request framework, because its internal network requests are essentially completed through OkHttp, while retro fit is only responsible for the encapsulation of network request interfaces. Specifically, the client requests the network through Retrofit. In fact, it encapsulates the request parameters, header, url and other information through the interface layer of Retrofit, and then OkHttp completes the subsequent request work. Then, after the server returns the data, OkHttp passes the original result to Retrofit, which parses and calls back the result to the client according to the relevant configuration of the client

Basic use

1, Get request

In the process of using Retrofit, the interface layer needs to be defined. Each method in the interface layer identifies an independent request. The interface layer ApiService is defined as follows

public interface ApiService {

    @GET("users/list")
    Call<List<User>> getUsers();
}

@The Get annotation acts on the method, indicating that this is a Get request. The value value of the @ Get annotation and the baseUrl configured in Retrofit form a complete request url. List < user > represents the response result type. The above request is completed through the Retrofit request

        // Building Retrofit objects in builder mode
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://xxx/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        // Create the proxy object of the interface layer, and create the proxy object of ApiService through dynamic proxy internally  
        ApiService api = retrofit.create(ApiService.class);
        // Execute asynchronous request
        api.getUsers().enqueue(new Callback<List<User>>() {
            @Override
            public void onResponse(Call<List<User>> call, Response<List<User>> response) {
                // Processing results
            }

            @Override
            public void onFailure(Call<List<User>> call, Throwable t) {
                // Handling exceptions
            }
        });

We know that the original request response result is actually ResponseBody, so here we declare that the response data type is list < user >, and how is Retrofit implemented internally? In fact, we complete the conversion of response results through the converter GsonConverterFactory passed in when we build Retrofit

2, Dynamic url access @ PATH

For example, I'm going to visit a url like this now https://api.example.com/user/1 Then the interface layer is defined as follows:

public interface ApiService {

    @GET("user/{id}")
    Call<User> getUser(@Path("id") String id);
}

@{id} is used as a placeholder in the Get annotation. The actual operation will be replaced by the parameters marked in the @ Path("id") annotation in the method. The access case is as follows:

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://xxx/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        ApiService api = retrofit.create(ApiService.class);
        api.getUser("1").enqueue(new Callback<User>() {
            @Override
            public void onResponse(Call<User> call, Response<User> response) {
              
            }

            @Override
            public void onFailure(Call<User> call, Throwable t) {
             
            }
        });

3, Get request with Query parameter setting @ Query

For example, we want to access such a url https://api.example.com/users?username=zhangsan The interface layer is defined as follows:

public interface ApiService {

    @GET("users")
    Call<User> getUserByUserName(@Query("username") String username);
}

The request method is indicated by @ Get, and the parameter name of the request is marked by @ Query. The same applies to Post requests. Just replace the @ Get annotation with @ Post. The access case is as follows:

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://xxx/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        ApiService api = retrofit.create(ApiService.class);
        api.getUserByUserName("zhangsan").enqueue(new Callback<User>() {
            @Override
            public void onResponse(Call<User> call, Response<User> response) {
              
            }

            @Override
            public void onFailure(Call<User> call, Throwable t) {

            }
        });

4, Pass the json string @ Body to the server in the form of POST request Body

In the normal interface interaction with the server, we usually directly encapsulate the request parameters into a json string and pass it to the server. How to implement it through Retrofit? The interface layer is defined as follows:

public interface ApiService {

    @POST("users/add")
    Call<List<User>> addUser(@Body User user);
}

You can see that we can mark the parameter object through the @ Body annotation, and then the User object is converted into json string inside Retrofit and passed to the server. The access case is as follows:

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://xxx/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        ApiService api = retrofit.create(ApiService.class);
        User user = new User("zhangsan",24,"zhangsan@163.com")
        api.addUser(user).enqueue(new Callback<User>() {
            @Override
            public void onResponse(Call<List<User>> call, Response<User> response) {
                
            }

            @Override
            public void onFailure(Call<List<User>> call, Throwable t) {
              
            }
        });

Retrofit can only accept the RequestBody type of @ Body by default, so we passed in a User object here. How does retrofit convert internally? In fact, we still use the converter GsonConverterFactory mentioned above.

5, Pass the key value pair @ FormUrlEncoded as a form

Form submission is very common in Web development, so how to implement a request for form submission through Retrofit? The interface layer is defined as follows:

public interface ApiService {

    @POST("login")
    @FormUrlEncoded
    Call<List<User>> login(@Field("username") String username,@Field("password") String password);
}

Indicate the url through @ POST, add @ FormUrlEncoded, and then add parameters through @ Field. The visit cases are as follows:

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://xxx/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        ApiService api = retrofit.create(ApiService.class);
        api.login("zhangsan","123456").enqueue(new Callback<User>() {
            @Override
            public void onResponse(Call<User> call, Response<User> response) {
                // Processing results
            }

            @Override
            public void onFailure(Call<User> call, Throwable t) {
                // Handling exceptions
            }
        });

6, Single file @ Multipart

Single file upload is realized through Retrofit. The interface layer is defined as follows:

public interface ApiService {

    @Multipart
    @POST("register")
    Call<User> register(@Part MultipartBody.Part header, @Part("username") RequestBody username,@Part("password") RequestBody password);
}

Here @ MultiPart means to allow multiple @ parts. We use three @ parts here. The first one is to upload a file and use multipartbody Part type, and the other two are simple key value pairs. The visit cases are as follows:

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://xxx/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        ApiService api = retrofit.create(ApiService.class);
        // Create a user avatar file that needs to be uploaded to the server
        File file = new File(Environment.getExternalStorageDirectory(), "photo.png");
        RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), file);
        MultipartBody.Part photo = MultipartBody.Part.createFormData("photo", file.getName(), photoRequestBody);
        ReqeustBody usernameRequestBody = RequestBody.create(null, "zhangsan");
        ReqeustBody passwordRequestBody = RequestBody.create(null, "123456");
        api.registerUser(photo, usernameRequestBody,passwordRequestBody).enqueue(new Callback<User>() {
            @Override
            public void onResponse(Call<User> call, Response<User> response) {
                // Processing results
            }

            @Override
            public void onFailure(Call<User> call, Throwable t) {
                // Handling exceptions
            }
        });

7, Multi file upload @ PartMap

Retrofit realizes multi file upload. The interface layer is defined as follows:

public interface ApiService {

    @Multipart
    @POST("register")
    Call<User> register(@PartMap Map<String, RequestBody> params, @Part("password") RequestBody password);
}

A new annotation @ PartMap is used here. This annotation is used to identify a Map. The key of the Map is of String type, which represents the key of the uploaded key value pair (corresponding to the key accepted by the server). The value is RequestBody, which is a bit similar to the encapsulated version of @ Part. The visit cases are as follows:

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://xxx")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        ApiService api = retrofit.create(ApiService.class);
        File file = new File(Environment.getExternalStorageDirectory(), "photo.png");
        RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png", file);
        Map<String,RequestBody> params = new HashMap<>();
        params.put("photo\"; filename=\"photo.png", photoRequestBody);
        params.put("username",  RequestBody.create(null, "zhangsan"));
        ReqeustBody passwordRequestBody = RequestBody.create(null, "123456");
        api.registerUser(params,passwordRequestBody).enqueue(new Callback<User>() {
            @Override
            public void onResponse(Call<User> call, Response<User> response) {
                // Processing results
            }

            @Override
            public void onFailure(Call<User> call, Throwable t) {
                // Handling exceptions
            }
        });

8, Add request Headers

Sometimes, we need to add a separate request header for a request, so how? The interface layer is defined as follows:

  • Set via @ Headers annotation
@Headers("Cache-Control: max-age=640000")
@GET("widget/list")
Call<List<Widget>> widgetList();
@Headers({
    "Accept: application/vnd.github.v3.full+json",
    "User-Agent: Retrofit-Sample-App"
})
@GET("users/{username}")
Call<User> getUser(@Path("username") String username);

Note that the request Headers added by @ Headers will not overlap each other, and all Headers with the same name will be included in the request.

  • Dynamically update request headers with @ Header annotation

The corresponding parameter must be provided for @ Header. If the value is null, the title will be omitted. Otherwise, toString is called on the value and the result is used

@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)
@GET("user")
Call<User> getUser(@HeaderMap Map<String, String> headers)

We can also use the OkHttp interceptor to specify the request header that needs to be added to each request

9, Synchronous and asynchronous requests

Call instances can be executed synchronously or asynchronously. Each call instance can only be used once, but calling Call#clone() will create a new instance that can be used. On Android, the callback will be executed on the main thread. On the JVM, the callback will occur on the same thread that executes the HTTP request.

10, Retrofit framework introduction method (Gradle)

implementation 'com.squareup.retrofit2:retrofit:(insert latest version)'

11, Retrofit obfuscation configuration

 

# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
# EnclosingMethod is required to use InnerClasses.
-keepattributes Signature, InnerClasses, EnclosingMethod

# Retrofit does reflection on method and parameter annotations.
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations

# Retain service method parameters when optimizing.
-keepclassmembers,allowshrinking,allowobfuscation interface * {
    @retrofit2.http.* <methods>;
}

# Ignore annotation used for build tooling.
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement

# Ignore JSR 305 annotations for embedding nullability information.
-dontwarn javax.annotation.**

# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
-dontwarn kotlin.Unit

# Top-level functions that can only be used by Kotlin.
-dontwarn retrofit2.KotlinExtensions
-dontwarn retrofit2.KotlinExtensions$*

# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy
# and replaces all potential values with null. Explicitly keeping the interfaces prevents this.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1>

 

12, Download File

Retrofit realizes file download, and the interface layer is defined as follows:

public interface ApiService {

    @GET("download")
    Call<ResponseBody> download();
}

Here, the direct return type is defined as ResponseBody, and then directly get its byteStream() to read the stream. The visit cases are as follows:

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://xxx")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        ApiService api = retrofit.create(ApiService.class);
        api.download().enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                 InputStream in = response.body().byteStream();
                 // Save file operation, here is the main thread!!!
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                 // Handling exceptions
            }
        });

It can be seen that we get the file stream in the callback onResponse, and then we can write the file. However, note that this is the main thread, and the file reading and writing operation is a time-consuming operation. We also have to switch to the sub thread operation, so we can directly use OkHttp for the file download operation.

Advanced configuration

1, Configure callFactory

Check the construction process of Retrofit. If the callFactory is not configured, the default OkHttpClient object will be created inside Retrofit as the callFactory

public Retrofit build() {
      if (baseUrl == null) {
        throw new IllegalStateException("Base URL required.");
      }

      okhttp3.Call.Factory callFactory = this.callFactory;
      // If callFactory is empty, the default OkHttpClient will be created
      if (callFactory == null) {
        callFactory = new OkHttpClient();
      }
      // ... Omit code
}

If we need to set OkHttpClient in detail, we need to build OkHttpClient object, and then use retrofit Builder #callfactory (okhttp3. Call. Factory) method retrofit Builder #client (OkHttpClient) or passed in.

2, Configure CallAdapterFactory

By default, Retrofit can only support call < T > type interface returns. If it needs to be used with RxJava, RxJavaCallAdapterFactory needs to be configured, so that the interface return value type is Observable object, that is, RxJava can be used for request operations.

3, Configure ConverterFactory

By default, Retrofit can only deserialize the HTTP Body into the ResponseBody type of OkHttp, and can only accept the RequestBody type of @ Body. You can add converters to support other types. The six member modules adopt the popular serialization library for our convenience.

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

This is an example of using the GsonConverterFactory class to generate the implementation of the GitHubService interface, which uses Gson for deserialization.

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https:xxx")
    .addConverterFactory(GsonConverterFactory.create())
    .build();

GitHubService service = retrofit.create(GitHubService.class);

4, Customize ConverterFactory

If you need to communicate with API s that use off the shelf formats (such as YAML, txt, custom formats) that Retrofit does not support, or want to use other libraries to implement existing formats, you can easily create your own converter. Create an extension converter Factory class and pass in the instance when building the adapter.

Tags: Android

Posted by sean paul on Mon, 18 Apr 2022 05:45:23 +0930