OkHttp学习

what

非常好用的请求库,还是附上官方文档

how

正常步骤就是

  1. 构建键Client
    1. 添加header
    2. 拦截器
    3. 缓存
    4. dns
    5. 代理
    6. 超时
    7. 监听事件
  2. 构建请求Request
    1. url
    2. header
    3. 请求方式
    4. 请求体
  3. 发送请求Call
  4. 获得响应Response
  5. 处理响应

简单同步请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://www.wanandroid.com/article/list/0/json")
.build();
Call call = client.newCall(request);
try(Response response = call.execute()) {
System.out.println(response.code());
System.out.println(response.message());
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}

//output
200
OK
(相应体数据)

简单异步请求

就是简单的将execute换成了enqueue传入回调,这是异步的哦,会切换线程。但是再Test测试方法中,需要让外面的线程等待下,不然是看不到的结果的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
  OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://www.wanandroid.com/article/list/0/json")
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
System.out.println("onFailure");
}

@Override
public void onResponse(Call call, Response response) throws IOException {
System.out.println({"in "+Thread.currentThread().getId());
System.out.println(response.code());
System.out.println(response.message());
System.out.println(response.body().string());
}
});

System.out.println( "out "+ Thread.currentThread().getId());

try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//output
out 1
in 13
200
OK
(相应体数据)

发送Post请求

如果不对Request进行设置,默认就是get请求。

1
2
3
4
5
RequestBody requestBody = new Request.Builder().addHeader().build();
Request request = new Request.Builder()
.url("https://www.wanandroid.com/login")
.post(requestBody)
.build();

所以主要就是看RequestBody的构建了

通过静态方法create进行构建

1
2
MediaType contentType = MediaType.parse("text/x-markdown; charset=utf-8");
RequestBody requestBody = RequestBody.create(contentType, "hello");

大概有这么几种:

1
2
3
4
5
create(@Nullable MediaType contentType, String content);//字符串
create(final @Nullable MediaType contentType, final ByteString content);
create(final @Nullable MediaType contentType, final byte[] content);//字节数组
create(final @Nullable MediaType contentType, final byte[] content,final int offset, final int byteCount);
create(final @Nullable MediaType contentType, final File file);//文件

所以也可以直接构建提交文件RequestBody的。

实现RequestBody

1
2
3
4
5
6
7
8
9
10
11
RequestBody requestBody1 = new RequestBody() {
@Override
public MediaType contentType() {
return null;
}

@Override
public void writeTo(BufferedSink sink) throws IOException {

}
};

发送普通表单

RequestBody有一个实现类FormBody

1
2
3
4
FormBody formBody = new FormBody.Builder()
.add("name", "zhangsan")
.add("password", "123")
.build();

发送Multi表单

RequestBody另一个一个实现类MultipartBody,添加许多Part

1
2
3
4
MultipartBody body = new MultipartBody.Builder()
.addFormDataPart("name", "zhangsan")
.addPart(MultipartBody.Part.create(null))
.build();

拦截器

OkHttp的核心可以就在于拦截器,内部通过拦截器链式调用。监听,重写,重试call

看下官网给的例子,用于测试请求消耗的时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}
}

拦截分为两种:

  • 全局拦截器 通过OkHttpClient.Builder#addInterceptor(Interceptor)加入

  • 网络拦截器 通过 OkHttpClient.Builder#addNetworkInterceptor(Interceptor)加入,真正发起网络请求调用

Application Interceptors

全局拦截器:

  • 不用关心重连,重定向
  • 总是调用一次,即使http响应是来自缓存。
  • 可以观察原始请求意图,
  • 允许短路,不调用Chain.proceed()
  • 允许多次调用Chain.proceed()

一般我们可比如:从本地取出并注入cookie,以及取出cookie持久化到本地

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 读取并设置cookie
*/
public class ReadCookiesInterceptor implements Interceptor {
private Context mContext;

public ReadCookiesInterceptor(Context context) {
mContext = context;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request.Builder builder = chain.request().newBuilder();
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(mContext);
HashSet<String> cookies = (HashSet<String>) pref.getStringSet("cookies", new HashSet<>());
for (String cookie : cookies) {
builder.addHeader("Cookie", cookie);
}
return chain.proceed(builder.build());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 保存cookie
*/
public class SaveCookiesInterceptor implements Interceptor {
private Context mContext;

public SaveCookiesInterceptor(Context context) {
mContext = context;
}

@Override
public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
if (!originalResponse.headers("Set-Cookie").isEmpty()) {
HashSet<String> cookies = new HashSet<>();
cookies.addAll(originalResponse.headers("Set-Cookie"));
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(mContext).edit();
editor.putStringSet("cookies", cookies).apply();
}
return originalResponse;
}
}

Network Interceptors

  • 能够操纵中间响应,重定向,重试
  • 缓存中响应不会调用
  • 可观察网络传输数据
  • 可以获得具体Connection进行操作,比如IP,TLS等

代码分析

首先看下各个类的概念

  • OkHttpClient 客户端:维持了线程池,避免重复创键消耗资源,最好使用复用一个
  • Dispatcher 线程池抽象聚合:维护一个核心线程为了0,上限Int最大数,存活时间60s的线程池
  • Response 请求的封装:包含了请求行,请求头,请求体
  • Response 响应的封装:包含了状态码,响应头,响应体
  • Call 一次请求的具体对象,分为同步和异步
  • Interceptor 拦截器,拦截并处理,交下个拦截器
  • Chain 请求链,和拦截器一起构成责任链模式

构建Client

Builder模式,构建client,有默认配置

1
2
3
public OkHttpClient() {
this(new Builder());
}

构建请求

同样Builder模式

1
2
3
4
5
6
7
Request(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers.build();
this.body = builder.body;
this.tags = Util.immutableMap(builder.tags);
}

newCall

实例出RealCall

1
2
3
4
5
6
7
8
9
10
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}

static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.transmitter = new Transmitter(client, call);
return call;
}

同步请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");//执行过不能执行
executed = true;
}
transmitter.timeoutEnter();//Okio超时,我还没搞懂233
transmitter.callStart();//触发监听
try {
client.dispatcher().executed(this);//RealCall入dispatcher的runningCall队列
return getResponseWithInterceptorChain();//进入
} finally {
client.dispatcher().finished(this);//继续调用其它异步call
}
}

call入队列

1
synchronized void executed(RealCall call) {  runningSyncCalls.add(call);}

进行请求返回响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());//全局拦截器
interceptors.add(new RetryAndFollowUpInterceptor(client));//请求重连拦截器
interceptors.add(new BridgeInterceptor(client.cookieJar()));//构建网络请求
interceptors.add(new CacheInterceptor(client.internalCache()));//cache
interceptors.add(new ConnectInterceptor(client));//封装httpCodec
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));//发送具体网络请求

Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
originalRequest, this, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());

boolean calledNoMoreExchanges = false;
try {
Response response = chain.proceed(originalRequest);//进入拦截器链
if (transmitter.isCanceled()) {
closeQuietly(response);
throw new IOException("Canceled");
}
return response;
} catch (IOException e) {
calledNoMoreExchanges = true;
throw transmitter.noMoreExchanges(e);
} finally {
if (!calledNoMoreExchanges) {
transmitter.noMoreExchanges(null);
}
}
}

进入拦截器链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
throws IOException {
if (index >= interceptors.size()) throw new AssertionError();

calls++;

// If we already have a stream, confirm that the incoming request will use it.
if (this.exchange != null && !this.exchange.connection().supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}

// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.exchange != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}

// Call the next interceptor in the chain.重新构建链
RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);//进入下一个拦截器

// Confirm that the next interceptor made its required call to chain.proceed().
if (exchange != 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");
}

if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}

return response;
}

经历重重拦截器,通过Okio库进行对Socket进行流的读写,就完成一次请求。

异步请求

和同步请求不一样,具体是AsyncCall,将请求提交给线程池,触发回调。

1
2
3
4
5
6
7
8
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.callStart();//触发事件监听
client.dispatcher().enqueue(new AsyncCall(responseCallback));//入队列
}
1
2
3
4
5
6
7
8
9
10
11
12
void enqueue(AsyncCall call) {
synchronized (this) {
readyAsyncCalls.add(call);//入队列
// Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
// the same host.
if (!call.get().forWebSocket) {
AsyncCall existingCall = findExistingCallWithHost(call.host());
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
}
}
promoteAndExecute();//执行
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
List<AsyncCall> executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {//遍历准备队列
AsyncCall asyncCall = i.next();
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.运行队列满
//请求Host的数量超出
if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.
i.remove();//出准备队列
asyncCall.callsPerHost().incrementAndGet();
executableCalls.add(asyncCall);//入执行队列
runningAsyncCalls.add(asyncCall);//入运行队列
}
isRunning = runningCallsCount() > 0;
}
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
asyncCall.executeOn(executorService());
}
return isRunning;
}

AsnycCall是个Runaable实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void executeOn(ExecutorService executorService) {
assert (!Thread.holdsLock(client.dispatcher()));
boolean success = false;
try {
executorService.execute(this);//提交到线程池
success = true;
} catch (RejectedExecutionException e) {
InterruptedIOException ioException = new InterruptedIOException("executor rejected");
ioException.initCause(e);
transmitter.noMoreExchanges(ioException);
responseCallback.onFailure(RealCall.this, ioException);//触发失败回调
} finally {
if (!success) {
client.dispatcher().finished(this); // This call is no longer running!
}
}
}

线程具体执行的地方

走到这里就和同步请求一致了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override protected void execute() {
boolean signalledCallback = false;
transmitter.timeoutEnter();
try {
Response response = getResponseWithInterceptorChain();//请求响应
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读取一次内容会关闭,所以只能读取一次
  • Response读取byteStream时,需要手动close它

参考: