一. 基本使用
okhttp的请求和响应大多数采用建造者模式设计.
1. GET同步请求
public void syncGetRequest() {
String url = "http://api.k780.com/?app=weather.future&weaid=1&&appkey=10003" +
"&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json";
final OkHttpClient okHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
.get()
.url(url)
.build();
new Thread(new Runnable() {
@Override
public void run() {
try {
Response response = okHttpClient.newCall(request).execute();
Log.d(TAG, "syncGetRequest: " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}).star
复制代码
2. GET异步请求
public void asyncGetRequest() {
String url = "http://api.k780.com/?app=weather.future&weaid=1&&appkey=10003" +
"&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json";
final OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.get()
.url(url)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, "Callback-Thread: " + Thread.currentThread());
String result = response.body().string();
Log.d(TAG, "asyncGetRequest: " + result);
//通过handler或者runOnUiThread方式切换线程.
runOnUiThread(new Runnable() {
@Override
public void run() {
idtv.setText("jasonhww");
}
});
/*
*如果获取的是文件/图片
*/
//方式一:通过获取流
//InputStream inputStream = response.body().byteStream();
//方式二:通过获取字节数组
//byte[] bytes = response.body().bytes();
}
});
}
复制代码
注意事项:
- Callback的回调方法是在子线程执行的,如需更新UI,可通过runjOnUiThread或者handler切换到主线程.
3. POST提交表单
可以使用FormBody.Builder构建一个表单请求体
public void asyncPostForm(){
String url = "http://api.k780.com/";
final OkHttpClient okHttpClient = new OkHttpClient();
//构建表单请求体
RequestBody requestBody = new FormBody.Builder()
.add("app","weather.future")
.add("weaid","1")
.add("appkey","10003")
.add("sign","b59bc3ef6191eb9f747dd4e83c99f2a4")
.add("format","json")
.build();
//创建POST请求
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, "asyncPostForm: "+response.body().string());
}
});
}
复制代码
4. POST提交JSON字符串
指定请求体的媒体类型为"application/json"
public void asyncJson() {
String url = "http://api.k780.com/?app=weather.future&weaid=1&&appkey=10003" +
"&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json";
String json = "{code:1,result:null}";
OkHttpClient okHttpClient = new OkHttpClient();
//指定媒体类型
MediaType mediaType = MediaType.parse("application/json,charset=utf-8");
RequestBody requestBody = RequestBody.create(mediaType, json);
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, "asyncJson: " + response.body().string());
}
});
}
复制代码
5. 缓存
缓存使用相对比较简单,只需指定一下缓存目录及大小即可.
//伪代码
Cache cache = new Cache(cacheDirectory,cacheSize);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
.build();
复制代码
6. 拦截器
通过拦截器我们可以很方便的去修改请求和响应的相关信息,如修改请求头,请求体等.
- 应用拦截器
拦截应用层与okhttp之间的请求和响应 - 网络拦截器
拦截okhttp与网络层之间的请求和响应,此时网络连接已经建立
public class LoggingInterceptor implements Interceptor {
private static final String TAG = "LoggingInterceptor";
@Override
public Response intercept(Chain chain) throws IOException {
//获取请求
Request request = chain.request();
//打印url,连接状态,请求头
Log.d(TAG, "LoggingInterceptor: url = " + request.url() +
"\nconnectionStatus = " + chain.connection() +
"\nrequestHeaderInfo = " + request.headers());
//执行请求
Response response = chain.proceed(request);
//打印url,响应头
Log.d(TAG, "LoggingInterceptor: url = " + response.request() +
"\nresponseHeaderInfo = " + response.headers());
return response;
}
}
public class NetInterceptor implements Interceptor {
private static final String TAG = "LoggingInterceptor";
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
//演示设置响应头
CacheControl.Builder builder = new CacheControl.Builder()
.maxAge(10,TimeUnit.MINUTES);
return response.newBuilder()
.header("Cache-Control",builder.build().toString)
.build();
}
}
复制代码
public void asyncCacheInterceptor() {
String url = "http://api.k780.com/?app=weather.future&weaid=1&&appkey=10003" +
"&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json";
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())//应用拦截
.addNetworkInterceptor(new NetInterceptor())//网络拦截器
.build();
//通过拦截器修改请求头
Request request = new Request.Builder()
.get()
.header("user-Agent","Interceptor example")
.url(url)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, "asyncCacheInterceptor: " + response.body().string());
}
});
}
复制代码
二. 基本原理
采用OkHttp源码版本为3.8.1
1. OkHttpClient的创建
OkHttpClient作为okhttp的入口,一般将OkHttpClient采用单例模式.
初始化两种方式
- 采用直接"new"的方式
- 采用建造者模式
对应源码
//内部也是通过建造者进行初始化
OkHttpClient okhttpClient = new OkHttpClient();
OkHttpClient okhttpClient = new OkHttpClient.Builder()
.build();
复制代码
通过初始化okhttpclient,完成对许多成员变量的初始化工作.
对应源码
OkHttpClient(Builder builder) {
//分发器对象,记录请求执行情况,内部维护一个线程池来执行异步请求
this.dispatcher = builder.dispatcher;
this.proxy = builder.proxy;
this.protocols = builder.protocols;
this.connectionSpecs = builder.connectionSpecs;
//应用拦截器集合
this.interceptors = Util.immutableList(builder.interceptors);
//网络拦截器集合
this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
this.eventListenerFactory = builder.eventListenerFactory;
this.proxySelector = builder.proxySelector;
//Cookie瓶
this.cookieJar = builder.cookieJar;
//磁盘缓存
this.cache = builder.cache;
this.internalCache = builder.internalCache;
this.socketFactory = builder.socketFactory;
//HTTPS相关成员变量初始化
......
}
复制代码
2. Call的创建
当okhttpclient初始化后,再通过okHttpClient.newCall(request),创建一个Call对象,最终实际返回了一个RealCall对象.
对应源码
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
复制代码
2.1 同步请求时
调用RealCall的execute方法
对应源码
/**
* 1. 首先看RealCall的execute方法.
*/
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
//实际调用了分发器的executed方法,传入RealCall对象.
client.dispatcher().executed(this);
//最终通过拦截器链获取响应.
//最终通过拦截器链获取响应.
//最终通过拦截器链获取响应.
//重要的话说三遍.
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
/**
* 2. 接着看dispatcher的executed方法
*/
synchronized void executed(RealCall call) {
//标识已经执行了请求
runningSyncCalls.add(call);
}
复制代码
2.1 异步请求时
同样调用RealCall的enqueue方法
对应源码
/**
* 1.首先看RealCall的enqueue方法.
*/
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
//实际调用了分发器的enqueue方法,传入AsyncCall对象
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
/**
* 2.接着看dispatcher对象的enqueue方法.
*/
synchronized void enqueue(AsyncCall call) {
//检测请求是否超过最大请求数和一个host对应最大的请求数
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//标识已经执行了请求(这里和同步方法一样操作,可以对比上一点方法)
runningAsyncCalls.add(call);
//使用线程池执行请求
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
/**
* 3.最后我们看AsyncCall的实现.
*/
//AsyncCall是RealCall的内部类,继承自NamedRunnable
//NamedRunnable的实现比较简单就是修改线程名.提供一个execute方法在run用调用,这里就不再晒出具体源码了.
//AsyncCall的实现主是获取响应,进行回调.
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
//....省略部分代码
@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);
}
复制代码
3.拦截器链的构建与启动
整个okhttp的核心设计之处
- 遵循单一原则,每个拦截器只做一项处理.
- 拦截器之间通过拦截器链(RealInterceptorChain)进行连接,构建出处理请求和响应的流水链.如图
请求通过层层拦截器处理,最后发送出去,反之响应.
对应源码
//通过拦截器链获取响应
Response getResponseWithInterceptorChain() throws IOException {
/**
* 初始化拦截器集合
*/
List<Interceptor> interceptors = new ArrayList<>();
//添加7种类型拦截器
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);
}
/**
* 拦截器链的启动
*/
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
//......省略部分代码
// 创建一个新的拦截器链,拦截器位置指向将index+1.即下一个.
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
//通过下标index获取当前要执行的拦截器
Interceptor interceptor = interceptors.get(index);
//执行拦截器intercept方法
//如果当前拦截器执行完后,则调用传入的next(新链对象)的proceed方法,执行下一个拦截器.依次类推.
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
return response;
}
复制代码
这7种拦截器执行顺序为:
- 应用拦截器
- retryAndFollowUpInterceptor
- BridgeInterceptor
- CacheInterceptor
- ConnectInterceptor
- 网络拦截器
- CallServerInterceptor
其中带下划线的拦截器,就是我们之前自定义的拦截器.接下来就依次讲解7种拦截器的作用.
3.1 应用拦截器
不再讲述
3.2 retryAndFollowUpInterceptor
处理错误重试和重定向
对应源码
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response = null;
boolean releaseConnection = true;
try {
//执行下一个拦截器,获取响应
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.getLastConnectException(), false, request)) {
throw e.getLastConnectException();
}
releaseConnection = false;
//满足条件,则重试
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, requestSendStarted, request)) throw e;
releaseConnection = false;
//满足条件,则重试
continue;
} finally {
//出现异常释放资源,continue在finally语句块执行后才执行
// We're throwing an unchecked exception. Release any resources.
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
//检测是否满足定向的要求代码
......
//重新赋值为重定向的请求
request = followUp;
priorResponse = response;
}
}
复制代码
3.3 BridgeInterceptor
桥接应用层和网络层
A. 将请求进行深加工,使其成为真正请求
- 对一些请求头的设置
B. 并且也对网络响应做响应处理.
- 解压缩,去掉不必要的响应头
对应源码
@Override public Response intercept(Chain chain) throws IOException {
//获取请求
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
//对一些请求头的设置.
RequestBody body = userRequest.body();
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
long contentLength = body.contentLength();
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
//如果没有配置,默认配置gzip.获取响应时需要解压缩.
requestBuilder.header("Accept-Encoding", "gzip");
}
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
//执行下一个拦截器获取响应
Response networkResponse = chain.proceed(requestBuilder.build());
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
//用于解压缩
GzipSource responseBody = new GzipSource(networkResponse.body().source());
//返回应用层去掉不需要的响应头
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
复制代码
3.4 CacheInterceptor
承担缓存的查找和保存职责
A. 不需要网络请求,缓存可用,直接返回
B. 缓存不可用,执行下一个拦截器获取响应;
如果用户配置了需要缓存,将响应写入缓存,并返回.
对应源码
@Override
public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
//检查缓存策略
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
//networkRequest不为空,需要发送请求
Request networkRequest = strategy.networkRequest;
//cacheResponse不为空,则缓存可用
Response cacheResponse = strategy.cacheResponse;
//......省略部分代码
// If we don't need the network, we're done.
//不需要网络请求,缓存可用,直接返回
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
//缓存不可用,执行下一个拦截器获取响应
networkResponse = chain.proceed(networkRequest);
} finally {
//......省略部分代码
}
//......省略部分代码
//包装网络响应
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
//用户配置了缓存
if (cache != null) {
//判断条件
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
//网络响应写入缓存,并返回
return cacheWritingResponse(cacheRequest, response);
}
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
复制代码
3.5 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");
//获取HTTP编码解码器
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
//获取一个链接
RealConnection connection = streamAllocation.connection();
//执行下一个拦截器
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
复制代码
3.6 网络拦截器
不再讲述
3.7 CallServerInterceptor
将请求发送出去
对应源码
@Override
public Response intercept(Chain chain) throws IOException {
//.......省略部分代码
//发送网络请求,httpCodec则是在上一个ConnectInterceptor拦截器获取到的
httpCodec.finishRequest();
if (responseBuilder == null) {
responseBuilder = httpCodec.readResponseHeaders(false);
}
//构建网络响应对象
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
//写入响应体
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
复制代码