OkHttp 流程浅析 - NoHarry的博客

简介

本文通过结合OkHttp源码,分析发送请求的大致流程。

  • 本文源码基于3.12.0版本

示例

首先我们创建一个最简单的请求,以此为例开始进行分析

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

OkHttpClient client=new OkHttpClient.Builder().build();

//创建Request

Request request=new Request

.Builder()

.url(url)

.build();

//发送一个异步请求

client.newCall(request).enqueue(new Callback() {

public void (Call call, IOException e) {

}

public void onResponse(Call call, Response response) throws IOException {

}

});

//发送一个同步请求

try {

Response response = client.newCall(request).execute();

} catch (IOException e) {

e.printStackTrace();

}

流程

1.1 创建请求

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
public final class Request {

final HttpUrl url;

final String method;

final Headers headers;

final @Nullable RequestBody body;

final Map<Class<?>, Object> tags;

private volatile @Nullable CacheControl cacheControl;

Request(Builder builder) {

this.url = builder.url;

this.method = builder.method;

this.headers = builder.headers.build();

this.body = builder.body;

this.tags = Util.immutableMap(builder.tags);

}

...

public static class Builder {

@Nullable HttpUrl url;

String method;

Headers.Builder headers;

@Nullable RequestBody body;

Map<Class<?>, Object> tags = Collections.emptyMap();

....

}

}

首先使用建造者模式构建一个Requst,来插入请求的数据。

1.2 封装请求

请求封装在了接口Call的实现类RealCall中:

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
final class RealCall implements Call {

...

private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {

//构建的OkHttpClient

this.client = client;

//用户构建的Request

this.originalRequest = originalRequest;

//是不是WebSocket请求

this.forWebSocket = forWebSocket;

//构建RetryAndFollowUpInterceptor拦截器

this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);

//Okio中提供的用于超时机制的方法

this.timeout = new AsyncTimeout() {

protected void timedOut() {

cancel();

}

};

this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);

}

...

}

1.3 执行请求

请求分为同步请求异步请求:

  • 同步请求:

    1
    
    2
    
    3
    
    4
    
    5
    
    6
    
    7
    
    8
    
    9
    
    10
    
    11
    
    12
    
    13
    
    14
    
    15
    
    16
    
    17
    
    18
    
    19
    
    20
    
    21
    
    22
    
    
    public Response execute() throws IOException {
    
    synchronized (this) {
    
    if (executed) throw new IllegalStateException("Already Executed");
    
    executed = true;
    
    }
    
    captureCallStackTrace();
    
    timeout.enter();
    
    eventListener.callStart(this);
    
    try {
    
    client.dispatcher().executed(this);
    
    Response result = getResponseWithInterceptorChain();
    
    if (result == null) throw new IOException("Canceled");
    
    return result;
    
    } catch (IOException e) {
    
    e = timeoutExit(e);
    
    eventListener.callFailed(this, e);
    
    throw e;
    
    } finally {
    
    client.dispatcher().finished(this);
    
    }
    
    }
    
  • 异步请求:
    1
    
    2
    
    3
    
    4
    
    5
    
    6
    
    7
    
    8
    
    9
    
    10
    
    
    public void enqueue(Callback responseCallback) {
    
    synchronized (this) {
    
    if (executed) throw new IllegalStateException("Already Executed");
    
    executed = true;
    
    }
    
    captureCallStackTrace();
    
    eventListener.callStart(this);
    
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
    
    }
    

从上面可以看到不论是同步请求还是异步请求都是在Dispatcher中进行处理,

区别在于:

  • 同步请求:直接执行executed,并返回结果
  • 异步请求:构造一个AsyncCall,并将其加入到readyAsyncCalls这个准备队列中
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
final class AsyncCall extends NamedRunnable {

private final Callback responseCallback;

AsyncCall(Callback responseCallback) {

super("OkHttp %s", redactedUrl());

this.responseCallback = responseCallback;

}

String host() {

return originalRequest.url().host();

}

Request request() {

return originalRequest;

}

RealCall get() {

return RealCall.this;

}

/**

* Attempt to enqueue this async call on {@code executorService}. This will attempt to clean up

* if the executor has been shut down by reporting the call as failed.

*/

void executeOn(ExecutorService executorService) {

assert (!Thread.holdsLock(client.dispatcher()));

boolean success = false;

try {

executorService.execute(this);

success = true;

} catch (RejectedExecutionException e) {

InterruptedIOException ioException = new InterruptedIOException("executor rejected");

ioException.initCause(e);

eventListener.callFailed(RealCall.this, ioException);

responseCallback.onFailure(RealCall.this, ioException);

} finally {

if (!success) {

client.dispatcher().finished(this); // This call is no longer running!

}

}

}

@Override protected void execute() {

boolean signalledCallback = false;

timeout.enter();

try {

Response response = getResponseWithInterceptorChain();

if (retryAndFollowUpInterceptor.isCanceled()) {

signalledCallback = true;

responseCallback.onFailure(RealCall.this, new IOException("Canceled"));

} else {

signalledCallback = true;

responseCallback.onResponse(RealCall.this, response);

}

} catch (IOException e) {

e = timeoutExit(e);

if (signalledCallback) {

// Do not signal the callback twice!

Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);

} else {

eventListener.callFailed(RealCall.this, e);

responseCallback.onFailure(RealCall.this, e);

}

} finally {

client.dispatcher().finished(this);

}

}

}

AsyncCall继承自NamedRunnable,而NamedRunnable可以看成一个会给其所运行的线程设定名字的Runnable,Dispatcher会通过ExecutorService来执行这些Runnable。

1.4 请求的调度

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
public final class Dispatcher {

private int maxRequests = 64;

private int maxRequestsPerHost = 5;

private @Nullable Runnable idleCallback;

/** Executes calls. Created lazily. */

private @Nullable ExecutorService executorService;

/** Ready async calls in the order they'll be run. */

private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */

private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

/** Running synchronous calls. Includes canceled calls that haven't finished yet. */

private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

void enqueue(AsyncCall call) {

synchronized (this) {

readyAsyncCalls.add(call);

}

promoteAndExecute();

}

synchronized void executed(RealCall call) {

runningSyncCalls.add(call);

}

private boolean promoteAndExecute() {

assert (!Thread.holdsLock(this));

List<AsyncCall> executableCalls = new ArrayList<>();

boolean isRunning;

synchronized (this) {

for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {

AsyncCall asyncCall = i.next();

if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.

if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.

i.remove();

executableCalls.add(asyncCall);

runningAsyncCalls.add(asyncCall);

}

isRunning = runningCallsCount() > 0;

}

for (int i = 0, size = executableCalls.size(); i < size; i++) {

AsyncCall asyncCall = executableCalls.get(i);

asyncCall.executeOn(executorService());

}

return isRunning;

}

}

请求的调度主要在Dispatcher类中进行,其中维护了3个双端队列:

  • readyAsyncCalls:准备队列用于添加准备执行的异步请求。
  • runningAsyncCalls:正在执行的异步请求队列。
  • runningSyncCalls:正在执行的同步请求队列。

对于同步请求,Dispatcher会直接将请求加入到同步请求队列执行;对于异步请求首先会将请求加入readyAsyncCalls中,接下来会遍历readyAsyncCalls判断如果当前执行的异步请求数量小于65并且同一host下的异步请求数小于5,则将readyAsyncCalls中的请求加入到runningAsyncCalls开始执行并从readyAsyncCalls中移除。

1.5 请求的执行

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
Response getResponseWithInterceptorChain() throws IOException {

// Build a full stack of interceptors.

List<Interceptor> interceptors = new ArrayList<>();

//用户自定义拦截器

interceptors.addAll(client.interceptors());

//用于处理请求失败时重试和重定向

interceptors.add(retryAndFollowUpInterceptor);

//给发送的添加请求头等信息,同时处理返回的响应使之转换成对用户友好的响应

interceptors.add(new BridgeInterceptor(client.cookieJar()));

//处理缓存相关逻辑

interceptors.add(new CacheInterceptor(client.internalCache()));

//处理建立连接相关

interceptors.add(new ConnectInterceptor(client));

if (!forWebSocket) {

interceptors.addAll(client.networkInterceptors());

}

//从服务器获取响应数据

interceptors.add(new CallServerInterceptor(forWebSocket));

Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,

originalRequest, this, eventListener, client.connectTimeoutMillis(),

client.readTimeoutMillis(), client.writeTimeoutMillis());

return chain.proceed(originalRequest);

}

可以说okhttp最核心的部分就是拦截器的这部分,这里采用责任链的设计模式,使各个功能充分解耦,各司其职,请求从用户自定义的拦截器开始层层传递到CallServerInterceptor,每层做出相应的处理,直到请求发出,与此同时,返回的响应从CallServerInterceptor开始逐层上传直到用户的自定义拦截器,每层都会对返回的响应做出相应处理,最终将处理好的响应结果返回给用户。


原文:大专栏  OkHttp 流程浅析 - NoHarry的博客

原文地址:https://www.cnblogs.com/wangziqiang123/p/11618292.html

时间: 2024-08-06 08:39:16

OkHttp 流程浅析 - NoHarry的博客的相关文章

浅析:个人博客网站如何才能盈利

楼主是一个崭新的小博客博主,纯粹的新手,下面来和大家分享一下经营博客怎么才能盈利,个人意见,欢迎大家一起来讨论.闲话少说,进入正题. 在互联网中,博客.论坛等等一系列网站就只是一个平台,这个平台搭建好之前,我们要想清楚一件事情,就是我们建立博客是里干什么的.我的肯定就是来赚钱的...这个目标明确了下面我们再来谈如何赚钱. 我推荐的博客网站盈利方式: 盈利方式一:通过广告联盟来赚钱. 我的博客投放广告选择的是百度广告联盟,当然审核比较难...不过还是推荐这个,百度广告联盟比一些小型的广告联盟跟让人

文顶顶iOS开发博客链接整理及部分项目源代码下载

文顶顶iOS开发博客链接整理及部分项目源代码下载 网上的iOS开发的教程很多,但是像cnblogs博主文顶顶的博客这样内容图文并茂,代码齐全,示例经典,原理也有阐述,覆盖面宽广,自成系统的系列教程却很难找.如果你是初学者,在学习了斯坦福iOS7公开课和跟着文顶顶的博客做项目之后,最快只需要2个月时间,就基本可以独立完成iOS App的开发工作.有经验的开发者也可以在该博客中寻找代码片段进行学习借鉴,必有所收获. 在此也向@文顶顶 表示严重感谢! 由于文顶顶博客博文繁多,每次找文章需要频繁的翻页,

lnmp环境下搭建wordpress博客程序

本文档主要介绍如何在lnmp环境下搭建完整的wordpress程序. 基本流程: 1.开源博客程序WordPress介绍 2.WordPress博客程序的搭建准备 3.开始安装blog博客程序 4.实现WordPress博客程序URL静态化 ---------------------------------------------------------------------------------------------------------------------------------

按照这样的流程玩博客,最后都到了这里

几天前,我已经把博客迁移到了 http://www.barretlee.com,而同样前几天看到 Nicholas C.Zakas 大师把自己的博客从 wordpress 迁移到了 jekyll,很巧的是我这几天也在干这件事情.不过我是迁移到 hexo,刚开始托管在 github,后来改到 gitcafe. 之前我捯饬过很多博客系统,也喜欢了解各个博客系统的实现方式,并且自己写插件.写主题.由于最开始接触的一门 web 后端语言是 php,所以先折腾小而美的 wordpress,后来发现它并不小

@借助 Alfred 简化博客发布流程

为了方便管理已经发布的博文,所以目前本地使用 Mweb 的外部模式对已有博文进行管理,所以整个博文发布流程变得有点繁琐,需要在终端与 Mweb 两个程序间进行不断的切换,而且每次发布过程都需要输入重复指令,如:hexo new post 'title', hexo clean, hexo g -d,git add -A git commit -m 'message'等等.为了能够简化这个发布流程,选择采用 Alfred 的工作流来解决这个问题. 目前的发布流程: 为什么要在 Mweb 外部模式和

【简单版】hexo博客搭建流程梳理

前言 本文章会为你梳理一个搭建hexo博客的流程 相关网址: Docs: https://hexo.io/docs/ Themes: https://hexo.io/themes/ 安装hexo 准备阶段-Git 和 nodejs 安装Git Windows: 下载然后安装Git [https://git-scm.com/download/win] 如果你下载慢,可以使用下面的链接 链接: https://pan.baidu.com/s/1HXujcEuaPZYFQLtzlSBf0Q 提取码:

测试流程博客.md

测试流程博客 如使用CSDN的看官,请转至notion地址(推荐)或者github pages,以获取更好的阅读体验. 1.在notion中写博客,比如这篇测试博客 2.导出csv 3.写脚本处理文件,unzip以及转到hexo对应的_post目录下 4.使用mweb一键发布csdn 5.快捷命令发布到github pages 原文地址:https://www.cnblogs.com/Alpes/p/ce-shi-liu-cheng-bo-kemd.html

浅析利用MetaWeblog接口同步多个博客

随着XML-RPC的越来越流行,MetaWeblog接口几乎成了目前最流行的离线Blog发送API.其能通过标准化的webservice接口,对任意blog进行添删改.目前,使用MetaWeblog接口发布离线博客客户端中流行的还是windows live writer.这里可以找到一些相关的设置方法. 话说回来,如何用metaweblog接口同步多个blog呢?当然你可以直接建立多个account来处理,但明显繁琐,也不符合我们diy的个性. 由于我自己个人Blog是完全自己写的,于是就不由想

博客开发流程

博客成品:邹振忠的博客 步骤: 1:列出博客大纲:用来干什么:为什么要做:怎么做: 2:列出博客的需求点 3:根据需求点整理出对应的技术文档 4:用workbench画出数据字典 5:开发好后列出我的博客测试文档,逐个测试. 6:上线. 7:复函: A:这次开发花了两个月的闲暇时间,其中60%以上花在了前端上. A.1:自己的前端功力还有待提高. A.1.1:这后面将会输出点击事件:普通点击,未来事件,iframe点击专题文章. A.1.2:输出jquery bootstrap源码解读文章. A