Android为我们提供了两种HTTP交互的方式:HttpURLConnection 和 Apache HTTP Client,虽然两者都支持HTTPS,流的上传和下载,配置超时,IPv6和连接池,已足够满足我们各种HTTP请求的需求。但更高效的使用HTTP 可以让您的应用运行更快、更节省流量。而OkHttp库就是为此而生。
okhttp特点:
OkHttp是一个高效的HTTP库:
1、支持 SPDY ,共享同一个Socket来处理同一个服务器的所有请求
2、如果SPDY不可用,则通过连接池来减少请求延时
3、无缝的支持GZIP来减少数据流量
4、缓存响应数据来减少重复的网络请求
SPDY
SPDY为speedy(单词原意:快速的)的缩写,读音也就是speedy。
speedy
英 [ˈspi:di] 美 [ˈspidi] adj.快的,迅速的;敏捷的
推荐阅读:SPDY、HTTP/2、QUIC协议
是Google开发的基于TCP的传输层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。SPDY并不是一种用于替代HTTP的协议,而是对HTTP协议的增强。新协议的功能包括数据流的多路复用、请求优先级以及HTTP报头压缩。谷歌表示,引入SPDY协议后,在实验室测试中页面加载速度比原先快64%。
GZIP
GZIP最早由Jean-loup Gailly和Mark Adler创建,用于UNⅨ系统的文件压缩。我们在Linux中经常会用到后缀为.gz的文件,它们就是GZIP格式的。现今已经成为Internet 上使用非常普遍的一种数据压缩格式,或者说一种文件格式。
HTTP协议上的GZIP编码是一种用来改进WEB应用程序性能的技术。大流量的WEB站点常常使用GZIP压缩技术来让用户感受更快的速度。这一般是指WWW服务器中安装的一个功能,当有人来访问这个服务器中的网站时,服务器中的这个功能就将网页内容压缩后传输到来访的电脑浏览器中显示出来.一般对纯文本内容可压缩到原大小的40%.这样传输就快了,效果就是你点击网址后会很快的显示出来.当然这也会增加服务器的负载. 一般服务器中都安装有这个功能模块的。
减少文件大小有两个明显的好处,一是可以减少存储空间,二是通过网络传输文件时,可以减少传输的时间。gzip 是在 Linux 系统中经常使用的一个对文件进行压缩和解压缩的命令,既方便又好用。
okhttp支持的协议:
okhttp连接池:
提高网络性能优化,很重要的一点就是降低延迟和提升响应速度。请求头的keep-alive 就是客户端和服务端之间保持长连接,这个连接是可以复用的。
okhttp线程调度类:
OkHttp同样支持网络缓存,OkHttp的缓存基于DiskLruCache:
1)支持http2,对一台机器的所有请求共享同一个socket
2)内置连接池,支持连接复用,减少延迟
3)支持透明的gzip压缩响应体
4)通过缓存避免重复的请求
5)请求失败时自动重试主机的其他ip,自动重定向
6)好用的API
其核心主要有路由(对地址的一个封装类)、连接协议、拦截器、代理、安全性认证、连接池以
及网络适配,拦截器主要是指添加,移除或者转换请求或者回应的头部信息。
上 面是OKHttp总体设计图,主要是通过Diapatcher不断从RequestQueue中取出请求(Call),根据是否已缓存调用Cache或 Network这两类数据获取接口之一,从内存缓存或是服务器取得请求的数据。该引擎有同步和异步请求,同步请求通过Call.execute()直接返 回当前的Response,而异步请求会把当前的请求Call.enqueue添加(AsyncCall)到请求队列中,并通过回调(Callback) 的方式来获取最后结果。
参考:
OKHttp源码解析
带你学开源项目:OkHttp-- 自己动手实现 okhttp
缺点
1、消息回来需要切到主线程,主线程要自己去写,
2、传入调用比较复杂。
简单的异步Get请求
第一步,创建OKHttpClient对象
第二步,创建Request请求
第三步,创建一个Call对象
第四步,将请求添加到调度中
//okHttp的基本使用 --- get方法
String url = "https://api.douban.com/v2/movie/top250?start=0&count=10";
//1,创建OKHttpClient对象
OkHttpClient mOkHttpClient = new OkHttpClient();
//2,创建一个Request
Request request = new Request.Builder().url(url).build();
//3,创建一个call对象
Call call = mOkHttpClient.newCall(request);
//4,将请求添加到调度中
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
}
@Override
public void onResponse(Response response) throws IOException {
if (response.isSuccessful()) {
final String message = response.body().string();
handler.post(new Runnable() {
@Override
public void run() {
tv_message.setText(message);
progressBar.setVisibility(View.GONE);
}
});
}
}
});
HTTPS和HTTP的区别:
HTTP(超文本传输协议,HyperText Transfer Protocol)
HTTPS(全称:Hypertext Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。 它是一个URI scheme(抽象标识符体系),句法类同http:体系。用于安全的HTTP数据传输。https:URL表明它使用了HTTP,但HTTPS存在不同于HTTP的默认端口及一个加密/身份验证层(在HTTP与TCP之间)。这个系统的最初研发由网景公司进行,提供了身份验证与加密通讯方法,现在它被广泛用于万维网上安全敏感的通讯,例如交易支付方面。
HTTPS其实就是建构在SSL/TLS之上的 HTTP协议,HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。
简单来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。
HTTPS和HTTP的区别主要如下:
1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
同步的使用:
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("http://www.baidu.com").build();
try {
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
System.out.println("成功");
}
} catch (IOException e) {
e.printStackTrace();
}
源码流程:
OkHttpClient:
//获取RealCall
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
RealCall:
//同步
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();//核心
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
//...................................................
//异步
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
client.dispatcher().enqueue(new AsyncCall(responseCallback));//线程池执行异步任务
}
//内部类:Runnable
final class AsyncCall extends NamedRunnable {
@Override protected void execute() {
boolean signalledCallback = false;
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) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
//................................................
//核心:拦截器链
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);
return chain.proceed(originalRequest);
}
这几个Interceptor都是在okhttp3后才被引入,它们非常重要,负责了重连、组装请求头部、读/写缓存、建立socket连接、向服务器发送请求/接收响应的全部过程。
interpolator
英[ɪn'tɜ:pəʊleɪtə] 美[ɪn'tɜ:poʊleɪtə]
n. 撺改者,校对机,分类机;
interceptor
英[ˌɪntəˈseptə(r)] 美[ˌɪntərˈseptə(r)]
n. 拦截机;
在okhttp3之前,这些行为都封装在HttpEngine类中。okhttp3之后,HttpEngine已经被删去,取而代之的是这5个Interceptor,可以说一次网络请求中的细节被解耦放在不同的Interceptor中,不同Interceptor只负责自己的那一环节工作(对Request或者Response进行获取/处理),使得拦截器模式完全贯穿整个网络请求。
Dispatcher:
public final class Dispatcher {
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
private @Nullable Runnable idleCallback;
private @Nullable ExecutorService executorService;
/** 缓存的异步call */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** 运行的异步call */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** 运行的同步call */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
//同步
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
//异步
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
}
Application interceptors与Network Interceptors
推荐阅读:OkHttp用法
用户可以添加自定义的Interceptor,okhttp把拦截器分为应用拦截器和网络拦截器。
final List<Interceptor> interceptors;
final List<Interceptor> networkInterceptors;
public Builder addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
return this;
}
public Builder addNetworkInterceptor(Interceptor interceptor) {
networkInterceptors.add(interceptor);
return this;
}
(1)调用OkHttpClient.Builder的addInterceptor()可以添加应用拦截器,只会被调用一次,可以处理网络请求回来的最终Response
(2)调用addNetworkInterceptor()可以添加network拦截器,处理所有的网络响应(一次请求如果发生了redirect ,那么这个拦截器的逻辑可能会被调用两次)
应用拦截器
不需要担心中间过程的响应,如重定向和重试.
总是只调用一次,即使HTTP响应是从缓存中获取.
观察应用程序的初衷. 不关心OkHttp注入的头信息如: If-None-Match.
允许短路而不调用 Chain.proceed(),即中止调用.
允许重试,使 Chain.proceed()调用多次.
网络拦截器
能够操作中间过程的响应,如重定向和重试.
当网络短路而返回缓存响应时不被调用.
只观察在网络上传输的数据.
携带请求来访问连接.
应用拦截器
...addInterceptor(new HeaderInterceptor())...
public class HeaderInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
String token = MMKV.defaultMMKV().decodeString(LocalKeys.TOKEN);
Request.Builder builder = chain.request().newBuilder();
if (!TextUtils.isEmpty(token)) {
builder.addHeader("HTTP_TOKEN", token); //添加请求头
}
builder.addHeader("ROLE", "1")
.addHeader("HTTP_APPCLIENT", "android");
return chain.proceed(builder.build());
}
}
网络拦截器一:
.......addNetworkInterceptor(new CacheInterceptor())//缓存机制.......
public class CacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//没网强制从缓存读取(必须得写,不然断网状态下,退出应用,或者等待一分钟后,就获取不到缓存)
if (!NetWorkUtils.isNetworkAvailable(MyApplication.getInstance())) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
}
Response response = chain.proceed(request);
Response responseLatest;
if (NetWorkUtils.isNetworkAvailable(MyApplication.getInstance())) {
// int maxAge = 0;// 有网络时 设置缓存超时时间0个小时
int maxAge = 60; //有网失效一分钟
responseLatest = response.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control", "public, max-age=" + maxAge)
.build();
} else {
// int maxStale = 60 * 60 * 24 * 28;// 无网络时,设置超时为4周
int maxStale = 60 * 60 * 6; // 没网失效6小时
responseLatest = response.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.build();
}
return responseLatest;
}
}
网络拦截器二:
//DEBUG模式下 添加日志拦截器
// System.out.println("BuildConfig.DEBUG===========" + BuildConfig.DEBUG);
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
// interceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
httpClientBuilder.addNetworkInterceptor(interceptor);
// HttpLoggingInterceptor logInterceptor = new HttpLoggingInterceptor(new HttpLogger());
// logInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
// httpClientBuilder.addNetworkInterceptor(logInterceptor);
}