preface

I recently came up with the idea of doing an in-depth analysis of the main Android open source framework, and then writing a series of articles, including detailed usage and source code analysis of the framework. The purpose is to understand the underlying principle of the framework by appreciating the source code of god, that is, to do not only know it, but also know why.

Here I say their own experience reading source code, I generally in accordance with the peacetime use of a framework or a system source code process, first of all to know how to use, and then go to the bottom of each step to do what, with what good design patterns, why so design.

Series of articles:

  • Android mainstream open source framework (a) OkHttp -HttpClient and HttpURLConnection use details
  • Android main open source framework (ii) OkHttp usage details
  • Android mainstream open source framework (three) OkHttp source code analysis
  • Android mainstream open source framework (iv) Retrofit usage details
  • Android mainstream open source framework (v) Retrofit source code analysis
  • Android mainstream open source framework (six) Glide execution process source code analysis
  • More frameworks continue to be updated…

Check out AndroidNotes for more dry stuff

I. Introduction to Retrofit

The previous article has introduced the use of OkHttp and OkHttp source code analysis, do not know the strongly recommended first look at these two articles. This article is about Retrofit, Square’s open source web framework, which is based on OkHttp at its base, but is easier to use than OkHttp and better suited to RESTful API format requests.

Ii. Use of Retrofit

2.1 Preparations before Use

Add network permissions to androidmanifest.xml file as follows:

<uses-permission android:name="android.permission.INTERNET"/>
Copy the code

Since we need to convert the ResponseBody returned from the server into an entity class, we need to add the Gson library dependency as the data parser. Finally add the following dependencies to build.gradle in the currently used Module:

// Retrofit
implementation 'com. Squareup. Retrofit2: retrofit: 2.5.0'
// Gson
implementation 'com. Squareup. Retrofit2: converter - gson: 2.5.0'
Copy the code

2.2 Simple GET Request

This is demonstrated using the GET interface provided by Postman. (Postman Echo)

(1) Create an entity class to receive data from the server:

public class PostmanGetBean {
    private String url;
    // Omit other fields, see demo for details.
}
Copy the code

(2) Create an interface for defining network requests:

public interface PostmanService {
    @GET("get")
    Call<PostmanGetBean> testGet(a);
}
Copy the code

As you can see, there is a testGet() method. The annotation @get on top of the method represents the GET request, and the “GET” in the annotation is concatenated with the following baseUrl to form a full path. For example, if baseUrl is https://postman-echo.com/, the complete path is https://postman-echo.com/get. It is recommended that baseUrl end with a/(slash), and that path in annotations do not start with a/(slash), as this is more intuitive. (3) Create an instance of Retrofit:

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://postman-echo.com/")// baseUrl
        .addConverterFactory(GsonConverterFactory.create())// Parse json data
        .build();
Copy the code

(4) Create an instance of the network request interface and Call the method in the interface to obtain the Call object:

PostmanService service = retrofit.create(PostmanService.class);
Call<PostmanGetBean> call = service.testGet();
Copy the code

(5) Make network requests

call.enqueue(new Callback<PostmanGetBean>() {
	@Override
	public void onResponse(Call<PostmanGetBean> call, Response<PostmanGetBean> response) {
		System.out.println(response.body().getUrl());
	}

	@Override
	public void onFailure(Call<PostmanGetBean> call, Throwable t) {}});Copy the code

Print result:

https://postman-echo.com/get
Copy the code

Example source code: retrofitactivity-testgetrequest

Retrofit notes

There are a number of annotations used in Retrofit, which are divided into three categories.

3.1 The first class: network request methods

They are @get, @post, @PUT, @delete, @path, @head, @options and @http. The first seven correspond to the network request methods in HTTP respectively. They all receive a string and baseUrl to form a complete URL. Set via @http annotation. The last @HTTP annotation can be used to replace the previous seven annotations, as well as other extended features. I’ll focus on the @HTTP annotation here. The other annotations are similar to the @GET annotation.

@http annotation example: the @http annotation has three attributes: method, path, and hasBody. It says that this annotation can be used to replace the previous seven annotations, so let’s replace the @get annotation mentioned earlier in the GET request.

Here only need to modify the interface, other unchanged:

public interface PostmanService {
    @HTTP(method = "GET", path = "get", hasBody = false)
    Call<PostmanGetBean> testHTTP(a);
}
Copy the code

Run result: Same as the @get annotation example. Example source code: retrofitActivity-testhTTp

3.2 Type 2: marks

3.2.1 @ FormUrlEncoded annotation

Summary: Indicates that the request body is a Form. Example: The POST interface provided by Postman is used to demonstrate this.

(1) create entity class:

public class PostmanPostBean {
    // Fields are omitted from overriding the toString() method, see demo
}
Copy the code

(2) Create interface:

public interface PostmanService {
    @POST("post")
    @FormUrlEncoded
    Call<PostmanPostBean> testFormUrlEncoded1(@Field("username") String name, @Field("password") String password);
}
Copy the code

As you can see, the @field annotation is used, which is the third type of annotation used to pass key-value pairs to the Post form, where username represents the key and name represents the value.

(3) Initiate a request:

Retrofit retrofit = new Retrofit.Builder()
		.baseUrl("https://postman-echo.com/")
		.addConverterFactory(GsonConverterFactory.create())
		.build();

PostmanService service = retrofit.create(PostmanService.class);
Call<PostmanPostBean> call = service.testFormUrlEncoded1("wildma"."123456");
call.enqueue(new Callback<PostmanPostBean>() {
	@Override
	public void onResponse(Call<PostmanPostBean> call, Response<PostmanPostBean> response) {
		System.out.println(response.body().getForm().toString());
	}

	@Override
	public void onFailure(Call<PostmanPostBean> call, Throwable t) {}});Copy the code

Running results:

FormEntity{username='wildma', password='123456'}
Copy the code

Example source code: RetrofitActivity-testFormUrlEncoded1

Pass a Map set: Pass key-value pairs to the Post form. Instead of passing them one by one, you can pass a Map set using the @fieldMap annotation as follows:

(1) Create interface:

public interface PostmanService {
    @POST("post")
    @FormUrlEncoded
    Call<PostmanPostBean> testFormUrlEncoded2(@FieldMap Map<String, Object> map);
}
Copy the code

(3) Initiate a request:

// Omit the instance code to create Retrofit
Map<String, Object> map = new HashMap<>();
map.put("username"."wildma");
map.put("password"."123456");
Call<PostmanPostBean> call = service.testFormUrlEncoded2(map);
// Omit the network request code
Copy the code

Example source code: RetrofitActivity-testFormurlencoded2

3.2.2 @ Multipart annotation

Description: The request body is a Form that supports file uploading. Example: The image upload interface provided by THE YESAPI is used to demonstrate this.

(1) create entity class:

public class UploadImgBean {
    // Fields are omitted from overriding the toString() method, see demo
}
Copy the code

(2) Create interface:

public interface FileUploadService {
    @POST("? service=App.CDN.UploadImg")
    @Multipart
    Call<UploadImgBean> testFileUpload1(@Part MultipartBody.Part file, @Part("app_key") RequestBody appKey);
}
Copy the code

As you can see, the @Part annotation is used, which is the third type of annotation for form fields and is suitable for file uploads. Two types of @part are used here, multipartBody. Part means to upload a file, and RequestBody means to upload a key-value pair, where app_key represents the key and appKey represents the value.

(3) Initiate a request:

Retrofit retrofit = new Retrofit.Builder()
		.baseUrl("http://hn216.api.yesapi.cn/")
		.addConverterFactory(GsonConverterFactory.create())
		.build();

RequestBody appKey = RequestBody.create(null."Replace it with the appKey you got on YESAPI.");
// test. PNG is the file in the SD card and directory
File file = new File(Environment.getExternalStorageDirectory(), "test.png");
RequestBody requestBody = RequestBody.create(MediaType.parse("image/png"), file);
// Build multipartbody. Part, where file is the key agreed by the server and test. PNG is the file name
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file"."test.png", requestBody);

FileUploadService service = retrofit.create(FileUploadService.class);
Call<UploadImgBean> call = service.testFileUpload1(filePart, appKey);
call.enqueue(new Callback<UploadImgBean>() {
	@Override
	public void onResponse(Call<UploadImgBean> call, Response<UploadImgBean> response) {
		System.out.println(response.body().toString());
	}

	@Override
	public void onFailure(Call<UploadImgBean> call, Throwable t) {}});Copy the code

Running results:

UploadImgBean {ret = 200, data = DataEntity {err_code = 0, err_msg = ", url = "http://cd7.yesapi.net/xxx.png"}, MSG = 'current small white interface: App.CDN.UploadImg'}Copy the code

Example source: retrofitActivity-testFileupload1

Multifile upload: If you want to upload multiple files, you can use @partMap to pass a Map set with

as follows:
,>

(1) Create interface:

public interface FileUploadService {
    @POST("? service=App.CDN.UploadImg")
    @Multipart
    Call<UploadImgBean> testFileUpload2(@PartMap Map<String, RequestBody> map);
}
Copy the code

(1) Initiate a request:

Retrofit retrofit = new Retrofit.Builder()
		.baseUrl("http://hn216.api.yesapi.cn/")
		.addConverterFactory(GsonConverterFactory.create())
		.build();

RequestBody appKey = RequestBody.create(null."Replace it with the appKey you got on YESAPI.");
// test. PNG is the file in the SD card and directory
File file = new File(Environment.getExternalStorageDirectory(), "test.png");
RequestBody requestBody = RequestBody.create(MediaType.parse("image/png"), file);
Map<String, RequestBody> requestBodyMap = new HashMap<>();
requestBodyMap.put("app_key", appKey);
// Add a file. File is the key agreed by the server, and test. PNG is the file name
requestBodyMap.put("file\"; filename=\"test.png", requestBody);
// If there are more files, continue putting ()...

FileUploadService service = retrofit.create(FileUploadService.class);
Call<UploadImgBean> call = service.testFileUpload2(requestBodyMap);
call.enqueue(new Callback<UploadImgBean>() {
	@Override
	public void onResponse(Call<UploadImgBean> call, Response<UploadImgBean> response) {
		System.out.println(response.body().toString());
	}

	@Override
	public void onFailure(Call<UploadImgBean> call, Throwable t) {}});Copy the code

Example source: retrofitActivity-testFileupload2

3.2.3 @ Streaming annotation

Summary: The data representing the response body is returned as a stream. If this annotation is not used, the data will be loaded into memory by default, and then the data will be retrieved from memory. Therefore, this annotation is usually used when returning large data, such as downloading a large file. Example: the head is used here to download my blog (wildma. Making. IO/medias avat…). Demonstrate.

[ResponseBody] [ResponseBody] [ResponseBody] [ResponseBody] (For the sake of demonstration, the following example will not parse into an entity class and will use ResponseBody directly to receive the raw data returned by the server.)

(2) Create interface:

public interface FileDownloadService {
    @Streaming
    @GET("medias/avatars/avatar.jpg")
    Call<ResponseBody> testFileDownload(a);
}
Copy the code

The @Streaming annotation is used here to indicate that the data in the response body is returned as a stream.

(3) Initiate a request:

Retrofit retrofit = new Retrofit.Builder()
		.baseUrl("https://wildma.github.io/")
		.addConverterFactory(GsonConverterFactory.create())
		.build();

FileDownloadService service = retrofit.create(FileDownloadService.class);
Call<ResponseBody> call = service.testFileDownload();
call.enqueue(new Callback<ResponseBody>() {
	@Override
	public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
		InputStream is = response.body().byteStream();
		// Save the file...
	}

	@Override
	public void onFailure(Call<ResponseBody> call, Throwable t) {}});Copy the code

Example source code: retrofitactivity-testFileDownload

3.3 Type 3: Network request parameters

3.3.1 @header, @headers and @HeaderMap annotations

Introduction: @header and @headermap are used to add a request Header with a fixed value, and @headers is used to add a request Header with a fixed value. @header and @headermap are passed in as parameters to the request method, and @headers is added directly to the request method. Example:

// @Header
@GET("headers")
Call<ResponseBody> testHeader(@Header("token") String token);

// @Headers
@Headers("token: 123")
@GET("headers")
Call<ResponseBody> testHeaders(a);

// @headers Multiple request Headers
@Headers({"token: 123", "sign: 456"})
@GET("headers")
Call<ResponseBody> testHeaders2(a);

// @HeaderMap
@GET("headers")
Call<ResponseBody> testHeaderMap(@HeaderMap Map<String, String> map);
Copy the code

Retrofitactivity-testheader (), testHeaders(), testHeaders2()

3.3.2 rainfall distribution on 10-12 @ Body annotation

Summary: @body is used for non-form request Body. The @body annotation can be easily implemented because it can be passed directly to an entity class, which will be converted into a JSON string request Body during the request process. (1) Create interface:

public interface PostmanService {
    @POST("post")
    Call<ResponseBody> testBody(@Body TestBodyBean testBodyBean);
}
Copy the code

(2) Initiate a request:

// Omit the instance code to create Retrofit
TestBodyBean bean = new TestBodyBean();
bean.setUsername("wildma");
bean.setPassword("123456");

PostmanService service = retrofit.create(PostmanService.class);
Call<ResponseBody> call = service.testBody(bean);
// Omit the network request code
Copy the code

Retrofitactivity-testbody ()

3.3.3 @field and @fieldMap annotations

Summary: Used to pass key-value pairs to the Post form. Example: The use of the @formurlencoded annotation was explained earlier. Retrofitactivity-testformurlencoded1 (), testFormUrlEncoded2()

3.3.4 @Part and @partMap annotations

Description: Used for form fields, applicable to file uploads. Example: The specific use of the @multipart annotation was covered earlier. Retrofitactivity-testfileupload1 (), testFileUpload2()

3.3.5 @query and @queryMap annotations

Description: Used for form fields, the function is the same as @field and @FiledMap, the difference is that @Query and @QueryMap data is reflected in the URL, and @field and @FiledMap data is reflected in the request body, but the generated data is the same. (1) Create interface:

public interface PostmanService {
    @GET("get")
    Call<ResponseBody> testQuery(@Query("username") String username);
}
Copy the code

(2) Initiate a request:

Retrofit retrofit = new Retrofit.Builder()
		.baseUrl("https://postman-echo.com/")
		.addConverterFactory(GsonConverterFactory.create())
		.build();

PostmanService service = retrofit.create(PostmanService.class);
Call<ResponseBody> call = service.testQuery("wildma");
// Omit the network request code
Copy the code

The above baseUrl is https://postman-echo.com/, part of the URL in the @get annotation is “GET”, and the final full URL should be https://postman-echo.com/get if the @query annotation is not used, With annotations, it becomes https://postman-echo.com/get?username=wildma.

The @queryMap annotation corresponds to the Map collection with the following interface:

public interface PostmanService {
    @GET("get")
    Call<ResponseBody> testQueryMap(@QueryMap Map<String, String> params);
}
Copy the code

The code that initiates the request will not be posted, just pass in a corresponding Map collection.

Retrofitactivity-testquery (), testQueryMap()

3.3.6 @ QueryName annotation

Brief introduction: This annotation is rarely used in actual projects. Its function is similar to that of @Query and @QueryMap. Parameters are all concatenated in THE URL, but @Query and @QueryMap are concatenated in the URL with key-value pairs, while @QueryName is only concatenated with keys. There is no value. (1) Create interface:

public interface PostmanService {
    @GET("get")
    Call<ResponseBody> testQueryName(@QueryName String... filters);
}
Copy the code

Annotations can be followed by String filter or String… Filters, where the latter is a variable length parameter that can pass multiple parameters or none. (2) Initiate a request:

Retrofit retrofit = new Retrofit.Builder()
		.baseUrl("https://postman-echo.com/")
		.addConverterFactory(GsonConverterFactory.create())
		.build();

PostmanService service = retrofit.create(PostmanService.class);
Call<ResponseBody> call = service.testQueryName("wildma"."tom");
// Omit the network request code
Copy the code

The final concatenated URL above is https://postman-echo.com/get?wildma&tom.

Retrofitactivity-testqueryname ()

3.3.7 @ Path annotation

Description: @path is used to set the default URL address. Example: This is demonstrated using the official API, which is to get a list of warehouses for the specified user. (1) Create interface:

public interface GitHubService {
    @GET("users/{user}/repos")
    Call<List<RepoBean>> listRepos(@Path("user") String user);
}
Copy the code

(2) Initiate a request:

Retrofit retrofit = new Retrofit.Builder()
		.baseUrl("https://api.github.com/")
		.addConverterFactory(GsonConverterFactory.create())
		.build();

GitHubService service = retrofit.create(GitHubService.class);
Call<ResponseBody> call = service.testPath("wildma");
// Omit the network request code
Copy the code

As you can see, the @get annotation has a “{user}” in the “users/{user}/repos”, which is the default URL address, The @path (“user”) String user in the listRepos() method indicates that the urSE passed is used to replace {user} above. So it’s the complete URL to https://api.github.com/users/wildma/repos.

Retrofitactivity-testpath ()

3.3.8 @ Url annotation

Description: @Url is used to dynamically set a complete Url. (1) Create interface:

public interface PostmanService {
    @GET()
    Call<ResponseBody> testUrl(@Url String url);
}
Copy the code

(2) Initiate a request:

Retrofit retrofit = new Retrofit.Builder()
		.baseUrl("https://api.github.com/")
		.addConverterFactory(GsonConverterFactory.create())
		.build();

PostmanService service = retrofit.create(PostmanService.class);
Call<ResponseBody> call = service.testUrl("https://postman-echo.com/get");
// Omit the network request code
Copy the code

As you can see, both baseUrl() and testUrl() have a URL set, but since the @url annotation is dynamically set, the URL set in testUrl() prevails. The result is https://postman-echo.com/get.

Retrofitactivity-testurl ()

Set up a custom OkHttpClient

When creating an instance of Retrofit, you can set up a custom OkHttpClient using the client() method, which can set uniform headers, add log interceptors, cookies, and so on. Here’s how to set a unified header. (1) Create OkHttpClient by adding an interceptor and setting a uniform header in the intercept() method:

OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(new Interceptor() {
	@Override
	public okhttp3.Response intercept(Chain chain) throws IOException {
		Request originalRequest = chain.request();
		Request request = originalRequest.newBuilder()
				.header("token"."123")
				.header("sign"."456")
				.build();
		return chain.proceed(request);
	}
}).build();
Copy the code

(2) Set a custom OkHttpClient using the client() method:

Retrofit retrofit = new Retrofit.Builder()
		.client(okHttpClient)// Set up a custom OkHttpClient
		.baseUrl("https://postman-echo.com/")
		.addConverterFactory(GsonConverterFactory.create())
		.build();
Copy the code

Example source code: RetrofitActivity-testCustomokHttpClient

Fifth, About Converter

Retrofit uses ResponseBody by default to receive data returned by the server. If you want to convert to the corresponding entity class, you can set up a data parser when creating an instance of Retrofit using the addConverterFactory() method. There are many options for data parsers, and Retrofit documentation provides many of them:

  • 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

In addition to the documents provided by these, but there is a kind of commonly used: fastjson: ‘org. Ligboy. Retrofit2: converter – fastjson – android

This is demonstrated using Gson. (1) Create interface:

public interface PostmanService {
    @GET("get")
    Call<PostmanGetBean> testGet(a);
}
Copy the code

Here we directly replace ResponseBody with the entity class PostmanGetBean. (2) Initiate a request:

Retrofit retrofit = new Retrofit.Builder()
		.baseUrl("https://postman-echo.com/")
		.addConverterFactory(GsonConverterFactory.create())// Add a Gson parser
		.build();
// Omit the network request code
Copy the code

Here Gson is added as the data parser.

Example source code: retrofitactivity-testget

6. About CallAdapter

If you want to return any other type, you can set up a CallAdapter when creating an instance of Retrofit by using the addCallAdapterFactory() method. Retrofit provides the following Calladapters:

  • Guava: com. Squareup. Retrofit2: adapter – guava
  • Java8: com. Squareup. Retrofit2: adapter – Java8:2.0.2
  • Rxjava: com. Squareup. Retrofit2: adapter – rxjava

RxJava is used to demonstrate this. (1) Add related dependencies:

/ / support rxjava2
implementation 'com. Squareup. Retrofit2: adapter - rxjava2:2.5.0'
// rxjava2
compile 'the IO. Reactivex. Rxjava2: rxjava: 2.2.13'
compile 'the IO. Reactivex. Rxjava2: rxandroid: 2.1.1'
Copy the code

(2) Create interface:

@GET("get")
Observable<ResponseBody> testCallAdapter(a);
Copy the code

This replaces Call with an Observable. (3) Initiate a request:

Retrofit retrofit = new Retrofit.Builder()
		.baseUrl("https://postman-echo.com/")
		.addConverterFactory(GsonConverterFactory.create())
		.addCallAdapterFactory(RxJava2CallAdapterFactory.create())// Set RxJava as the current CallAdapter
		.build();

PostmanService service = retrofit.create(PostmanService.class);
Observable<ResponseBody> observable = service.testCallAdapter();
observable.subscribeOn(Schedulers.io())               // Network requests are made in the IO thread
		.observeOn(AndroidSchedulers.mainThread())  // Process the result of the request in the main thread
		.subscribe(new Observer<ResponseBody>() {
			@Override
			public void onSubscribe(Disposable d) {}@Override
			public void onNext(ResponseBody responseBody) {
				try {
					System.out.println(responseBody.string());
				} catch(IOException e) { e.printStackTrace(); }}@Override
			public void onError(Throwable e) {}@Override
			public void onComplete(a) {}});Copy the code

Set RxJava as the current CallAdapter and call Observable methods to handle network requests and request results. Example source code: retrofitActivity-testCallAdapter

Seven, the source code

Use of Retrofit demo

About me

I am Wildma, CSDN certified blog expert, excellent author of Simple book programmer, good at screen adaptation. If the article is helpful to you, a “like” is the biggest recognition for me!