NetworkingModule
为JS端提供了发送网络请求的接口。在Native层,React Native 依赖了okttp网络库。主要API 包括以下三个:
- sendRequest:发送网络请求
- abortRequest:中断网络请求
- clearCookies :清除cookie
其中最重要的的是sendRequest
方法,下面我们进行详细分析。
NetworkingModule
创建时初始化了默认的OkHttpClient,并在初始化时设置了cookie管理和存储策略:
public static OkHttpClient createClient() {
// No timeouts by default
OkHttpClient.Builder client = new OkHttpClient.Builder()
.connectTimeout(0, TimeUnit.MILLISECONDS)
.readTimeout(0, TimeUnit.MILLISECONDS)
.writeTimeout(0, TimeUnit.MILLISECONDS)
.cookieJar(new ReactCookieJarContainer());
return enableTls12OnPreLollipop(client).build();
}
public void initialize() {
// cookie管理和存储策略
mCookieJarContainer.setCookieJar(new JavaNetCookieJar(mCookieHandler));
}
默认情况下使用webview的CookieManager
来管理cookie。首先看一下暴露给JS端的网络请求方法sendRequest
:
public void sendRequest(
final ExecutorToken executorToken, //用于保证JS调用Native API时返回结果给对应的JS VM
String method,
String url,
final int requestId,
ReadableArray headers,
ReadableMap data,
final String responseType,
final boolean useIncrementalUpdates,
int timeout) {
Request.Builder requestBuilder = new Request.Builder().url(url);
if (requestId != 0) {
requestBuilder.tag(requestId);
}
final RCTDeviceEventEmitter eventEmitter = getEventEmitter(executorToken);
OkHttpClient.Builder clientBuilder = mClient.newBuilder();
// 如果JS端监听了下载进度,则client加入ProgressResponseBody对返回结果拦截
if (useIncrementalUpdates) {
clientBuilder.addNetworkInterceptor(new Interceptor() {
@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
ProgressResponseBody responseBody = new ProgressResponseBody(
originalResponse.body(),
new ProgressListener() {
long last = System.nanoTime();
@Override
public void onProgress(long bytesWritten, long contentLength, boolean done) {
long now = System.nanoTime();
if (!done && !shouldDispatch(now, last)) {
return;
}
if (responseType.equals("text")) {
// For 'text' responses we continuously send response data with progress info to
// JS below, so no need to do anything here.
return;
}
ResponseUtil.onDataReceivedProgress(
eventEmitter,
requestId,
bytesWritten,
contentLength);
last = now;
}
});
return originalResponse.newBuilder().body(responseBody).build();
}
});
}
// 以调用时的timeout为准
if (timeout != mClient.connectTimeoutMillis()) {
clientBuilder.readTimeout(timeout, TimeUnit.MILLISECONDS);
}
OkHttpClient client = clientBuilder.build();
// 构建Header
Headers requestHeaders = extractHeaders(headers, data);
if (requestHeaders == null) {
ResponseUtil.onRequestError(eventEmitter, requestId, "Unrecognized headers format", null);
return;
}
String contentType = requestHeaders.get(CONTENT_TYPE_HEADER_NAME);
String contentEncoding = requestHeaders.get(CONTENT_ENCODING_HEADER_NAME);
requestBuilder.headers(requestHeaders);
// 构建body
if (data == null) {
requestBuilder.method(method, RequestBodyUtil.getEmptyBody(method));
} else if (data.hasKey(REQUEST_BODY_KEY_STRING)) {
if (contentType == null) {
ResponseUtil.onRequestError(
eventEmitter,
requestId,
"Payload is set but no content-type header specified",
null);
return;
}
String body = data.getString(REQUEST_BODY_KEY_STRING);
MediaType contentMediaType = MediaType.parse(contentType);
if (RequestBodyUtil.isGzipEncoding(contentEncoding)) {
RequestBody requestBody = RequestBodyUtil.createGzip(contentMediaType, body);
if (requestBody == null) {
ResponseUtil.onRequestError(eventEmitter, requestId, "Failed to gzip request body", null);
return;
}
requestBuilder.method(method, requestBody);
} else {
requestBuilder.method(method, RequestBody.create(contentMediaType, body));
}
} else if (data.hasKey(REQUEST_BODY_KEY_BASE64)) {
if (contentType == null) {
ResponseUtil.onRequestError(
eventEmitter,
requestId,
"Payload is set but no content-type header specified",
null);
return;
}
String base64String = data.getString(REQUEST_BODY_KEY_BASE64);
MediaType contentMediaType = MediaType.parse(contentType);
requestBuilder.method(
method,
RequestBody.create(contentMediaType, ByteString.decodeBase64(base64String)));
} else if (data.hasKey(REQUEST_BODY_KEY_URI)) {
if (contentType == null) {
ResponseUtil.onRequestError(
eventEmitter,
requestId,
"Payload is set but no content-type header specified",
null);
return;
}
String uri = data.getString(REQUEST_BODY_KEY_URI);
InputStream fileInputStream =
RequestBodyUtil.getFileInputStream(getReactApplicationContext(), uri);
if (fileInputStream == null) {
ResponseUtil.onRequestError(
eventEmitter,
requestId,
"Could not retrieve file for uri " + uri,
null);
return;
}
requestBuilder.method(
method,
RequestBodyUtil.create(MediaType.parse(contentType), fileInputStream));
} else if (data.hasKey(REQUEST_BODY_KEY_FORMDATA)) {
if (contentType == null) {
contentType = "multipart/form-data";
}
ReadableArray parts = data.getArray(REQUEST_BODY_KEY_FORMDATA);
MultipartBody.Builder multipartBuilder =
constructMultipartBody(executorToken, parts, contentType, requestId);
if (multipartBuilder == null) {
return;
}
requestBuilder.method(
method,
RequestBodyUtil.createProgressRequest(
multipartBuilder.build(),
new ProgressListener() {
long last = System.nanoTime();
@Override
public void onProgress(long bytesWritten, long contentLength, boolean done) {
long now = System.nanoTime();
if (done || shouldDispatch(now, last)) {
ResponseUtil.onDataSend(eventEmitter, requestId, bytesWritten, contentLength);
last = now;
}
}
}));
} else {
// Nothing in data payload, at least nothing we could understand anyway.
requestBuilder.method(method, RequestBodyUtil.getEmptyBody(method));
}
// 开始请求
addRequest(requestId);
client.newCall(requestBuilder.build()).enqueue(
new Callback() {
@Override
public void onFailure(Call call, IOException e) {
if (mShuttingDown) {
return;
}
removeRequest(requestId);
ResponseUtil.onRequestError(eventEmitter, requestId, e.getMessage(), e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (mShuttingDown) {
return;
}
removeRequest(requestId);
// Before we touch the body send headers to JS
ResponseUtil.onResponseReceived(
eventEmitter,
requestId,
response.code(),
translateHeaders(response.headers()),
response.request().url().toString());
ResponseBody responseBody = response.body();
try {
// If JS wants progress updates during the download, and it requested a text response,
// periodically send response data updates to JS.
if (useIncrementalUpdates && responseType.equals("text")) {
readWithProgress(eventEmitter, requestId, responseBody);
ResponseUtil.onRequestSuccess(eventEmitter, requestId);
return;
}
// Otherwise send the data in one big chunk, in the format that JS requested.
String responseString = "";
if (responseType.equals("text")) {
responseString = responseBody.string();
} else if (responseType.equals("base64")) {
responseString = Base64.encodeToString(responseBody.bytes(), Base64.NO_WRAP);
}
ResponseUtil.onDataReceived(eventEmitter, requestId, responseString);
ResponseUtil.onRequestSuccess(eventEmitter, requestId);
} catch (IOException e) {
ResponseUtil.onRequestError(eventEmitter, requestId, e.getMessage(), e);
}
}
});
}
请求结果返回后,Native通过RCTDeviceEventEmitter
将结果传递给JS端:
public static void onDataReceived(
RCTDeviceEventEmitter eventEmitter,
int requestId,
String data) {
WritableArray args = Arguments.createArray();
args.pushInt(requestId);
args.pushString(data);
eventEmitter.emit("didReceiveNetworkData", args);
}
在JS端进行监听即可获取到请求结果。同样,JS为了简化业务代码,使用RCTNetworking
进行了进一步封装。