在工作中用到封装HTTP传输的OkHTTP,OkHttp是相对成熟的解决方案,同时也是开源项目。本文将从源码角度看下OkHttp是如何实现一些网络操作的。

HTTP GET:

OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
return response.body().string();
} else {
throw new IOException("Unexpected code " + response);
}   //enqueue方式
  Call call2 = client.newCall(request);
  call2.enqueue(new Callback() {
@Override
public void onResponse(Response response) throws IOException {
System.out.println("调用enqueue返回的结果:"+response.body().string());
} @Override
public void onFailure(Request request, IOException e) { }
  });
}

  

Request是OkHttp中访问的请求,Builder是辅助类。Response即OkHttp中的响应。

Builder作用见: http://www.cnblogs.com/mywy/p/5103683.html

execute和enqueue方法的区别

1.调用execute方法会立即执行我们的请求,execute()的代码逻辑如下

public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
try {
//立即把本次Call放入 已执双端行队列中
client.getDispatcher().executed(this);
//调用
Response result = getResponseWithInterceptorChain(false);
if (result == null) throw new IOException("Canceled");
return result;
} finally {
//从执行完成队列中移除
client.getDispatcher().finished(this);
}
}

execute的逻辑是首先判断 是否已经执行过,如果已经执行了则直接抛出异常,也就是说一个Call 的实例只能调用一次execute方法。 Call类的注释:

A call is a request that has been prepared for execution. A call can be canceled. As this object represents a single request/response pair (stream), it cannot be executed twice.

如果没有执行过则首先client调用Dispatcher的executed(Call call)方法把本次调用加入已经执行的双端队列中,调用getResponseWithInterceptorChain方法返回Response对象,从已经执行的双端队列中移除本次Call,返回response对象。

2.调用enqueue方法执行代码逻辑如下:

public void enqueue(Callback responseCallback) {
enqueue(responseCallback, false);
} void enqueue(Callback responseCallback, boolean forWebSocket) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
client.getDispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
}

  

其实是调用了Call内部的私有enqueue方法,同样的也进行了判断本次Call是否是第一次调用,如果是第一次调用则调用client获取Dispatcher对象,然后调用enqueue(AsyncCall call)进行入队操作。

AsyncCall 是Call的内部类,final修饰,继承了NamedRunnable,而NamedRunnable  实现了Runnable接口,可以指定线程的名称。

NamedRunnbale提供了一个抽象方法execute()来供AsyncCall 实现

AsyncCall 的实现逻辑:

@Override protected void execute() {
boolean signalledCallback = false;
try {
//同样调用getResponseWithInterceptorChain方法来获取Response对象
Response response = getResponseWithInterceptorChain(forWebSocket);
if (canceled) {
signalledCallback = true;
//取消执行的时候调用
responseCallback.onFailure(originalRequest, new IOException("Canceled"));
} else {
signalledCallback = true;
//执行成功的回调
responseCallback.onResponse(response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
} else {
//网络请求失败的时候
responseCallback.onFailure(engine.getRequest(), e);
}
} finally {
//从dispatcher 删除本次Call
client.getDispatcher().finished(this);
}
}
}

通过该段代码可以很清楚的看到我们在通过异步请求的时候传入的回调函数Callback 就是在这里调用的。client.getDispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));发生了

调用execute的网络请求时序图:

在哪里调用等待队列中的请求?

当AsyncCall中的execute执行的时候,代码会走到finally块,OkHttpClient 调用getDispatcher()方法获取Dispathcer对象,然后调用finished方法。下面我们看一下finished方法里面的代码逻辑:

synchronized void finished(AsyncCall call) {
//从正在执行队列中移除本次Call
if (!runningCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
///把准备队列中的请求加入到执行队列中,并且执行其他请求
promoteCalls();
}

  

 

promoteCalls的执行逻辑:

//把准备队列中的请求加入到执行队列中,并且执行其他请求
private void promoteCalls() {
//判断是否超过了最大的请求数量(默认为64个)
if (runningCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyCalls.isEmpty()) return; // No ready calls to promote.
//遍历准备队列
for (Iterator<AsyncCall> i = readyCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
//把准备队列中的请求加入到执行队列中
runningCalls.add(call);
//调用线程池,执行本次线线程
getExecutorService().execute(call);
}
//执行队列已经满了,不再添加执行任务。
if (runningCalls.size() >= maxRequests) return; // Reached max capacity.
}
}

  

 

getResponseWithInterceptorChain方法内部实现逻辑:

private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
//实例化一个拦截器
Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
//调用proceed方法获取Response对象
return chain.proceed(originalRequest);
}

  

proceed处理逻辑:

核心方法Response getResponse(Request request, boolean forWebSocket)实现逻辑:

Response getResponse(Request request, boolean forWebSocket) throws IOException {
// Copy body metadata to the appropriate request headers.
//post有http请求body
RequestBody body = request.body();
if (body != null) {
//完善http请求头信息添加Content-Type,Content-Length
Request.Builder requestBuilder = request.newBuilder();
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
//获取请求body的长度
long contentLength = body.contentLength();
if (contentLength != -1) {
//body长度确定 一次性发送给服务器
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
//body长度不确定设置标记为Transfer-Encoding:chunked
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
request = requestBuilder.build();
}
// Create the initial HTTP engine. Retries and redirects need new engine for each attempt.
//初始化Http 引擎,重新连接,重定向都需要一个新的引擎。
engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null, null);
//记录重新请求的次数
int followUpCount = 0;
while (true) {
//是否已经取消执行
if (canceled) {
//释放连接
engine.releaseConnection();
throw new IOException("Canceled");
}
try {
//发起请求
engine.sendRequest();
//获取响应
engine.readResponse();
} catch (RequestException e) {
// The attempt to interpret the request failed. Give up.
throw e.getCause();
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
//路由失败,视图重新构建HttpEngine进行连接
HttpEngine retryEngine = engine.recover(e);
if (retryEngine != null) {
//如果恢复过来重新发送请求,重新获取response对象
engine = retryEngine;
continue;
}
// Give up; recovery is not possible.
throw e.getLastConnectException();
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
//网络连接失败从新获取连接
HttpEngine retryEngine = engine.recover(e, null);
if (retryEngine != null) {
//如果恢复过来重新发送请求,重新获取response对象
engine = retryEngine;
continue;
}
// Give up; recovery is not possible.
//抛出异常 无法重新连接
throw e;
}
//获取响应
Response response = engine.getResponse();
//获取重新请求对象
Request followUp = engine.followUpRequest();
//重新发情请求对象为空,说明无需重新构建请求,直接返回响应对象response
if (followUp == null) {
//没有采用websocket,无需进维护连接,释放连接
if (!forWebSocket) {
//释放连接
engine.releaseConnection();
}
//返回response退出循环
return response;
}
//如果重连的次数超过最大连接数这里默认是20,则抛出异常
if (++followUpCount > MAX_FOLLOW_UPS) {
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
if (!engine.sameConnection(followUp.httpUrl())) {
engine.releaseConnection();
}
Connection connection = engine.close();
request = followUp;
//从新构建http引擎
engine = new HttpEngine(client, request, false, false, forWebSocket, connection, null, null,
response);
}
}

  

底层如何建立连接和服务器进行交互?

  • 如何发送请求头?
  • 如何发送请求体?
  • 如何接受服务器响应?

这里要使用到一个关键的接口:Transport

可以看出该接口有两个实现类,一个是HttpTransport,支持用来http协议。另一个是FramedTransport用来支持spdy协议。该接口定义了一系列的方法来支持我们

向服务器发送请求头、请求体、创建请求体等。

HttpTransport:

public HttpTransport(HttpEngine httpEngine, HttpConnection httpConnection) {
/传入HttpEngine 对象
this.httpEngine = httpEngine;
//传入HttpConnection 对象
this.httpConnection = httpConnection;
}

  

 

FramedTransport:

  

public FramedTransport(HttpEngine httpEngine, FramedConnection framedConnection) {
//传入HttpEngine 对象
this.httpEngine = httpEngine;
////传入HttpConnection 对象
this.framedConnection = framedConnection;
}

我们可以看到实际上Transport实现类的相关方法是调用HttpConnection(支持Http协议)或FramedConnection(支持spdy协议)来发送请求头、请求体、获取响应对象的。


总结

OKHttpClient是一个基于java优秀的网络请求框架,

  • 支持SPDY, 可以合并多个到同一个主机的请求
  • 使用连接池技术减少请求的延迟(如果SPDY是可用的话)
  • 使用GZIP压缩减少传输的数据量
  • 缓存响应避免重复的网络请求

当你的网络出现拥挤的时候,就是OKHttp 大显身手的时候, 它可以避免常见的网络问题,如果你的服务是部署在不同的IP上面的,如果第一个连接失败, OkHTtp会尝试其他的连接. 这个对现在IPv4+IPv6 中常见的把服务冗余部署在不同的数据中心上.  OkHttp 将使用现在TLS特性(SNI ALPN) 来初始化新的连接. 如果握手失败, 将切换到SLLv3使用OkHttp很容易,   同时支持 异步阻塞请求和回调。我们可以结合自己的项目合理应用起来帮助我们构建健壮的应用程序。

下面整理出一般的OkHttp工具类:

import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.message.BasicNameValuePair;
import cn.wiz.sdk.constant.WizConstant;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response; public class OkHttpUtil {
private static final OkHttpClient mOkHttpClient = new OkHttpClient();
static{
mOkHttpClient.setConnectTimeout(30, TimeUnit.SECONDS);
}
/**
* 该步会开启异步线程。
* @param request
* @return
* @throws IOException
*/
public static Response execute(Request request) throws IOException{
return mOkHttpClient.newCall(request).execute();
}
/**
* 开启异步线程访问网络
* @param request
* @param responseCallback
*/
public static void enqueue(Request request, Callback responseCallback){
mOkHttpClient.newCall(request).enqueue(responseCallback);
}
/**
* 开启异步线程访问网络, 且不在意返回结果(实现空callback)
* @param request
*/
public static void enqueue(Request request){
mOkHttpClient.newCall(request).enqueue(new Callback() { @Override
public void onResponse(Response arg0) throws IOException { } @Override
public void onFailure(Request arg0, IOException arg1) { }
});
}
public static String getStringFromServer(String url) throws IOException{
Request request = new Request.Builder().url(url).build();
Response response = execute(request);
if (response.isSuccessful()) {
String responseUrl = response.body().string();
return responseUrl;
} else {
throw new IOException("Unexpected code " + response);
}
}
private static final String CHARSET_NAME = "UTF-8";
/**
* 这里使用了HttpClinet的API。只是为了方便
* @param params
* @return
*/
public static String formatParams(List<BasicNameValuePair> params){
return URLEncodedUtils.format(params, CHARSET_NAME);
}
/**
* 为HttpGet 的 url 方便的添加多个name value 参数。
* @param url
* @param params
* @return
*/
public static String attachHttpGetParams(String url, List<BasicNameValuePair> params){
return url + "?" + formatParams(params);
}
/**
* 为HttpGet 的 url 方便的添加1个name value 参数。
* @param url
* @param name
* @param value
* @return
*/
public static String attachHttpGetParam(String url, String name, String value){
return url + "?" + name + "=" + value;
}
}

  

OkHttp 源码分析的更多相关文章

  1. Okhttp源码分析--基本使用流程分析

    Okhttp源码分析--基本使用流程分析 一. 使用 同步请求 OkHttpClient okHttpClient=new OkHttpClient(); Request request=new Re ...

  2. Okhttp同步请求源码分析

    进阶android,OKhttp源码分析——同步请求的源码分析 OKhttp是我们经常用到的框架,作为开发者们,我们不单单要学会灵活使用,还要知道他的源码是如何设计的. 今天我们来分析一下OKhttp ...

  3. Android面试题-OkHttp3源码分析

    本文配套视频: okhttp内核分析配套视频一 okhttp内核分析配套视频二 okhttp内核分析配套视频三 源码分析相关面试题 Volley源码分析 注解框架实现原理 基本使用 从使用方法出发,首 ...

  4. Android开源框架源码分析:Okhttp

    一 请求与响应流程 1.1 请求的封装 1.2 请求的发送 1.3 请求的调度 二 拦截器 2.1 RetryAndFollowUpInterceptor 2.2 BridgeInterceptor ...

  5. okhttp框架源码分析从同步&异步请求使用开始

    对于okhttp在如今项目中的普及程度已经不言而喻啦,基本上如今网络请求都会基于它去进行封装,而非前几年用Android的网络框架HttpURLConnection和Apache HttpClient ...

  6. Retrofit源码分析(一)

    1.基本用法 创建接口 public interface GitHubService { @GET("users/{user}/repos") Observable<List ...

  7. OKHttp源码解析

    http://frodoking.github.io/2015/03/12/android-okhttp/ Android为我们提供了两种HTTP交互的方式:HttpURLConnection 和 A ...

  8. 深入理解OkHttp源码(一)——提交请求

    本篇文章主要介绍OkHttp执行同步和异步请求的大体流程.主要流程如下图: 主要分析到getResponseWidthInterceptorChain方法,该方法为具体的根据请求获取响应部分,留着后面 ...

  9. 深入理解OkHttp源码(三)——网络操作

    这篇博客侧重于了解OkHttp的网络部分,包括Socket的创建.连接,连接池等要点.OkHttp对Socket的流操作使用了Okio进行了封装,本篇博客不做介绍,想了解的朋友可以参考拆轮子系列:拆O ...

随机推荐

  1. C++程序结构---1

    C++ 基础教程Beta 版 原作:Juan Soulié 翻译:Jing Xu (aqua) 英文原版 本教程根据Juan Soulie的英文版C++教程翻译并改编. 本版为最新校对版,尚未定稿.如 ...

  2. python 批量爬取代理ip

    import urllib.request import re import time import random def getResponse(url): req = urllib.request ...

  3. 转:如何理解c和c ++的复杂类型声明

    本文作者girlrong是网易广州社区的C语言版版主,这篇文章被选在精华区.很是不错,不敢独享!据说她乐于助人,虚心诚恳,颇受网友欢迎.只可惜现在已退隐江湖了.在最近学习C语言过程中,了解些前辈大牛的 ...

  4. Apache运行模式

    Apache 2.X 支持插入式并行处理模块,称为多路处理模块(MPM: Multi-Processing Modules).在编译apache时必须选择也只能选择一个MPM,对类UNIX系统,有几个 ...

  5. php文件遍历

    <?php $dirname="shangchuan/uploads"; echo $dirname."共计大小为:".toSize(dirsize($d ...

  6. (转) ICML2016 TUTORIAL参会分享

    ICML2016 TUTORIAL参会分享 本文转自: https://mp.weixin.qq.com/s?__biz=MzI3MDE4NTk4MQ==&mid=2658399541& ...

  7. CLR thread pool

    Thread Pooling https://msdn.microsoft.com/en-us/library/windows/desktop/ms686756(v=vs.85).aspx Threa ...

  8. C# 数组,ArrayList与List对象的区别

    在C#中,当我们想要存储一组对象的时候,就会想到用数组,ArrayList,List这三个对象了.那么这三者到底有什么样的区别呢? 我们先来了解一下数组,因为数组在C#中是最早出现的. 数组 数组有很 ...

  9. MySQL : interactive_timeout v/s wait_timeout

    Most of the database intensive applications are worring about the default values of these variables ...

  10. 常用MIME类型

    后缀名       MIME名称*.3gpp    audio/3gpp, video/3gpp*.ac3    audio/ac3*.asf       allpication/vnd.ms-asf ...