Cronet同步模型异步请求分析与完善

1. 背景

在通过数据分析的过程中,发现我们在统计上无法统计到404的场景,透过数据去查问题,由于我们App使用OkHttp、Cronet的混合,通过动态调度来进行切换,这里主要是记录使用cronet遇到的问题。

2. Cronet使用模型

Android Cronet是异步网络库(Java封装层),请求是异步,结果通过回调的方式;但是官方基于异步模型封装了一个同步请求的,接下来我们来介绍这2种方式。

2.1 异步方式请求Cronet

先看下大致的异步请求代码:

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
43
44
45
46
47
48
49
50
51
52

private CronetEngine getCronetEngine(Context context) {
// Enable caching of HTTP data and
// other information like QUIC server information, HTTP/2 protocol and QUIC protocol.
return new CronetEngine.Builder(context)
.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_IN_MEMORY, 100 * 1024)
.enableHttp2(true)
.enableQuic(true)
.build();
}


public void request() {
// Create an executor to execute the request
Executor executor = Executors.newSingleThreadExecutor();
UrlRequest.Callback callback = new SimpleUrlRequestCallback();
// 实际上CronetEngine不这样用,需要全局的
UrlRequest.Builder builder = getCronetEngine().newUrlRequestBuilder(
url, callback, executor);
// Start the request
builder.setRequestFinishedListener(this)
.build().start();

}

class SimpleUrlRequestCallback extends UrlRequest.Callback {

@Override
public void onRedirectReceived(
UrlRequest request, UrlResponseInfo info, String newLocationUrl) {
request.followRedirect();
}

@Override
public void onResponseStarted(UrlRequest request, UrlResponseInfo info) {
// 响应开始,比如一个请求需要判断是否是http层的成功(httpcode == 200)
}

@Override
public void onReadCompleted(
UrlRequest request, UrlResponseInfo info, ByteBuffer byteBuffer) {
}

@Override
public void onSucceeded(UrlRequest request, UrlResponseInfo info) {
}

@Override
public void onFailed(UrlRequest var1, UrlResponseInfo var2, CronetException var3) {
}
}

以上代码就是cronet请求的样子,发起请求时,需要提供一个callback,这个callback,但是这个callback的触发不完全是被动,需要进行某些操作才能够触发。异步请求个人在实践过程中发现一个问题,这个问题就是在请求响应回来时,我们会对code进行判断,判断是否是http层成功(code==200),如果不是http层成功,比如4xx、5xx,那么我们就会回调一个error给上层的业务。

如下时序图:

有如下的说明:

  • 时序图中,onResponseStarted方法是已经响应处理的地方,如果这个时候判断code非成功(成功是200=<code<300),直接return,cronet底层的metric监控将不会回调
  • 只有进行数据的读取操作,才会调用onReadCompleted、onSucceeded
  • 连接、ssl、数据读取会产生异常
  • 触发Metric监控的回调cronet内部产生了异常、进行了读取数据(成功、失败)、上层调用cancel
  • Cronet面向Android的Java层api,没有提供close接口,只有cancel

网络底层统计逻辑,面对各种各样的请求,我们不能在面向业务回调的地方进行埋点,Cronet提供了一套完整的监控回调体系,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public abstract static class Listener {
private final Executor mExecutor;

public Listener(Executor executor) {
if (executor == null) {
throw new IllegalStateException("Executor must not be null");
} else {
this.mExecutor = executor;
}
}

public abstract void onRequestFinished(RequestFinishedInfo var1);

public Executor getExecutor() {
return this.mExecutor;
}
}

提供了一个抽象的类,来进行数据的收集,在上面的请求中的setRequestFinishedListener,我们可以针对每个请求加上监听回调。

所以在底层通过RequestFinishedListener来监听获取请求网络数据,需要解决异步回调处理非 200 响应code的问题,比如如下处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

@Override
public void onResponseStarted(UrlRequest urlRequest, UrlResponseInfo urlResponseInfo) {
if (!isSuccessful(urlResponseInfo.getHttpStatusCode())) {
handleFail(urlResponseInfo,
new CronetExceptionImpl("onResponseStarted code error " +
urlResponseInfo.getHttpStatusCode(),
new Exception("code is " + urlResponseInfo.getHttpStatusCode())
));
return;
}
urlRequest.read(ByteBuffer.allocateDirect(CAPACITY));
}

判断code是否是成功的http响应,不是的话就直接return,这样就会导致底层的监听没有回调,为啥没有回调?

  • 能回调onResponseStarted方法,说明网络层tcp层是成功的,由于没有进行数据的读取,所以不会驱动onReadCompleted、onSucceeded的回调,所以也就没有回调Metric监听器

目前底层Metric监控的状态回调中有三种状态:

1
2
3
4
5

public static final int SUCCEEDED = 0;
public static final int FAILED = 1;
public static final int CANCELED = 2;

如果在这里判断http code来决定是否读取数据,会导致监控数据缺失,所以这处应该要调用release

或者 close 接口,但cronet 在java层并没有提供。

2.2 添加close接口

由于连接、ssl这块如果出现问题,会回调onFail,也会到底层Metric接口中,所有close只需要处理以下2种场景

  • http层非成功的响应码(4xx、5xx这些)
  • 同步请求中,读超时(http code 200)

3 同步请求

同步请求是使用UrlHttpConnection的接口来封装的,也就是同步调用的异步网络请求方式。

比如下载场景的,同步接口调用的时序图: