0%

10-retrofit

注意:Retrofit 的源码是基于 2.0.1

一、切面编程

Okhttp 作为一个网络库是非常好的了,因为它采用了AOP切面思想(那些 Interceptors),也实现高内聚低耦合,但是在实际使用的时候有一些问题:

  • 用户网络请求的接口配置繁琐,尤其需要配置复杂请求body 、请求头 的时候

  • 数据解析过程中需要用户手动拿到 responseBody 进行解析,(请求参数)不能复用

  • 无法适配自动进行线程切换

  • 存在嵌套的网络请求(如:UserId 获取到之后再获取用户详细信息,详细信息获取后再下单),就会陷入“回调地狱”

基于此,Retrofit 就诞生了,它主要解决 2 个问题:

  • 请求前:统一配置网络的请求头,一致适配请求 request

  • 请求后:数据适配(解析json数据)、线程切换

Retrofit 总共 16个类,却采用了 8 种设计模式。

二、Retrofit 类的本身设计思想

2.1 Retrofit 的整体使用

总体分为4个步骤:

  1. 构建一个Retrofit

  2. 基于访问接口创建一个 接口类型的对象

  3. 基于接口类型的对象以及参数构建一个 Okhttp Call

  4. 将请求 Call 添加到 Okhttp 的请求队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//第一步
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();

//第二步
GitHubService service = retrofit.create(GitHubService.class);

//第三步
Call<List<Repo>> repos = service.listRepos("octocat");

//第四步
repos.enqueue(new Callable<List<Repo>>(){
public void onResponse(XX xx, YY yy) {

}
public void onFailure(M m ,N n) {

}
});

其中,接口 GitHubService.java 的代码可能是这样的 :

1
2
3
4
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}

根据上面的代码,我们可以有几个疑问:

  • 看第二步,貌似我们的接口被直接使用了?根据我们学习的 Java 知识,接口不能被直接使用啊,没有接口实例啊

  • 第三步传入的 参数 “octocat” 到底去哪里了

  • url 到底怎么形成的?因为我们传入的只有一个主域名 “https://api.github.com/"

  • 为什么所有的请求都是同样的方式

当你一行行看代码的时候就完蛋了,就会陷入细节,应该先看框架,看大体,思路流程好了之后再看细节。

参数大于 5 个 && 带有可选参数,我们就可以使用构建者模式,这是使用 Builder 的一个原则。

三、Retrofit 设计的 AOP 思想

从代码:

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

可以看出,retrofit 肯定创建了接口子类对象了, 不然不可能给接口变量赋值。在 Retrofit 类中这个 create 方法的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public <T> T create(final Class<T> service) {
validateServiceInterface(service);
return (T)
Proxy.newProxyInstance(
service.getClassLoader(),
new Class<?>[] {service},
new InvocationHandler() {
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;
Platform platform = Platform.get();
return platform.isDefaultMethod(method)
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args);
}
});
}

从上面可以看出来,这是动态代理实现的。它会生成一个

四、是否可以作为单例

Retrofit 在使用的时候,能不能封装成单例呢?很多同学在 build 出来 Retrofit 对象后,将其作为单例保存,但其实不建议这么做。老师说的是可能带来内存泄漏,在 build 的时候,会有很多变量都会保存起来在 Retrofit 中,都会一直占内存。(对于这个论据不太认可,网络是会贯穿整个 App 使用周期的,所以网络的对象持续整个 App 生命周期感觉问题也不大)

如果网络请求非常非常频繁,比如股票,那我们就可以考虑 netty 。普通 App 的请求使用完就可以销毁。

五、ServiceMethod 的存在价值

注解的使用:

  • apt 技术:生成代码

  • 注解 + 反射 运用

ServiceMethod 主要作用就是解析注解,解析请求返回值

将接口转变为 Okhttp 的 Call ,把注解和传入的参数一起封装在里面。所以,一个请求接口对应一个 ServiceMethod ,当一个 interface 中有多个 方法时,就会对应多个 ServiceMethod 。

默认的话,获取到的 Call 是 ExecutorCallbackCall 这个类型。第四步中的 enqueue 操作,最终会调用到 OkhttpCall 的 enqueue ,会里面会创建一个真正的 OkhttpCall 。

ServiceMethod 中封装了所有的变量,所以 toRequest 方法可以利用这些变量(包括 post/get 等)封装出一个 OKhttp 的 Request 对象。

谢谢你的鼓励