它是一个基于HTTP+HTTP/2的java开发的客户端网络访问库,拥有丰富的功能以及高效的性能。由square公司开源,目前github上已有3w+的star,可见人们对它的喜爱。
我们先来演示一下其简单的用法,参考官网的例子: 构建Maven项目,引入依赖:
<dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>3.7.0</version> </dependency> 复制代码
执行一个GET请求,代码如下:
OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url("http://www.baidu.com") .build(); try (Response response = client.newCall(request).execute()) { String resStr = response.body().string(); System.out.println(resStr); } catch (IOException e) { e.printStackTrace(); } 复制代码
再来看一个POST请求的例子:
public static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); OkHttpClient client = new OkHttpClient(); String post(String url, String json) throws IOException { RequestBody body = RequestBody.create(JSON, json); Request request = new Request.Builder() .url(url) .post(body) .build(); try (Response response = client.newCall(request).execute()) { return response.body().string(); } 复制代码
从上面的代码示例可以看出,okhttp的api使用起来还是很简单的,创建一个OkHttpClient的客户端,创建一个Request请求,通过client来执行请求即可。
client
http客户端的工作说起来就是接受你的请求并且执行请求,返回相应的响应内容。
request
每个请求都包含url,header,请求方式(get,post...),请求体。okhttp提供了重写请求,跟随请求(重定向)以及重试等功能。
response
响应内容包含响应码,响应头和响应体,okhttp提供了自动重写响应的功能。
calls
将请求到获取响应的执行过程抽象成一个call,执行任务的方式有同步和异步的方式: Synchronous:阻塞当前线程,直到返回结果
Asynchronous:将请求加入队列,通过回调的方式获取响应,异步请求的返回是在一个单独的work线程中。
异步请求代码示例:
client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { e.printStackTrace(); } @Override public void onResponse(Call call, Response response) throws IOException { try (ResponseBody responseBody = response.body()) { if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); Headers responseHeaders = response.headers(); for (int i = 0, size = responseHeaders.size(); i < size; i++) { System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i)); } System.out.println(responseBody.string()); } } }); } 复制代码
更多代码示例参考github: github.com/square/okht…
拦截器是okhttp提供的一个强大功能,它允许你在请求和响应的某个环节上进行相应的操作。按照调用位置的不同,拦截器可以分为两大类APPLICATION和NETWORK,两种类型的分类标准是按照拦截器在核心拦截器(后面章节会提到,它是整个库的核心,也是我认为设计非常棒的地方)之前或之后来分的,如下图所示
拦截器使用实例,下面创建了一个日志拦截器,用于在请求发送前和接收响应后打印日志
class LoggingInterceptor implements Interceptor { @Override public Response intercept(Interceptor.Chain chain) throws IOException { Request request = chain.request(); long t1 = System.nanoTime(); logger.info(String.format("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers())); Response response = chain.proceed(request); long t2 = System.nanoTime(); logger.info(String.format("Received response for %s in %.1fms%n%s", response.request().url(), (t2 - t1) / 1e6d, response.headers())); return response; } } 复制代码
上面的chain.request()和chain.proceed(request)是关键。chain.request()从链中获取请求,chain.proceed()是执行具体的请求,并将请求传到到下一个链中。将创建的拦截器加入client中
OkHttpClient client = new OkHttpClient().newBuilder() .addInterceptor(new LoggingInterceptor()) .build(); 复制代码
上面的代码默认添加的是一个application类型的拦截器,如果要添加一个netword类型的拦截器,如下面代码所示:
OkHttpClient client = new OkHttpClient.Builder() .addNetworkInterceptor(new LoggingInterceptor()) .build(); 复制代码
重写request
可以添加,删除或替换相关的请求头和请求体,下面的代码展示了一段对请求体进行压缩的示例:
final class GzipRequestInterceptor implements Interceptor { @Override public Response intercept(Interceptor.Chain chain) throws IOException { Request originalRequest = chain.request(); if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) { return chain.proceed(originalRequest); } Request compressedRequest = originalRequest.newBuilder() .header("Content-Encoding", "gzip") .method(originalRequest.method(), gzip(originalRequest.body())) .build(); return chain.proceed(compressedRequest); } private RequestBody gzip(final RequestBody body) { return new RequestBody() { @Override public MediaType contentType() { return body.contentType(); } @Override public long contentLength() { return -1; // We do not know the compressed length in advance! } @Override public void writeTo(BufferedSink sink) throws IOException { BufferedSink gzipSink = Okio.buffer(new GzipSink(sink)); body.writeTo(gzipSink); gzipSink.close(); } }; } 复制代码
重写response
同样可以对接收到的响应头和响应体进行修改,但是通常这么做是很危险的,不建议这么做。
private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() { @Override public Response intercept(Interceptor.Chain chain) throws IOException { Response originalResponse = chain.proceed(chain.request()); return originalResponse.newBuilder() .header("Cache-Control", "max-age=60") .build(); } }; 复制代码
okhttp提供了许多事件点,供开发程序可以方便的进行扩展和监听。例如你可以利用这些事件点来对调用请求的频率和性能进行监控。事件点如下图所示:
从图中可以看出请求主要分为CONNECTING和CONNECTED两个阶段。如果之前已有相同的链接请求,将会从连接池中复用已有链接,从而提高性能。如果没有连接,则将通过dns解析拿到请求的IP地址,再请求建立连接。你可以利用框架提供的EventListener回调接口来处理你感兴趣的事件,下面的代码示例统计了请求期间,各个阶段所花费的时间:
class PrintingEventListener extends EventListener { private long callStartNanos; private void printEvent(String name) { long nowNanos = System.nanoTime(); if (name.equals("callStart")) { callStartNanos = nowNanos; } long elapsedNanos = nowNanos - callStartNanos; System.out.printf("%.3f %s%n", elapsedNanos / 1000000000d, name); } @Override public void callStart(Call call) { printEvent("callStart"); } @Override public void callEnd(Call call) { printEvent("callEnd"); } @Override public void dnsStart(Call call, String domainName) { printEvent("dnsStart"); } @Override public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) { printEvent("dnsEnd"); } ... } 复制代码
在client中加入listener
OkHttpClient client = new OkHttpClient().newBuilder() .eventListener(new PrintingEventListener()) .build(); 复制代码
由于EvenetListener是与每一个call绑定的,如果你要区别不同call,可以通过工厂的方式来创建一个EventListener,为每一个listener创建一个唯一ID,这样你就可以在日志中通过该ID来区分不同的调用了。如下所示:
class PrintingEventListener extends EventListener { public static final Factory FACTORY = new Factory() { final AtomicLong nextCallId = new AtomicLong(1L); @Override public EventListener create(Call call) { long callId = nextCallId.getAndIncrement(); System.out.printf("%04d %s%n", callId, call.request().url()); return new PrintingEventListener(callId, System.nanoTime()); } }; final long callId; final long callStartNanos; public PrintingEventListener(long callId, long callStartNanos) { this.callId = callId; this.callStartNanos = callStartNanos; } private void printEvent(String name) { long elapsedNanos = System.nanoTime() - callStartNanos; System.out.printf("%04d %.3f %s%n", callId, elapsedNanos / 1000000000d, name); } @Override public void callStart(Call call) { printEvent("callStart"); } @Override public void callEnd(Call call) { printEvent("callEnd"); } ... } 复制代码
最后在client中设置eventFactory
OkHttpClient client = new OkHttpClient().newBuilder() .eventListenerFactory(PrintingEventListener.FACTORY) .build(); 复制代码