前言
在Android应用开发中,网络请求监控是性能优化和问题排查的重要环节。OkHttp作为Android开发中广泛使用的网络库,其提供的EventListener机制为我们提供了强大的网络请求监控能力。本文将深入探讨如何使用HttpEventListener实现全面的网络请求监控,包括请求耗时分析、连接状态监控、错误追踪等关键功能。
OkHttp EventListener概述
OkHttp的EventListener是一个用于监听网络请求生命周期的接口,它提供了从请求开始到结束的完整回调。通过实现这个接口,我们可以捕获网络请求的各个阶段信息,为性能监控和问题诊断提供数据支持。
核心回调方法解析
public abstract class EventListener {
public static final Factory FACTORY = new Factory() {
@Override public EventListener create(Call call) {
return NONE;
}
};
public void callStart(Call call) {}
public void dnsStart(Call call, String domainName) {}
public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) {}
public void connectStart(Call call, InetSocketAddress inetSocketAddress, Proxy proxy) {}
public void secureConnectStart(Call call) {}
public void secureConnectEnd(Call call, Handshake handshake) {}
public void connectEnd(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, Protocol protocol) {}
public void connectFailed(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, Protocol protocol, IOException ioe) {}
public void connectionAcquired(Call call, Connection connection) {}
public void connectionReleased(Call call, Connection connection) {}
public void requestHeadersStart(Call call) {}
public void requestHeadersEnd(Call call, String requestHeaders) {}
public void requestBodyStart(Call call) {}
public void requestBodyEnd(Call call, long byteCount) {}
public void responseHeadersStart(Call call) {}
public void responseHeadersEnd(Call call, Response response) {}
public void responseBodyStart(Call call) {}
public void responseBodyEnd(Call call, long byteCount) {}
public void callEnd(Call call) {}
public void callFailed(Call call, IOException ioe) {}
public void canceled(Call call) {}
}
实战:构建完整的网络监控系统
1. 自定义EventListener实现
import okhttp3.*;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
public class NetworkMonitorEventListener extends EventListener {
private static final ConcurrentHashMap<String, NetworkRequestInfo> requestMap = new ConcurrentHashMap<>();
public static class NetworkRequestInfo {
public String requestId;
public long callStartTime;
public long dnsStartTime;
public long connectStartTime;
public long secureConnectStartTime;
public long requestHeadersStartTime;
public long requestBodyStartTime;
public long responseHeadersStartTime;
public long responseBodyStartTime;
public long totalDuration;
public String url;
public String method;
public int responseCode = -1;
public long requestSize = 0;
public long responseSize = 0;
public String errorMessage;
public boolean isSuccessful = false;
public NetworkRequestInfo(String requestId) {
this.requestId = requestId;
this.callStartTime = System.nanoTime();
}
}
@Override
public void callStart(Call call) {
String requestId = generateRequestId();
NetworkRequestInfo info = new NetworkRequestInfo(requestId);
info.url = call.request().url().toString();
info.method = call.request().method();
requestMap.put(requestId, info);
// 记录请求开始
System.out.println("Network Request Started: " + requestId + " " + info.url);
}
@Override
public void dnsStart(Call call, String domainName) {
NetworkRequestInfo info = getCurrentRequestInfo(call);
if (info != null) {
info.dnsStartTime = System.nanoTime();
}
}
@Override
public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) {
NetworkRequestInfo info = getCurrentRequestInfo(call);
if (info != null) {
long dnsDuration = System.nanoTime() - info.dnsStartTime;
System.out.println("DNS Resolution Time: " + dnsDuration / 1_000_000 + "ms for " + domainName);
}
}
@Override
public void connectStart(Call call, InetSocketAddress inetSocketAddress, Proxy proxy) {
NetworkRequestInfo info = getCurrentRequestInfo(call);
if (info != null) {
info.connectStartTime = System.nanoTime();
}
}
@Override
public void secureConnectStart(Call call) {
NetworkRequestInfo info = getCurrentRequestInfo(call);
if (info != null) {
info.secureConnectStartTime = System.nanoTime();
}
}
@Override
public void secureConnectEnd(Call call, Handshake handshake) {
NetworkRequestInfo info = getCurrentRequestInfo(call);
if (info != null && info.secureConnectStartTime > 0) {
long sslDuration = System.nanoTime() - info.secureConnectStartTime;
System.out.println("SSL Handshake Time: " + sslDuration / 1_000_000 + "ms");
}
}
@Override
public void requestHeadersStart(Call call) {
NetworkRequestInfo info = getCurrentRequestInfo(call);
if (info != null) {
info.requestHeadersStartTime = System.nanoTime();
}
}
@Override
public void requestBodyStart(Call call) {
NetworkRequestInfo info = getCurrentRequestInfo(call);
if (info != null) {
info.requestBodyStartTime = System.nanoTime();
}
}
@Override
public void requestBodyEnd(Call call, long byteCount) {
NetworkRequestInfo info = getCurrentRequestInfo(call);
if (info != null) {
info.requestSize = byteCount;
}
}
@Override
public void responseHeadersStart(Call call) {
NetworkRequestInfo info = getCurrentRequestInfo(call);
if (info != null) {
info.responseHeadersStartTime = System.nanoTime();
}
}
@Override
public void responseBodyStart(Call call) {
NetworkRequestInfo info = getCurrentRequestInfo(call);
if (info != null) {
info.responseBodyStartTime = System.nanoTime();
}
}
@Override
public void responseBodyEnd(Call call, long byteCount) {
NetworkRequestInfo info = getCurrentRequestInfo(call);
if (info != null) {
info.responseSize = byteCount;
}
}
@Override
public void callEnd(Call call) {
NetworkRequestInfo info = getCurrentRequestInfo(call);
if (info != null) {
info.totalDuration = System.nanoTime() - info.callStartTime;
info.isSuccessful = true;
processNetworkRequestInfo(info);
requestMap.remove(info.requestId);
}
}
@Override
public void callFailed(Call call, IOException ioe) {
NetworkRequestInfo info = getCurrentRequestInfo(call);
if (info != null) {
info.errorMessage = ioe.getMessage();
info.totalDuration = System.nanoTime() - info.callStartTime;
processNetworkRequestInfo(info);
requestMap.remove(info.requestId);
}
}
private NetworkRequestInfo getCurrentRequestInfo(Call call) {
for (NetworkRequestInfo info : requestMap.values()) {
if (info.url.equals(call.request().url().toString())) {
return info;
}
}
return null;
}
private String generateRequestId() {
return "req_" + System.currentTimeMillis() + "_" + Thread.currentThread().getId();
}
private void processNetworkRequestInfo(NetworkRequestInfo info) {
// 这里可以将网络请求信息发送到监控系统
System.out.println("=== Network Request Complete ===");
System.out.println("Request ID: " + info.requestId);
System.out.println("URL: " + info.url);
System.out.println("Method: " + info.method);
System.out.println("Duration: " + info.totalDuration / 1_000_000 + "ms");
System.out.println("Request Size: " + info.requestSize + " bytes");
System.out.println("Response Size: " + info.responseSize + " bytes");
System.out.println("Success: " + info.isSuccessful);
if (info.errorMessage != null) {
System.out.println("Error: " + info.errorMessage);
}
System.out.println("===============================");
}
}
2. 集成到OkHttpClient
public class NetworkMonitor {
private static OkHttpClient client;
public static OkHttpClient createMonitoredClient() {
if (client == null) {
client = new OkHttpClient.Builder()
.eventListener(new NetworkMonitorEventListener())
.build();
}
return client;
}
public static void makeRequest() {
Request request = new Request.Builder()
.url("https://api.example.com/data")
.build();
createMonitoredClient().newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
System.out.println("Request failed: " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
System.out.println("Response received: " + response.code());
response.close();
}
});
}
}
高级监控功能实现
1. 性能指标统计
public class NetworkPerformanceAnalyzer {
private static final int SLOW_REQUEST_THRESHOLD_MS = 2000; // 2秒
private static final int LARGE_RESPONSE_THRESHOLD = 1024 * 1024; // 1MB
public static void analyzePerformance(NetworkRequestInfo info) {
long durationMs = info.totalDuration / 1_000_000;
// 检测慢请求
if (durationMs > SLOW_REQUEST_THRESHOLD_MS) {
System.out.println("SLOW REQUEST DETECTED: " + info.url + " took " + durationMs + "ms");
}
// 检测大响应
if (info.responseSize > LARGE_RESPONSE_THRESHOLD) {
System.out.println("LARGE RESPONSE DETECTED: " + info.url + " size: " + info.responseSize + " bytes");
}
// 分析各阶段耗时
analyzeStageDurations(info);
}
private static void analyzeStageDurations(NetworkRequestInfo info) {
// 这里可以计算DNS、连接、SSL握手等各阶段的具体耗时
// 为网络优化提供数据支持
}
}
2. 错误分类与统计
public class NetworkErrorAnalyzer {
public enum ErrorType {
TIMEOUT,
NETWORK_UNAVAILABLE,
SSL_ERROR,
SERVER_ERROR,
CLIENT_ERROR,
UNKNOWN
}
public static ErrorType categorizeError(IOException error) {
String errorMsg = error.getMessage().toLowerCase();
if (errorMsg.contains("timeout") || errorMsg.contains("time out")) {
return ErrorType.TIMEOUT;
} else if (errorMsg.contains("ssl") || errorMsg.contains("certificate")) {
return ErrorType.SSL_ERROR;
} else if (errorMsg.contains("network") || errorMsg.contains("connection")) {
return ErrorType.NETWORK_UNAVAILABLE;
} else {
return ErrorType.UNKNOWN;
}
}
}
实际应用场景
1. 网络性能监控
通过EventListener可以实现:
- 请求耗时分析:识别慢请求,优化网络性能
- 连接时间统计:分析DNS解析、SSL握手等耗时
- 流量统计:监控上传下载流量,优化数据使用
2. 错误追踪与诊断
- 错误分类统计:统计各类网络错误发生频率
- 异常请求追踪:完整记录失败请求的生命周期
- 故障定位:快速定位网络问题发生的具体阶段
3. 用户体验优化
- 加载状态提示:根据请求进度提供更精确的加载提示
- 缓存策略优化:根据网络质量动态调整缓存策略
- 降级策略:网络异常时自动启用降级方案
最佳实践建议
1. 性能考虑
- 避免在EventListener回调中执行耗时操作
- 合理使用异步处理,避免阻塞网络请求
- 控制日志输出频率,避免过度日志影响性能
2. 数据安全
- 敏感信息(如请求参数)需要脱敏处理
- 监控数据传输需要加密保护
- 遵循数据隐私保护规范
3. 内存管理
- 及时清理已完成请求的监控数据
- 使用弱引用避免内存泄漏
- 合理设置监控数据的缓存策略
总结
OkHttp的EventListener机制为我们提供了强大的网络监控能力,通过合理实现可以构建完整的网络监控系统。在实际应用中,需要根据具体业务需求设计监控指标,平衡监控粒度与性能影响,确保监控系统既能提供有价值的网络数据,又不影响应用的正常运行。
通过本文的实践示例,开发者可以快速构建自己的网络监控系统,为应用的网络性能优化和问题排查提供有力支持。
参考资料
okhttp在3.11版本开始提供了一个网络时间监控的回调接口
HttpEventListener能进行一些耗时和事件统计:https://github.com/square/okhttp/blob/5c0591b13559565de42bbb845519438adec6395b/okhttp/src/main/java/okhttp3/EventListener.java
360的方案加入拦截器统计响应时间和上下行流量:https://github.com/Qihoo360/ArgusAPM/blob/bc03d63c65019cd3ffe2cbef9533c9228b3f2381/argus-apm/argus-apm-okhttp/src/main/java/com/argusapm/android/okhttp3/NetWorkInterceptor.java













网友评论