Anroid框架-Okhttp

转载

一、OkHttp的优点
  • 1、拦截器,责任链模式,简化逻辑。每层只需要关注自己的责任,各层之间通过约定的接口/协议进行合作
  • 2、默认缓存拦截器会获取 请求头、响应头 Cache-control 配置 可由前后端配合控制缓存的有效性。
  • 3、连接池,避免了频繁创建和回收请求连接,节省资源。
  • 4、使用 GZIP压缩,减少传输的数据量
二、OkHttp执行请求的整个流程

OkHttpClient.Builder > OkHttpClient > + Request > RealCall > execute / enqueue >
拦截器链 > CallServerInterceptor请求网络

Okhttp 内部 socket 连接成功后,会获取 socket 的输入、输出流,用Okio包装,
封装成一个 HttpCodeC 对象,用于操作请求信息的写入、和返回信息的读取

设置超时时间
添加自定义拦截器
配置Https
调用 build 方法构建 OkHttpClient对象
OkHttpClient#newCall 传入Request
构建 RealCall 对象
同步请求 RealCall # execute
异步请求 RealCall # enqueue
异步请求
running 请求队列已满
最多同时64个请求
同一个host最多同时5个请求
异步请求
running 请求队列未满
从 ready队列移除
放入 running队列
core==0 max==Integer#MAX_VALUE
的线程池中执行
OkHttpClient.Builder
OkHttpClient
okhttp3 # Call # Factory 的实现类
用于创建 RealCall 对象
Request
url/method/header/body
RealCall
Diapatcher#execute
Diapatcher#enqueue
塞入等待执行队列 readyAsyncCalls
塞入正在执行队列 runningAsyncCalls
用线程池执行请求
拦截器#责任链模式调用
自定义拦截器
重试拦截器 RetryAndFollowUpInterceptor
转换拦截器 BridgeInterceptor
包含gzip压缩与解压处理
缓存拦截器 CacheInterceptor
解析 请求头-响应头-Cache-Control 控制缓存
连接拦截器 ConnectInterceptor
内部连接池 实现 连接复用
网络拦截器 networkInterceptors
配置 OkHttpClient 时设置的
请求拦截器 CallServerInterceptor
Response
code/message/header/body
Response会顺着拦截器链逐个往上返回
三、OkHttp中的拦截器

责任链模式:
它包含了一些命令对象和一系列的处理对象,每一个处理对象决定它能处理哪些命令对象,
它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象
该模式还描述了往该处理链的末尾添加新的处理对象的方法。

Interceptor 把实际的网络请求、缓存、透明压缩等功能都统一了起来,每一个功能都只是一个
Interceptor,它们再连接成一个 Interceptor.Chain,环环相扣,最终圆满完成一次网络请求。

1)首先调用的是,在配置 OkHttpClient 时设置的 interceptors

2)RetryAndFollowUpInterceptor:负责失败重试以及重定向的

3)BridgeInterceptor:负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应的

4)CacheInterceptor:负责读取缓存直接返回、更新缓存的

5)ConnectInterceptor:负责和服务器建立连接的

6)networkInterceptors:配置 OkHttpClient 时设置的

7)CallServerInterceptor:负责向服务器发送请求数据、从服务器读取响应数据的

拦截器链会逐个调用,如果中间没有拦截器完成处理,会一直往下调用 CallServerInterceptor 执行真正的网络请求
请求的结果会返回给上一个调用的拦截器,逐级返回
这个过程类似View的事件机制。

四、OkHttp中的同步请求与异步请求的理解及其源码

1、同步请求

String run(String url) throws IOException {
	Request request = new Request.Builder()
	  .url(url)
	  .build();

	Response response = client.newCall(request).execute();
	return response.body().string();
}

final class RealCall implements Call {
	@Override 
	public Response execute() throws IOException {
		synchronized (this) {
			if (executed) throw new IllegalStateException("Already Executed");  // (1)
			executed = true;
		}
		try {
			client.dispatcher().executed(this);                                 // (2)
			Response result = getResponseWithInterceptorChain();                // (3)
			if (result == null) throw new IOException("Canceled");
			return result;
		} finally {
			client.dispatcher().finished(this);                                 // (4)
		}
	}
}

1)检查这个 call 是否已经被执行了,每个 call 只能被执行一次
如果想要一个完全一样的 call,可以利用 call#clone 方法进行克隆。

2)利用 client.dispatcher().executed(this) 来进行实际执行,
dispatcher 是刚才看到的 OkHttpClient.Builder 的成员之一,
它的文档说自己是异步 HTTP 请求的执行策略,现在看来,同步请求它也有掺和。

3)调用 getResponseWithInterceptorChain() 函数获取 HTTP 返回结果,
从函数名可以看出,这一步还会进行一系列“拦截”操作。

4)最后还要通知 dispatcher 自己已经执行完毕。

dispatcher 这里我们不过度关注,在同步执行的流程中,
涉及到 dispatcher 的内容只不过是告知它我们的执行状态,
比如开始执行了(调用 executed),比如执行完毕了(调用 finished),在异步执行流程中它会有更多的参与。

2、建立连接 ConnectInterceptor

@Override
public Response intercept(Chain chain) throws IOException {
	RealInterceptorChain realChain = (RealInterceptorChain) chain;
	Request request = realChain.request();
	StreamAllocation streamAllocation = realChain.streamAllocation();

	// We need the network to satisfy this request. Possibly for validating a conditional GET.
	boolean doExtensiveHealthChecks = !request.method().equals("GET");
	HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
	RealConnection connection = streamAllocation.connection();
	return realChain.proceed(request, streamAllocation, httpCodec, connection);
}

建立连接就是创建了一个 HttpCodec 对象,它将在后面的步骤中被使用。
Http1Codec:对应 HTTP/1.1 的实现
Http2Codec:对应 HTTP/2 的实现

Http1Codec 中,它利用 Okio 对 Socket 的读写操作进行封装
Okio 对 java.io 和 java.nio 进行了封装,让我们更便捷高效的进行 IO 操作。

1)首先获取 StreamAllocation 对象,StreamAllocation中封装了 连接池 ConnectionPool
ConnectionPool 连接池中有一个队列,保存了 RealConnection 对象

2)根据请求信息,先从连接池中找到能用的 RealConnection
找不到则创建新的 RealConnection对象并返回

3)利用 RealConnection 的输入输出(BufferedSource 和 BufferedSink)创建 HttpCodec 对象

4)RealConnection的 BufferedSource 和 BufferedSink 是在 Socket 连接成功后创建的

public class RealConnection extends xxx implements Connection {
	
	private void connectSocket(int connectTimeout, int readTimeout, Call call, 
									EventListener eventListener) throws IOException {
		Proxy proxy = route.proxy();
		Address address = route.address();
		rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
			? address.socketFactory().createSocket()
			: new Socket(proxy);
		eventListener.connectStart(call, route.socketAddress(), proxy);
		rawSocket.setSoTimeout(readTimeout);
		try {
		  Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
		} catch (ConnectException e) {
		  ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
		  ce.initCause(e);
		  throw ce;
		}
		// The following try/catch block is a pseudo hacky way to get around a crash on Android 7.0
		// More details:
		// https://github.com/square/okhttp/issues/3245
		// https://android-review.googlesource.com/#/c/271775/
		try {
		  //【BufferedSource】内部封装的是 socket.getInputStream(),用于接收服务器返回的数据
		  source = Okio.buffer(Okio.source(rawSocket));
		  //【BufferedSink】内部封装的是 socket.getOutputStream(),用于向服务器发送数据
		  sink = Okio.buffer(Okio.sink(rawSocket));
		} catch (NullPointerException npe) {
		  if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
			throw new IOException(npe);
		  }
		}
	  }
}

HttpCodec 中,持有了RealConnection 的输入输出(BufferedSource 和 BufferedSink)
就可以 发送数据到服务器 、 接收服务器数据 了。

3、发送和接收数据 CallServerInterceptor

@Override 
public Response intercept(Chain chain) throws IOException {
	
	HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
	StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
	Request request = chain.request();

	long sentRequestMillis = System.currentTimeMillis();
	
	/*【1】httpCodec中持有 RealConnection的 BufferedSource 和 BufferedSink 对象
	 * 可以通过 BufferedSink把数据发送给服务器
	 * 这里是把 request header 发送给服务器
	 */
	httpCodec.writeRequestHeaders(request);
	//【2】如果有 requetsBody,就向服务器发送
	if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
		Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
		BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
		request.body().writeTo(bufferedRequestBody);
		bufferedRequestBody.close();
	}
	httpCodec.finishRequest();
	//【3】读取 response header,先构造一个 Response 对象
	Response response = httpCodec.readResponseHeaders()
		  .request(request)
		  .handshake(streamAllocation.connection().handshake())
		  .sentRequestAtMillis(sentRequestMillis)
		  .receivedResponseAtMillis(System.currentTimeMillis())
		  .build();
	//【4】如果有 response body,就在【3】的基础上加上body,构造一个新的 Response 对象
	if (!forWebSocket || response.code() != 101) {
		response = response.newBuilder()
			.body(httpCodec.openResponseBody(response))
			.build();
	}
	if ("close".equalsIgnoreCase(response.request().header("Connection"))
		|| "close".equalsIgnoreCase(response.header("Connection"))) {
		streamAllocation.noNewStreams();
	}
	// 省略部分检查代码
	return response;
}

核心工作都由 HttpCodec 对象完成,HttpCodec 实际上是用Okio

4、异步请求

核心线程 0,最大线程数 Integer.MAX_VALUE,闲置时间60秒
readyAsyncCalls 等待执行请求队列
runningAsyncCalls 正在执行的请求队列

Okhttp:默认最大同时请求数
Okhttp:一个 host 最多能同时有 5个请求

private int maxRequests = 64;// 默认最大同时请求数
private int maxRequestsPerHost = 5;//一个 host 最多能同时有 5个请求

client.newCall(request).enqueue(new Callback() {
	@Override
	public void onFailure(Call call, IOException e) {
	}

	@Override
	public void onResponse(Call call, Response response) throws IOException {
		System.out.println(response.body().string());
	}
});

// RealCall#enqueue
@Override 
public void enqueue(Callback responseCallback) {
	synchronized (this) {
		if (executed) throw new IllegalStateException("Already Executed");
		executed = true;
	}
	client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

// Dispatcher#enqueue
public final class Dispatcher {
	
	//核心线程 0,最大线程数 Integer.MAX_VALUE,闲置时间60秒。
	public synchronized ExecutorService executorService() {
		if (executorService == null) {
			executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
			new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
		}
		return executorService;
	}

	synchronized void enqueue(AsyncCall call) {
		/* dispatcher 在异步执行时发挥的作用了,
		 * 如果当前还能执行一个并发请求,那就立即执行,否则加入 readyAsyncCalls 队列。
		 * 而正在执行的请求执行完毕之后,会调用 promoteCalls() 函数,
		 * 来把 readyAsyncCalls 队列中的 AsyncCall “提升”为 runningAsyncCalls,并开始执行。
		 */
		if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
			runningAsyncCalls.add(call);
			executorService().execute(call);
		} else {
			readyAsyncCalls.add(call);
		}
	}
}

Okhttp默认线程池:核心线程 0,最大线程数 Integer.MAX_VALUE,闲置时间60秒。

final class AsyncCall extends NamedRunnable {
	private final Callback responseCallback;

	AsyncCall(Callback responseCallback) {
	  super("OkHttp %s", redactedUrl());
	  this.responseCallback = responseCallback;
	}
	...
	//execute() 是在父类的 run()中调用的
	@Override 
	protected void execute() {
		boolean signalledCallback = false;
		try {
			//这里开始调用拦截器链,一个个拦截器开始执行
			Response response = getResponseWithInterceptorChain();
			//执行完成后,把结果通过 callback 回调到上层
			if (retryAndFollowUpInterceptor.isCanceled()) {
				signalledCallback = true;
				responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
			} else {
				signalledCallback = true;
				responseCallback.onResponse(RealCall.this, response);
			}
		} catch (IOException e) {
			...
		} finally {
			/* 执行完毕后会调用 Diaspatcher的 finish()
			 * finish()方法中会把当前请求从 runningAsyncCalls 队列中移除
			 */
			client.dispatcher().finished(this);
		}
	}
}
5、返回数据的获取

同步(Call#execute() 执行之后)或者异步(Callback#onResponse() 回调中)请求完成之后,
我们就可以从 Response 对象中获取到响应数据了,
包括 HTTP status code,status message,response header,response body 等。

这里 body 部分最为特殊,因为服务器返回的数据可能非常大,
所以 必须通过数据流的方式 来进行访问
(当然也提供了诸如 string() 和 bytes() 这样的方法将流内的数据一次性读取完毕),
而响应中其他部分则可以随意获取。

响应 body 被封装到 ResponseBody 类中,该类主要有两点需要注意:
1)每个 body 只能被消费一次,多次消费会抛出异常
2)body 必须被关闭,否则会发生资源泄漏

HttpCodec#openResponseBody 提供具体 HTTP 协议版本的响应 body,
HttpCodec 则是利用 Okio 实现具体的数据 IO 操作

这里有一点值得一提,OkHttp 对响应的校验非常严格
HTTP status line 不能有任何杂乱的数据,否则就会抛出异常。

6、HTTP 缓存

CacheInterceptor 位于 ConnectInterceptor 之前
在建立 连接之前,会先检查相应是否已经被缓存、缓存是否可用
如果是,则直接返回缓存的数据。否则进行后面的流程,并在返回之前把网络数据写入缓存。

OkHttp内置封装了一个 Cache 类
它会解析 请求头、响应头 Cache-Control,来控制是否使用缓存、以及缓存是否有效

它利用 DiskLruCache ,用磁盘上的有限大小空间进行缓存,按 LRU算法进行缓存淘汰

如果要自定义缓存策略,实现 InternalCache 接口,在构造 OkHttpClient 时设置。

7、总结
1)OkHttpClient 实现 Call.Factory,负责为 Request 创建 Call;
2)RealCall 为具体的 Call 实现,其 enqueue() 异步接口通过 Dispatcher 利用 ExecutorService 实现,
最终进行网络请求时和同步 execute() 接口一致,都是通过 getResponseWithInterceptorChain() 函数实现;
3)getResponseWithInterceptorChain() 中利用 Interceptor 链条,分层实现缓存、透明压缩、网络 IO 等功能;


五、OkHttp中涉及到的设计模式

Builder模式、责任链模式、外观模式(门面模式)、策略模式(缓存这块)

六、OkHttp底层网络请求实现,socket还是URLConnection

Socket

RealConnection#connect() 中会创建并连接 socket

七、HttpDns

使用自己信任的dns Http服务器,做dns域名解析,降低被劫持的风险。

定义:

HttpDns,是对DNS解析的另一种实现方式,将域名解析的协议由 DNS协议 换成 Http协议。

原理:

客户端直接访问HttpDNS接口,获取业务在域名配置管理系统上配置的访问延迟最优的IP
(基于容灾考虑,还是保留次选使用运营商LocalDNS解析域名的方式)

HttpDns优势:

绕过运营商的本地域名解析服务器,避免本地域名解析服务器缓存/劫持/插入广告

提供HttpDns解析服务的厂商:提供httpdns解析服务的有:
阿里云HttpDNS:Get请求,返回一个json
腾讯 DNSPod D+:Get请求,返回结果不是json,有免费版本
1、使用Interceptor,直接将域名替换为ip地址

1)优点
对Dns的控制偏上层,可更加细化,控制灵活。
容灾处理更容易

2)缺点 一切跟域名有关的处理全部失效
在Https下处理SSL证书会出现校验问题
ip访问时出现Cookie校验问题。

2、使用OkHttp提供的dns接口,新建Dns子类,实现lookup()方法。
1)优点

Https下不会存在证书校验问题,保证流程正常执行
种 Cookie 时不会存在问题

2)缺点

时机过于底层,容灾控制都不方便。
okhttp自身存在缓存,一旦dns自身ttl过期,okhttp缓存有可能还在使用,会存在一定的风险。

3)实现
compile 'com.qiniu:happy-dns:0.2.13'
compile 'com.squareup.okhttp3:okhttp:3.9.0'

public class HttpDns implements Dns {
	private DnsManager dnsManager;
	public HttpDns() {
		IResolver[] resolvers = new IResolver[1];
		try {
			resolvers[0] = new Resolver(getByName("119.29.29.29"));
			dnsManager = new DnsManager(NetworkInfo.normal, resolvers);
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
	}

	@Override
	public List<InetAddress> lookup(String hostname) throws UnknownHostException {
		if (dnsManager == null)  //当构造失败时使用默认解析方式
			return Dns.SYSTEM.lookup(hostname);

		try {
			String[] ips = dnsManager.query(hostname);  //获取HttpDNS解析结果
			if (ips == null || ips.length == 0) {
				return Dns.SYSTEM.lookup(hostname);
			}
			List<InetAddress> result = new ArrayList<>();
			for (String ip : ips) {  //将ip地址数组转换成所需要的对象列表
				result.addAll(Arrays.asList(getAllByName(ip)));
			}
			return result;
		} catch (IOException e) {
			e.printStackTrace();
		}
		//当有异常发生时,使用默认解析
		return Dns.SYSTEM.lookup(hostname);
	}
}

OkHttpClient okHttpClient = new OkHttpClient.Builder().dns(new HttpDns()).build();	

推荐阅读:
拆轮子系列-拆OkHttp

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值