睡前给IT女闺蜜讲解的;Retrofit原理及源码解析,她居然……对我

前言:

昨天晚上正准备睡觉,女闺蜜打电话来想问关于Retrofit的网络框架;于是找出这篇全网最详细的Retrofit资料,给她讲解;究竟是什么Retrofit解析让她感动的如此献殷勤?

一、什么是Retrofit

Retrofit 是一个用于 Android 和 Java 平台的类型安全的网络请求框架。Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装。 网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责 网络请求接口的封装,可以结合Rxjava、协程、以及LiveData使用。返回的序列化数据可以是 Gson、Jackson、Moshi、Protobuf等等

二、Retrofit的使用方法与示例

2.1 使用前准备

(1)加入网络权限 在 AndroidManifest.xml 文件中加入如下:

<uses-permission android:name="android.permission.INTERNET"/>
复制代码

(2)添加 Retrofit 库的依赖 因为需要将服务器返回的 ResponseBody 转换成实体类,所以需要添加 Gson 库的依赖作为数据解析器。 最终在当前使用的 module 下的 build.gradle 中加入如下依赖:

// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
// Gson
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
复制代码

2.2 简单的 GET 请求

这里使用 postman 提供的 GET 接口进行演示。(Postman Echo

(1)创建一个实体类,用于接收服务器返回的数据:

public class PostmanGetBean {
    
    
    private String url;
    // 其余字段省略,具体看 demo。
}
复制代码

(2)创建一个接口,用于定义网络请求:

public interface PostmanService {
    
    
    @GET("get")
    Call<PostmanGetBean> testGet();
}
复制代码

可以看到,这里有一个 testGet() 方法,方法上面的注解 @GET 表示 GET 请求,注解里面的 “get” 会与后面的 baseUrl 拼接成完整的路径。例如 baseUrl 为 https://postman-echo.com/,则完整的路径为 https://postman-echo.com/get。这里建议 baseUrl 以 /(斜线)结尾,注解中的 path 统一不要以 /(斜线)开头,因为这种方式看起来比较直观。
(3)创建 Retrofit 的实例:

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://postman-echo.com/")// baseUrl
        .addConverterFactory(GsonConverterFactory.create())// 解析json数据
        .build();
复制代码

(4)创建网络请求接口的实例,并调用接口中的方法获取 Call 对象:

PostmanService service = retrofit.create(PostmanService.class);
Call<PostmanGetBean> call = service.testGet();
复制代码

(5)进行网络请求

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) {
    
    

	}
});
复制代码

打印结果:

扫描二维码关注公众号,回复: 14263538 查看本文章
https://postman-echo.com/get
复制代码

示例源码:RetrofitActivity-testGetRequest

三、Retrofit 注解说明

Retrofit 中使用了大量的注解,这里将这些注解分成 3 类。

3.1 第一类:网络请求方法

分别是 @GET、@POST、@PUT、@DELETE、@PATH、@HEAD、@OPTIONS 和 @HTTP,前 7 个分别对应 HTTP 中的网络请求方法,都接收一个字符串与 baseUrl 组成完整的 URL,也可以不指定,通过 @HTTP 注解设置。最后一个 @HTTP 注解可以用来替换前面 7 个注解,以及其他扩展功能。 这里主要讲下 @HTTP 注解,其他注解与 @GET 注解类似。

@HTTP 注解示例:
@HTTP 注解有 3 个属性:method、path 与 hasBody,上面说了这个注解可以用来替换前面 7 个注解,所以就替换一下前面讲到 GET 请求中的 @GET 注解吧。

这里只需要修改接口即可,其他不变:

public interface PostmanService {
    
    
    @HTTP(method = "GET", path = "get", hasBody = false)
    Call<PostmanGetBean> testHTTP();
}
复制代码

运行结果:
与 @GET 注解示例一样。
示例源码:RetrofitActivity-testHTTP

3.2 第二类:标记

3.2.1 @FormUrlEncoded 注解

简介: 表示请求体是一个 Form 表单。
示例:
这里使用 postman 提供的 POST 接口进行演示。

单个键值对传:
(1)创建实体类:

public class PostmanPostBean {
    
    
    // 字段与重写 toString() 方法省略,具体看 demo
}
复制代码

(2)创建接口:

public interface PostmanService {
    
    
    @POST("post")
    @FormUrlEncoded
    Call<PostmanPostBean> testFormUrlEncoded1(@Field("username") String name, @Field("password") String password);
}
复制代码

可以看到,这里使用了 @Field 注解,它属于第三类注解,用来向 Post 表单传入键值对,其中 username 表示键,name 表示值。

(3)发起请求:

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) {
    
    

	}
});
复制代码

运行结果:

FormEntity{username='wildma', password='123456'}
复制代码

示例源码:RetrofitActivity-testFormUrlEncoded1

传入一个 Map 集合:
向 Post 表单传入键值对除了上面一个个传,还可以使用注解 @FieldMap 传一个 Map 集合,如下:

(1)创建接口:

public interface PostmanService {
    
    
    @POST("post")
    @FormUrlEncoded
    Call<PostmanPostBean> testFormUrlEncoded2(@FieldMap Map<String, Object> map);
}
复制代码

(3)发起请求:

// 省略创建 Retrofit 的实例代码
Map<String, Object> map = new HashMap<>();
map.put("username", "wildma");
map.put("password", "123456");
Call<PostmanPostBean> call = service.testFormUrlEncoded2(map);
// 省略网络请求代码
复制代码

示例源码:RetrofitActivity-testFormUrlEncoded2

3.2.2 @Multipart 注解

简介: 表示请求体是一个支持文件上传的 Form 表单。
示例:
这里使用 YESAPI 提供的图片上传接口进行演示。

单文件上传:
(1)创建实体类:

public class UploadImgBean {
    
    
    // 字段与重写 toString() 方法省略,具体看 demo
}
复制代码

(2)创建接口:

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

可以看到,这里使用了 @Part 注解,它属于第三类注解,用于表单字段,适用于有文件上传的情况。这里使用了@Part 的两种类型,MultipartBody.Part 表示上传一个文件,RequestBody 表示传一个键值对,其中 app_key 表示键,appKey 表示值。

(3)发起请求:

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

RequestBody appKey = RequestBody.create(null, "替换成你在 YESAPI 上获取的 appKey");
// test.png 为 SD 卡跟目录下的文件,需要提前放好
File file = new File(Environment.getExternalStorageDirectory(), "test.png");
RequestBody requestBody = RequestBody.create(MediaType.parse("image/png"), file);
// 构建 MultipartBody.Part,其中 file 为服务器约定好的 key,test.png 为文件名称
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) {
    
    
	}
});
复制代码

运行结果:

UploadImgBean{ret=200, data=DataEntity{err_code=0, err_msg='', url='http://cd7.yesapi.net/xxx.png'}, msg='当前小白接口:App.CDN.UploadImg'}
复制代码

示例源码:RetrofitActivity-testFileUpload1

多文件上传:
如果想上传多个文件,则可以使用注解 @PartMap 传一个键值对为 <String, RequestBody> 的 Map 集合,如下:

(1)创建接口:

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

(1)发起请求:

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

RequestBody appKey = RequestBody.create(null, "替换成你在 YESAPI 上获取的 appKey");
// test.png 为 SD 卡跟目录下的文件,需要提前放好
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);
// 加入一个文件,其中 file 为服务器约定好的 key,test.png 为文件名称
requestBodyMap.put("file\"; filename=\"test.png", requestBody);
// 有更多文件,则继续 put()...

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) {
    
    
	}
});
复制代码

示例源码:RetrofitActivity-testFileUpload2

3.2.3 @Streaming 注解

简介: 表示响应体的数据用流的形式返回,如果没有使用该注解,默认会把数据全部载入内存,之后获取数据就从内存中读取,所以该注解一般用在返回数据比较大的时候,例如下载大文件。
示例:
这里使用下载我的博客头像( wildma.github.io/medias/avat… ) 进行演示。

(1)下载文件不需要创建一个实体类,直接用 ResponseBody 来接收服务器返回的数据。(后面的示例为了方便演示,也不再解析成实体类,直接用 ResponseBody 来接收服务器返回的原始数据)

(2)创建接口:

public interface FileDownloadService {
    
    
    @Streaming
    @GET("medias/avatars/avatar.jpg")
    Call<ResponseBody> testFileDownload();
}
复制代码

这里使用了 @Streaming 注解用来表示响应体的数据用流的形式返回。

(3)发起请求:

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();
		// 保存文件...
	}

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

	}
});
复制代码

示例源码:RetrofitActivity-testFileDownload

3.3 第三类:网络请求参数

3.3.1 @Header、@Headers 与 @HeaderMap 注解

简介: @Header 与 @HeaderMap 用于添加不固定值的请求头,@Headers 用于添加固定值的请求头。@Header 与 @HeaderMap 是作为请求方法的参数传入,@Headers 则直接添加到请求方法上。
示例:

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

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

// @Headers 多个请求头
@Headers({
    
    "token: 123", "sign: 456"})
@GET("headers")
Call<ResponseBody> testHeaders2();

// @HeaderMap
@GET("headers")
Call<ResponseBody> testHeaderMap(@HeaderMap Map<String, String> map);
复制代码

示例源码:RetrofitActivity-testHeader()、testHeaders()、testHeaders2()

3.3.2 @Body 注解

简介: @Body 用于非表单请求体。很多时候后台要求前端传一个 json 字符串的请求体,这时候我们可以使用 @Body 注解来轻松实现,因为该注解可以直接传一个实体类,发起请求的过程中会把该实体类转换成 json 字符串的请求体传给后台。
示例:
(1)创建接口:

public interface PostmanService {
    
    
    @POST("post")
    Call<ResponseBody> testBody(@Body TestBodyBean testBodyBean);
}
复制代码

(2)发起请求:

// 省略创建 Retrofit 的实例代码
TestBodyBean bean = new TestBodyBean();
bean.setUsername("wildma");
bean.setPassword("123456");

PostmanService service = retrofit.create(PostmanService.class);
Call<ResponseBody> call = service.testBody(bean);
// 省略网络请求代码
复制代码

示例源码:RetrofitActivity-testBody()

3.3.3 @Field 与 @FieldMap 注解

简介: 用于向 Post 表单传入键值对。
示例:
具体使用前面讲 @FormUrlEncoded 注解的时候已经讲过了。
示例源码:RetrofitActivity-testFormUrlEncoded1()、testFormUrlEncoded2()

3.3.4 @Part 与 @PartMap 注解

简介: 用于表单字段,适用于有文件上传的情况。
示例:
具体使用前面讲 @Multipart 注解的时候已经讲过了。
示例源码:RetrofitActivity-testFileUpload1()、testFileUpload2()

3.3.5 @Query 与 @QueryMap 注解

简介: 用于表单字段,功能与 @Field、@FiledMap 一样,区别在于 @Query、@QueryMap 的数据体现在 URL 上,而 @Field、@FiledMap 的数据体现在请求体上,但生成的数据是一样的。
示例:
(1)创建接口:

public interface PostmanService {
    
    
    @GET("get")
    Call<ResponseBody> testQuery(@Query("username") String username);
}
复制代码

(2)发起请求:

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");
// 省略网络请求代码
复制代码

上面的 baseUrl 为 https://postman-echo.com/,@GET 注解中的部分 URL 为 “get”,最终完整 URL 如果没用 @Query 注解应该是 https://postman-echo.com/get,用了注解就变成 https://postman-echo.com/get?username=wildma 了。

@QueryMap 注解则对应 Map 集合,接口如下:

public interface PostmanService {
    
    
    @GET("get")
    Call<ResponseBody> testQueryMap(@QueryMap Map<String, String> params);
}
复制代码

发起请求的代码就不贴出来了,传一个对应的 Map 集合进来即可。

示例源码:RetrofitActivity-testQuery()、testQueryMap()

3.3.6 @QueryName 注解

简介: 用于没有值的查询参数,该注解实际项目中很少用到,功能与 @Query、@QueryMap 类似,参数都拼接在 URL 上,但是 @Query、@QueryMap 在 URL 上是以键值对拼接的,而 @QueryName 只是拼接键,没有值。
示例:
(1)创建接口:

public interface PostmanService {
    
    
    @GET("get")
    Call<ResponseBody> testQueryName(@QueryName String... filters);
}
复制代码

注解后面可跟 String filter,也可跟 String… filters,其中后者是可变长参数,可以传多个参数也可不传参数。
(2)发起请求:

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");
// 省略网络请求代码
复制代码

上面最终拼接的 URL 为 https://postman-echo.com/get?wildma&tom

示例源码:RetrofitActivity-testQueryName()

3.3.7 @Path 注解

简介: @Path 用于设置 URL 地址的缺省值。
示例:
这里使用官方提供的 API,即获取指定用户的仓库列表进行演示。
(1)创建接口:

public interface GitHubService {
    
    
    @GET("users/{user}/repos")
    Call<List<RepoBean>> listRepos(@Path("user") String user);
}
复制代码

(2)发起请求:

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");
// 省略网络请求代码
复制代码

可以看到,@GET 注解里面的 “users/{user}/repos” 中有一个 “{user}”,这个就是 URL 地址的缺省值, listRepos() 方法中的 @Path(“user”) String user 表示传入的 urse 就是用来替换上面的 {user} 的。所以最终完整的 URL 为 https://api.github.com/users/wildma/repos

示例源码:RetrofitActivity-testPath()

3.3.8 @Url 注解

简介: @Url 用于动态设置一个完整的 URL。
示例:
(1)创建接口:

public interface PostmanService {
    
    
    @GET()
    Call<ResponseBody> testUrl(@Url String url);
}
复制代码

(2)发起请求:

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");
// 省略网络请求代码
复制代码

可以看到,baseUrl() 与 testUrl() 都设置了一个 URL,但由于 @Url 注解标识的 URL 是动态设置的,所以最终以 testUrl() 中设置的为准,也就是最终使用的是 https://postman-echo.com/get

示例源码:RetrofitActivity-testUrl()

四、设置自定义的 OkHttpClient

在创建 Retrofit 的实例的时候可以通过 client() 方法设置自定义的 OkHttpClient,自定义 OkHttpClient 可以设置统一的 header,添加 log 拦截器、Cookie 等。这里就讲下怎么设置统一的 header 吧!
(1)创建 OkHttpClient 的时候通过添加拦截器,然后在拦截器的 intercept() 方法中设置统一的 header:

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();
复制代码

(2)通过 client() 方法设置自定义的 OkHttpClient:

Retrofit retrofit = new Retrofit.Builder()
		.client(okHttpClient)// 设置自定义的 OkHttpClient
		.baseUrl("https://postman-echo.com/")
		.addConverterFactory(GsonConverterFactory.create())
		.build();
复制代码

示例源码:RetrofitActivity-testCustomOkHttpClient

五、关于 Converter

Retrofit 默认用 ResponseBody 来接收服务器返回的数据,如果想要转换成对应的实体类,那么在创建 Retrofit 的实例的时候可以通过 addConverterFactory() 方法设置一个数据解析器,数据解析器有多种选择,Retrofit 文档中就提供了很多种:

  • 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

除了文档提供的这几种,其实还有一种常用的:
fastjson:'org.ligboy.retrofit2:converter-fastjson-android

这里使用 Gson 进行演示。
(1)创建接口:

public interface PostmanService {
    
    
    @GET("get")
    Call<PostmanGetBean> testGet();
}
复制代码

这里直接用实体类 PostmanGetBean 替换 ResponseBody。
(2)发起请求:

Retrofit retrofit = new Retrofit.Builder()
		.baseUrl("https://postman-echo.com/")
		.addConverterFactory(GsonConverterFactory.create())// 添加 Gson 解析器
		.build();
// 省略网络请求代码
复制代码

这里添加了 Gson 作为数据解析器。

示例源码:RetrofitActivity-testGet

六、关于 CallAdapter

前面创建接口的时候,发现接口中的方法返回类型都是 Call,如果想要返回其他类型,那么在创建 Retrofit 的实例的时候可以通过 addCallAdapterFactory() 方法设置一个 CallAdapter,Retrofit 提供了如下 CallAdapter:

  • guava:com.squareup.retrofit2:adapter-guava
  • Java8:com.squareup.retrofit2:adapter-java8:2.0.2
  • rxjava:com.squareup.retrofit2:adapter-rxjava

这里使用 RxJava 进行演示。
(1)添加相关依赖:

// 支持 rxjava2
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'
// rxjava2
compile 'io.reactivex.rxjava2:rxjava:2.2.13'
compile 'io.reactivex.rxjava2:rxandroid:2.1.1'
复制代码

(2)创建接口:

@GET("get")
Observable<ResponseBody> testCallAdapter();
复制代码

这里使用 Observable 替换 Call。
(3)发起请求:

Retrofit retrofit = new Retrofit.Builder()
		.baseUrl("https://postman-echo.com/")
		.addConverterFactory(GsonConverterFactory.create())
		.addCallAdapterFactory(RxJava2CallAdapterFactory.create())// 设置 RxJava 作为当前的 CallAdapter
		.build();

PostmanService service = retrofit.create(PostmanService.class);
Observable<ResponseBody> observable = service.testCallAdapter();
observable.subscribeOn(Schedulers.io())               // 在 IO 线程进行网络请求
		.observeOn(AndroidSchedulers.mainThread())  // 在主线程处理请求结果
		.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() {
    
    
			}
		});
复制代码

这里设置 RxJava 作为当前的 CallAdapter,并且调用 Observable 的相关方法进行网络请求与请求结果的处理。
示例源码:RetrofitActivity-testCallAdapter

七、Retrofit源码剖析

7.1创建以及使用

同样是Builder的模式创建的,可以指定BaseUrl,指定OkHttpClient,指定适配器,以及序列号工厂

  1. 创建Retrofit
 Retrofit retrofit=new Retrofit.Builder()
                .baseUrl(baseUrl)
                .client(new OkHttpClient())
                // 指定Rxjava适配器
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                // 指定序列化结构
                .addConverterFactory(GsonConverterFactory.create())
                .build();

复制代码
  1. 创建接口
public interface ApiService {
    
    
    @GET("/wxarticle/list")
    Call<ResponseBody> getNetData();
    @GET("/wxarticle/list")
    Observable<ResponseBody> getNetData2();
}
复制代码
  1. 请求
 // 创建 ApiService
 ApiService apiService = retrofit.create(ApiService.class);
 // 使用原始自带的请求
  retrofit2.Call<NetData> netData = apiService.getNetData();
  netData.enqueue(new retrofit2.Callback<NetData>() {
    
    
      @Override
      public void onResponse(retrofit2.Call<NetData> call, retrofit2.Response<NetData> response) {
    
    

      }

      @Override
      public void onFailure(retrofit2.Call<NetData> call, Throwable t) {
    
    

      }
  });

 // 适配成Rxjava流的请求
  apiService.getNetData2()
          .subscribeOn(Schedulers.io())
          .observeOn(AndroidSchedulers.mainThread())
          .subscribe(data -> {
    
    
          }, error -> {
    
    
          });
复制代码

7.2解析

创建retrofit的 Builder.Build方法

 public Retrofit build() {
    
    
    if (baseUrl == null) {
    
    
      throw new IllegalStateException("Base URL required.");
    }
	// 虽然这里用的工厂模式,但是 就new了一个OkHttpClient
    okhttp3.Call.Factory callFactory = this.callFactory;
    if (callFactory == null) {
    
    
      callFactory = new OkHttpClient();
    }

    Executor callbackExecutor = this.callbackExecutor;
    if (callbackExecutor == null) {
    
    
    // 因为在android中platform就是 Android,Platform中的一个内部类Android
      callbackExecutor = platform.defaultCallbackExecutor();
    }

    // 这就是添加所有的adapter,这里用到的适配器模式,也就是从Okhttp的Call适配成Retrofit的Call
    // 也可以添加咱们自己的适配器,比如RxJava2CallAdapterFactory.create(),Rxjava的。
    // 还可以添加协程的 或者是LiveData的
    
    List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
    callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));

    // 这个是转化成javabean的转化类,有Gson 有 Moshi,有Protobuf,
    List<Converter.Factory> converterFactories =
        new ArrayList<>(
            1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());

    converterFactories.add(new BuiltInConverters());
    converterFactories.addAll(this.converterFactories);
    converterFactories.addAll(platform.defaultConverterFactories());
 // 执行 Retrofit 的构造方法
    return new Retrofit(
        callFactory,
        baseUrl,
        unmodifiableList(converterFactories),
        unmodifiableList(callAdapterFactories),
        callbackExecutor,
        validateEagerly);
  }
  
 // 创建APIService,用的是动态代理的模式,在运行时创建了一个对象
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 {
    
    
                // 如果是来自Object方法,继续走原来的逻辑
                if (method.getDeclaringClass() == Object.class) {
    
    
                  return method.invoke(this, args);
                }
                args = args != null ? args : emptyArgs;
                 // 这个在Android中 isDefaultMethod()方法默认返回false。因为咱们就是用的接口中的方法
                 // 不会调用接口中的默认方法
                 //Java 8 引入了新的语言特性——默认方法(Default Methods
                 // 所以会走 loadServiceMethod(method).invoke(args)
                return platform.isDefaultMethod(method)
                    ? platform.invokeDefaultMethod(method, service, proxy, args)
                    : loadServiceMethod(method).invoke(args);
              }
            });
  }
  
  
  // 返回一个ServiceMethod,执行里面的方法
 ServiceMethod<?> loadServiceMethod(Method method) {
    
    
  // 先从缓存中拿,有直接返回
  ServiceMethod<?> result = serviceMethodCache.get(method);
  if (result != null) return result;
  synchronized (serviceMethodCache) {
    
    
    result = serviceMethodCache.get(method);
    if (result == null) {
    
    
     // 因为用的反射,所以缓存一份
     // 这里会返回 HttpServiceMethod ,ServiceMethod的一个子类
      result = ServiceMethod.parseAnnotations(this, method);
      serviceMethodCache.put(method, result);
    }
  }
  return result;
}
复制代码

HttpServiceMethod:把Retorfit的注解参数以及Url 变成 Okhttp的Requst,有返回回来的Respone,还有在这里适配的请求,以及 适配成各种返回值,默认 Okhttp的Call 适配成 Retrofit的Call,或者 Rxjava 的 Observable,或者是协程,以及序列化 都是在这里转化的

7.3看一下请求,直接看默认的,不用看Rxjava的即可,看Retrofit的Call怎样转化到Okhttp的Call的

当我们执行的时候会走到 HttpServiceMethod中的invoke

abstract class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> {
    
    
  static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
      Retrofit retrofit, Method method, RequestFactory requestFactory) {
    
    
      // 是否是kotlin中的协程方法
    boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
    boolean continuationWantsResponse = false;
    boolean continuationBodyNullable = false;
	// 得到所有的注解
    Annotation[] annotations = method.getAnnotations();
    // 获取返回类型
    Type adapterType;
    // 先不看 有关协程的,
    if (isKotlinSuspendFunction) {
    
    
      Type[] parameterTypes = method.getGenericParameterTypes();
      Type responseType =
          Utils.getParameterLowerBound(
              0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
      if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
    
    
        // Unwrap the actual body type from Response<T>.
        responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
        continuationWantsResponse = true;
      } else {
    
    
      }

      adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
      annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
    } else {
    
    
    // 获取返回类型
      adapterType = method.getGenericReturnType();
    }

 // 这里会走到Retrofit的nextCallAdapter方法,也就是从刚才咱们设置的Adapter的集合中遍历拿到 这个返回类型的
    CallAdapter<ResponseT, ReturnT> callAdapter =
        createCallAdapter(retrofit, method, adapterType, annotations);
      // 得到返回序列化类型,下面通过这个类型去找 序列化方法
    Type responseType = callAdapter.responseType();
    if (responseType == okhttp3.Response.class) {
    
    
         }
    if (responseType == Response.class) {
    
    
      throw methodError(method, "Response must include generic type (e.g., Response<String>)");
    }
    if (requestFactory.httpMethod.equals("HEAD") && !Void.class.equals(responseType)) {
    
    
      throw methodError(method, "HEAD method must use Void as response type.");
    }

 // 拿到 刚才设置的 converterFactories 集合,遍历拿到属于序列化方式
    Converter<ResponseBody, ResponseT> responseConverter =
        createResponseConverter(retrofit, method, responseType);

    okhttp3.Call.Factory callFactory = retrofit.callFactory;
    if (!isKotlinSuspendFunction) {
    
    
    // 咱们先看这个不是协程的,CallAdapted 就是ResponseCallAdapter 或者是 RxJava2CallAdapter
      return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
    } 
  }

}

复制代码

刚才咱们也看到了OkHttpCall,这里面封装这Okhttp的Call。当我们调用请求的时候,实际就是调用的Okhttp的请求

final class OkHttpCall<T> implements Call<T> {
    
    
@Override
  public void enqueue(final Callback<T> callback) {
    
    
    Objects.requireNonNull(callback, "callback == null");
	// 拿到Okhttp的call
    okhttp3.Call call;
    Throwable failure;

    synchronized (this) {
    
    
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;

      call = rawCall;
      failure = creationFailure;
      if (call == null && failure == null) {
    
    
        try {
    
    
         // 默认使用的就是OkhttpClient的 new Call,上面说过,虽然用的是工厂,但是还是直接new的OkHttpClient
          call = rawCall = createRawCall();
        } catch (Throwable t) {
    
    
          throwIfFatal(t);
          failure = creationFailure = t;
        }
      }
    }

// 调用请求
    call.enqueue(
        new okhttp3.Callback() {
    
    
          @Override
          public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
    
    
            Response<T> response;
            try {
    
    
            // 请求成功之后,来解析这个请求,返回真正的锁需要的
              response = parseResponse(rawResponse);
            } catch (Throwable e) {
    
    
              throwIfFatal(e);
              callFailure(e);
              return;
            }
          private void callFailure(Throwable e) {
    
    
            try {
    
    
              callback.onFailure(OkHttpCall.this, e);
            } catch (Throwable t) {
    
    
                          t.printStackTrace(); // TODO this is not great
            }
          }
        });
  }
}

// 解析返回值
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
    
    
 try {
    
    
   // responseConverter 就是默认的转化器,GsonRequestBodyConverter也可以,序列化成对应的格式
      T body = responseConverter.convert(catchingBody);
      return Response.success(body, rawResponse);
    } catch (RuntimeException e) {
    
    
      // If the underlying source threw an exception, propagate that rather than indicating it was
      // a runtime exception.
      catchingBody.throwIfCaught();
      throw e;
    }
}

结尾:

全文讲解到了Retrofit的使用与方法示例;以及Retrofit的源码分析,全文篇幅较长可收藏反复学习阅读。

最近女闺蜜对我可温柔了,老是给我东西吃,还要经常来我家玩;要我晚上传输更多的Android开发知识;我最近晚上忙的整理出的Android开发学习资料偷偷留给你们一份《Android开发核心技术知识》,不说了女闺蜜叫我了。

猜你喜欢

转载自blog.csdn.net/Androidxiaofei/article/details/125083956