How Vivo Game Center Boosted Weak‑Network Performance with QUIC and Cronet
This article details how the Vivo Game Center team tackled slow page loads and resource failures in weak‑network environments by integrating the QUIC‑enabled Cronet library into OkHttp, defining precise network states, using custom interceptors, and conducting A/B tests that reduced failure rates by 40% and cut request latency by 7%.
Weak Network Optimization Background
The practice focuses on improving the Game Center app’s performance in weak‑network scenarios where page loading is slow and resources often fail to load. By adopting the QUIC‑enabled Cronet network library, connection establishment and data transmission speeds are increased, resulting in a 40% drop in page‑load failure rate, a 7% reduction in request latency, and noticeable image‑loading speed gains across all network conditions.
How to Define Network State
In mobile applications, network state encompasses connection type, speed, latency, and packet‑loss rate. The Game Center defines several dimensions—Wi‑Fi signal strength, cellular signal, ping to Vivo or Baidu domains, recent request failure rate, upstream/downstream bandwidth, and average request latency—to assign a numeric network‑state value used for analytics and optimization.
Weak network: Severe degradation that visibly harms user experience.
Suspected weak network: Unstable or degrading conditions that have not yet reached the severe level.
Game Center Integration of the QUIC Protocol
QUIC Overview
QUIC (Quick UDP Internet Connections) is a UDP‑based protocol introduced by Google in 2013. It aims to make web and app loading faster and more secure by establishing multiple streams per connection, reducing handshake latency, and automatically adjusting bandwidth to avoid congestion.
Application Scenarios
Lightweight resource transfer: Faster delivery of small files such as images and icons.
Video playback enhancement: Quicker first‑frame rendering and fewer interruptions.
High‑frequency interactive requests: Improved response times for login, payment, and other frequent interactions.
Stability under poor networks: Maintains data flow despite high latency or packet loss.
Large‑scale concurrent access: Better connection handling for many simultaneous users.
Implementation Details
Integrating Cronet with OkHttp
Cronet natively supports QUIC, while OkHttp does not. To keep existing OkHttp customizations, a Cronet interceptor is added to the OkHttp chain. The interceptor performs three main steps:
Convert an OkHttp Request into a Cronet Request.
Send the Cronet request and manage its lifecycle.
Convert the Cronet Response back into an OkHttp Response.
The custom interceptor is placed at the end of the OkHttp interceptor chain so that caching, logging, and authentication run before QUIC handling.
// 1. Create cache directory
val cachePath = File(AppContext.getContext().cacheDir, CRONET_CACHE_PATH)
if (!cachePath.isDirectory) {
cachePath.mkdirs()
VLog.d(TAG, "no cronet cache dir, mkdirs")
}
// 2. Build CronetEngine
var builder = CronetEngine.Builder(AppContext.getContext())
try {
builder = builder
.setStoragePath(cachePath.absolutePath) // set cache path
.enableBrotli(false) // Brotli disabled for now
.enableQuic(true) // enable QUIC
.enableHttp2(true) // enable HTTP/2
.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP, SIZE_1_MB.toLong())
// Add QUIC hints for configured hosts
NetworkManager.getInstance().quicHintHosts?.forEach {
builder = builder.addQuicHint(it, 443, 443)
}
cronetEngine = builder.build()
} catch (e: Throwable) {
VLog.e(TAG, "init cronet engine fail", e)
cronetEngine = null
}
// 3. Build OkHttpClient with Cronet integration
val netClientBuilder = defOkhttpClient.newBuilder()
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
.addInterceptor(CronetInterceptor.Builder(cronetEngine).build())The CronetInterceptor decides per‑request whether to use Cronet based on a configurable domain whitelist.
public final class CronetInterceptor implements Interceptor {
private static final String TAG = "CronetInterceptor";
private final RequestResponseConverter mConverter;
private CronetInterceptor(RequestResponseConverter converter) {
this.mConverter = checkNotNull(converter);
}
@Override
public Response intercept(Chain chain) throws IOException {
if (chain.call().isCanceled()) {
throw new IOException("Request call canceled");
}
Request request = chain.request();
if (OkHttpClientHelper.INSTANCE.isNeedUseCronet(request.url())) {
VLog.d(TAG, "use Cronet request:" + request.url());
return proceedWithCronet(chain);
} else {
VLog.d(TAG, "don't use Cronet request:" + request.url());
return proceedDefault(chain);
}
}
private Response proceedWithCronet(Chain chain) throws IOException {
RequestResponseConverter.CronetRequestAndOkHttpResponse requestAndOkHttpResponse =
mConverter.convert(chain.request(), chain.readTimeoutMillis(), chain.writeTimeoutMillis());
try {
requestAndOkHttpResponse.getRequest().start();
return toInterceptorResponse(requestAndOkHttpResponse.getResponse(), chain.call());
} catch (Throwable e) {
VLog.e(TAG, "proceedWithCronet exception:", e);
throw e;
}
}
private Response proceedDefault(Chain chain) throws IOException {
try {
Request request = chain.request();
VLog.d(TAG, "intercept " + request.method() + ", " + request.tag());
request = RequestHelper.handleRequest(request);
Response response = chain.proceed(request);
int retryNum = 0;
while ((response == null || !response.isSuccessful()) && retryNum < DEFAULT_RETRY_COUNT) {
retryNum++;
if (response != null && response.body() != null) {
response.body().close();
}
response = chain.proceed(request);
}
return response;
} catch (Throwable e) {
if (e instanceof IOException) {
throw e;
} else {
throw new IOException(e);
}
}
}
private Response toInterceptorResponse(Response response, Call call) {
checkNotNull(response.body());
return response.newBuilder()
.body(new CronetInterceptorResponseBody(response.body(), call))
.build();
}
}After receiving the Cronet response, it is transformed back into an OkHttp response so the rest of the app can continue using the familiar OkHttp API.
Testing Configuration
Three aspects are configured for the A/B experiment:
Domain support: Domains that should use QUIC must be listed and verified with the carrier for GQUIC/IQUIC compatibility.
Speed‑limit parameters: Different network‑state thresholds are set (see the image below).
Test tools: A custom bash script simulates latency, bandwidth, and packet‑loss on the device.
#!/bin/bash
# Set latency (ms), bandwidth (kbit/mbit), and packet loss (%).
# Example: 300ms latency, 100kbit bandwidth, 50% loss
# bash NetworkSimulation.sh 300ms 100kbit 50%
# Example: 100ms latency, 1mbit bandwidth, 0% loss
# bash NetworkSimulation.sh 100ms 1mbit 0%
adb root
adb shell setenforce 0
adb shell tc qdisc del dev ifb0 root
adb shell ip link set dev ifb0 down
adb shell tc qdisc del dev wlan0 ingress
adb shell tc qdisc del dev wlan0 root
if [ $# -eq 1 ]; then
echo "setup cleared"
elif [ $# -eq 3 ]; then
latency=$1
bandwidth=$2
packetloss=$3
adb shell ip link set dev wlan0 up
adb shell ip link set dev ifb0 up
adb shell tc qdisc del dev wlan0 clsact
adb shell tc qdisc add dev wlan0 handle ffff: ingress
adb shell tc filter add dev wlan0 parent ffff: protocol all u32 match u32 00 action mirred egress redirect dev ifb0
# Throttle upload
adb shell tc qdisc add dev wlan0 root handle 1: htb default11
adb shell tc class add dev wlan0 parent 1: classid 1:1 htb rate "$bandwidth"
adb shell tc class add dev wlan0 parent 1:1 classid 1:11 htb rate "$bandwidth"
adb shell tc qdisc add dev wlan0 parent 1:11 handle 10: netem delay "$latency" loss "$packetloss"
# Throttle download
adb shell tc qdisc add dev ifb0 root handle 1: htb default10
adb shell tc class add dev ifb0 parent 1: classid 1:1 htb rate "$bandwidth"
adb shell tc class add dev ifb0 parent 1:1 classid 1:10 htb rate "$bandwidth"
else
echo "Invalid parameters"
fiOptimization Results
Page‑load failure rate: Decreased by 40% overall.
Request latency: Average page request time reduced by 7%.
Image loading in normal networks: Speed improved by 38%.
Image loading in weak networks: Speed improved by 30%.
Image loading in extremely poor networks: Speed improved by up to 58%.
These results demonstrate that integrating QUIC via Cronet and fine‑grained network‑state detection can substantially enhance user experience even under challenging connectivity conditions.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
vivo Internet Technology
Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
