Retrofit:网络框架

前言

从我刚开始Android开发的时候,我总会羡慕大牛们写的APP都是网络请求数据相关的,总觉得这个网络请求是最难的部分,当我学完JAVA和Kotlin的时候,再回头看网络,其实都是由输入输出流来放入Socket套接字进行UDP连接。但是对于我刚开始学的时候,总是需要不停地看Retrofit的介绍和博客,总是很复杂,感觉没有最明确的告诉你如何去使用,于是一年多使用了几次Retrofit的时候,想要自己去写一篇博客,以供自己回顾。

(注:前面可以只看什么是网络请求和如何利用Java使用Retrofit,源码解析及以后的可以以后在写)

什么是网络请求?

当我们看一些牛人的代码的时候,我们总能看到一些仓库数据类叫什么RemoteRepo,LocalRepo 什么意思?就是说我的APP数据来源于两个地方:有可能是我请求服务器时候拿来的数据(个人信息等等),有可能是我本地存储的数据(历史记录等等),在互联网中请求不属于本地服务器的数据,就是网络请求。在网络请求中,我们分为了几个步骤:

  • 配置网络请求参数(Build request)
  • 创建网络请求对象 (Call requesut)
  • 发送网络请求 (Call request)
    解析数据 (Converter(转换器))
  • 返回数据给对象
  • 拿到对象UI展示

我们把网络请求分为这几步,具体其实分的很细,接下来我们将具体怎么做的时候,会来分别的说清楚这几步。

什么是Retrofit?(Retrofit2.0)

每当我看很多博客的时候,对于Retrofit的介绍这里,总是很多很多看不懂的东西。在这里,如果对于一个框架进行一个介绍,那么那就是一个API,一套封装好的库来进行开发,但是对于Retrofit来说,这个封装是针对另一个框架Okhttp的封装,所以对于网络请求来说,就是Okhttp在做,我们只是用封装的封装罢了。

那么Retrofit怎么去做网络处理请求呢?

  1. Build request

    通过注解的形式来进行网络请求配置请求参数

  2. Call request

    通过动态代理创建网络请求的执行器和适配器并且发送网络请求

  3. Converter

    服务器返回数据以后去利用转换器来转换接收的数据

  4. Executor

    切换线程,处理数据

这里我想说一下为什么要切换线程?对于Android来说,我们总是期待用户进行点击或者一些活动的时候APP给予最快速率的响应,但是在我们系统来说有很多耗时的操作,我们为了加快响应用户的行为而对数据的加载切换到子线程中进行,而在主线程(UI、Activity、Fragment)来进行呈现数据给用户。(具体见引用中对于多线程任务的描述)

总结:Retrofit经过几年的发展,以独特方便的注解方式、高效性能、设计风格广受开发者的好评,成为近几年来最受欢迎的网络请求框架。

如何使用Retrofit?(Java)

项目地址:

对于一个项目而言,首先要明确你是有一个数据类,有一个封装着网络配置参数的类,在MainActivity中进行切换线程的网络请求操作和(无优化,后面会写如何优化)

  • 如何去写数据类?

首先要明确,后端给你的api文档大概是这样的

img

其他的也是大同小异,如果给你.yml文件在这个网站打开也是这个样

看不懂?不要紧,我们一步一步来

如果我们想要请求一个User.login的数据,如何创建数据类呢?

首先我们应该查看api文档中的Model中的你需要请求数据的Json格式数据

img

也就是这里的

1
2
3
4
5
6
7
8
9
id	integer($int64)
username string
firstName string
lastName string
email string
password string
phone string
userStatus integer($int32)
User Status

在Android Studio中利用一个插件来自动生产我们的数据类GsonFormaster(使用方法推荐视频:https://www.bilibili.com/video/BV1WJ411X7Jt?p=5(10分钟就能完成))

最后你就可以得到一个类似与这样的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package com.example.film_review.firstpage.attention;

public class AttentionItemData {


/**
* User_id : string
* Name : string
* User_picture : string
* Review_id : 0
* Title : string
* Content : string
* Time : string
* Tag : string
* Picture : string
* Comment_sum : 0
* Like_sum : 0
*/

private String User_id;
private String Name;
private String User_picture;
private int Review_id;
private String Title;
private String Content;
private String Time;
private String Tag;
private String Picture;
private int Comment_sum;
private int Like_sum;

public String getUser_id() {
return User_id;
}

public void setUser_id(String User_id) {
this.User_id = User_id;
}

public String getName() {
return Name;
}

public void setName(String Name) {
this.Name = Name;
}

public String getUser_picture() {
return User_picture;
}

public void setUser_picture(String User_picture) {
this.User_picture = User_picture;
}

public int getReview_id() {
return Review_id;
}

public void setReview_id(int Review_id) {
this.Review_id = Review_id;
}

public String getTitle() {
return Title;
}

public void setTitle(String Title) {
this.Title = Title;
}

public String getContent() {
return Content;
}

public void setContent(String Content) {
this.Content = Content;
}

public String getTime() {
return Time;
}

public void setTime(String Time) {
this.Time = Time;
}

public String getTag() {
return Tag;
}

public void setTag(String Tag) {
this.Tag = Tag;
}

public String getPicture() {
return Picture;
}

public void setPicture(String Picture) {
this.Picture = Picture;
}

public int getComment_sum() {
return Comment_sum;
}

public void setComment_sum(int Comment_sum) {
this.Comment_sum = Comment_sum;
}

public int getLike_sum() {
return Like_sum;
}

public void setLike_sum(int Like_sum) {
this.Like_sum = Like_sum;
}
}

这个类里面有什么?有一些参数,最后接收的网络请求数据封装在这个类里面,自动的有一些get与set方法

好了在正式开始Retrofit2.0之前,我们首先要了解一下什么是异步通信

Retrofit2.0采用的是异步通信的方法来进行网络请求。那有人要问了,这个异步和我有什么关系??我凭什么要了解这个事情,因为了解异步有助于去写一个更好的网络请求框架。

异步通信用数字逻辑的话来说就是没有使用同一个时钟源,也就是说网络请求分为什么?发送与接收。一般怎么做?客户端先发送网络请求,等发完了以后进行接收,这就是同步,也就是有先后顺序。而异步是我接收与发送是分开的,我这边接收是一个整体,发送是另一个整体,在发送之前我就已经准备好了接收,随时准备接收,而发送是可以晚一点进行的。

所以我们现在搞清楚了以后,来正式的写Retfoit

  1. 实例化Retrofit对象,创建网络请求接口实例、配置参数(Bulid request)
1
2
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("www.baidu.com")
  1. 异步的准备好接收数据(Converter)
1
2
3
4
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("www.baidu.com")
.addConverterFactory(GsonConverterFactory.create())
.build();

猜猜下一步要干什么?是发送数据!我们来这样想,Retrofit首先先配置好,然后创建好接收数据的客户端,最后来进行发送数据。

  1. 创建Retrofit接口!!!!前面的Retrofit是一个Class,而后面的是一个接口!
1
2
3
4
5
6
public interface RetrofitAPI {
@GET("api/v1/grounds/{user_id}")
Call<List<AttentionItemData>> getAttention(@Header("token") String token, @Path("user_id") String user_id);
}
//在这里说清楚请求资源的方式是GET还是POST还是什么,这里的api/v1/grounds/{user_id}是除了baseurl的url配置信息
//返回数据类型是一个List<类> 最后用这个类封装信息
  1. 在处理网络请求util类中实现接口端点(没有进行实例化对象,就是一个引用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
RetrofitAPI API = retrofit.create(RetrofitAPI.class);
API.enqueue(new Callback<List<AttentionItemData>>() {//异步发送
@Override
public void onResponse(Call<List<AttentionItemData>> call, Response<List<AttentionItemData>> response) {
//要处理里的事情,如果处理成功返回的response就是正确处理的List集合类
//一般就是通过一些UI组件来展示response.body();
}
@Override
public void onFailure(Call<List<AttentionItemData>> call, Throwable t) {
Log.e("TAG", "FAILURE");
t.printStackTrace();
//请求失败的处理结果
}
});

源码解析

在介绍了这些以后,我们还是需要来分析一下源码,在retrofit2的这种封装下,其实源码使用的是动态代理模式,我们看似只是通过接口从而拿到了方法,其实内部做了一个代理。具体如下:

我们点击enqueue方法来分析一下

1
2
3
4
5
public interface Call<T> extends Cloneable {

void enqueue(Callback<T> callback);

}

主要就是通过接口能够看到我们具体返回的对象是谁返回的list对象是谁

重点看一下retrofit.create(类.class)方法的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//Retrofit.java
public <T> T create(final Class<T> service) {
validateServiceInterface(service);
//1
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 Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
args = args != null ? args : emptyArgs;
return platform.isDefaultMethod(method)
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args);
}
});
}

重点是注解1,是一个动态代理,返回的是我们定义的RetrofitAPI的实例,说到动态代理,我们来讨论一下

动态代理就是实现了一个接口的对象,拦截接口的方法并且成功的添加的一些东西,是不是有点像Kotlin的扩展函数,只不过扩展函数是给类加了一些方法。

而对于Retrofit2来说:拦截到方法、参数,再根据我们在方法上的注解,去拼接为一个正常的Okhttp请求,然后执行。