前言
移动端线上监控体系包括网络请求的部分,Android上实际开发起来并不是一件容易的事情。
ios因为有系统提供的NSURLProtocol,相对较容易实现;鸭梨山大。
需求背景
1.虽然高版本的okhttp提供了EventListener监听请求的各个时机回调,应用到线上并不容易
2.难点在于把每个请求的回调能区分识别出来。
3.网络库实现基于Retrofit实现,无缝对接kotlin协程。
核心突破点,EventListener众多回调接口都带call对象,call对象可以直接获取request对象;
通过研究Retrofit的在创建request对象的时候塞入一个tag,RequestFactory在创建请求的时候塞入一个请求tag。
final class RequestFactory {
okhttp3.Request create(Object[] args) throws IOException {
...
RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl,
headers, contentType, hasBody, isFormEncoded, isMultipart);
final Request.Builder builder = requestBuilder.get();
...
return builder
.tag(Invocation.class, new Invocation(method, argumentList))
.build();
}
}
通过牢牢的锁定这个tag即可实现
public class ProbeEventListener extends EventListener {
public static ProbeEventListener create() {
return new ProbeEventListener();
}
private static ConcurrentHashMap<Integer, ProbeEventEntity> callMap = new ConcurrentHashMap<>();
private ProbeEventListener() {
}
public static ProbeEventEntity getEventEntity(Integer key) {
return callMap.get(key);
}
public static ProbeEventEntity getEventEntity(Call call) {
if (call == null) {
return null;
}
return getEventEntity(call.request());
}
public static ProbeEventEntity getEventEntity(Request request) {
if (request == null) {
return null;
}
Invocation invocation = request.tag(Invocation.class);
if (invocation == null) {
return null;
}
return getEventEntity(invocation.hashCode());
}
/**
* 移除callMap里的call数据,防止内存泄漏
* */
public static void removeCall(Call call) {
if (call == null) {
return;
}
Invocation invocation = call.request().tag(Invocation.class);
if (invocation == null) {
return;
}
if (callMap.containsKey(invocation.hashCode())) {
callMap.remove(invocation.hashCode());
}
}
@Override
public void callStart(Call call) {
Invocation invocation = call.request().tag(Invocation.class);
if (invocation == null) {
return;
}
ProbeEventEntity entity = new ProbeEventEntity();
entity.setCallStart(System.currentTimeMillis());
callMap.put(invocation.hashCode(), entity);
}
@Override
public void dnsStart(Call call, String domainName) {
ProbeEventEntity entity = getEventEntity(call);
if (entity != null) {
entity.setDnsStart(System.currentTimeMillis());
}
}
@Override
public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) {
ProbeEventEntity entity = getEventEntity(call);
if (entity != null) {
entity.setDnsEnd(System.currentTimeMillis());
}
}
@Override
public void connectStart(Call call, InetSocketAddress inetSocketAddress, Proxy proxy) {
ProbeEventEntity entity = getEventEntity(call);
if (entity != null) {
entity.setConnectStart(System.currentTimeMillis());
}
}
@Override
public void secureConnectStart(Call call) {
ProbeEventEntity entity = getEventEntity(call);
if (entity != null) {
entity.setSecureConnectStart(System.currentTimeMillis());
}
}
@Override
public void secureConnectEnd(Call call, Handshake handshake) {
ProbeEventEntity entity = getEventEntity(call);
if (entity != null) {
entity.setSecureConnectEnd(System.currentTimeMillis());
if (handshake != null && handshake.tlsVersion() != null){
entity.setTlsVersion(handshake.tlsVersion().javaName());
}
}
}
@Override
public void connectEnd(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, Protocol protocol) {
ProbeEventEntity entity = getEventEntity(call);
if (entity != null) {
entity.setConnectEnd(System.currentTimeMillis());
}
}
@Override
public void connectFailed(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, Protocol protocol, IOException ioe) {
ProbeEventEntity entity = getEventEntity(call);
if (entity != null) {
entity.setConnectFailed(System.currentTimeMillis());
}
}
@Override
public void connectionAcquired(Call call, Connection connection) {
ProbeEventEntity entity = getEventEntity(call);
if (entity != null) {
entity.setConnectAcquired(System.currentTimeMillis());
}
}
@Override
public void connectionReleased(Call call, Connection connection) {
ProbeEventEntity entity = getEventEntity(call);
if (entity != null) {
entity.setConnectionReleased(System.currentTimeMillis());
}
}
@Override
public void requestHeadersStart(Call call) {
ProbeEventEntity entity = getEventEntity(call);
if (entity != null) {
entity.setRequestHeadersStart(System.currentTimeMillis());
}
}
@Override
public void requestHeadersEnd(Call call, Request request) {
ProbeEventEntity entity = getEventEntity(call);
if (entity != null) {
entity.setRequestHeadersEnd(System.currentTimeMillis());
}
}
@Override
public void requestBodyStart(Call call) {
ProbeEventEntity entity = getEventEntity(call);
if (entity != null) {
entity.setRequestBodyStart(System.currentTimeMillis());
}
}
@Override
public void requestBodyEnd(Call call, long byteCount) {
ProbeEventEntity entity = getEventEntity(call);
if (entity != null) {
entity.setRequestBodyEnd(System.currentTimeMillis());
}
}
@Override
public void responseHeadersStart(Call call) {
ProbeEventEntity entity = getEventEntity(call);
if (entity != null) {
entity.setResponseHeadersStart(System.currentTimeMillis());
}
}
@Override
public void responseHeadersEnd(Call call, Response response) {
ProbeEventEntity entity = getEventEntity(call);
if (entity != null) {
entity.setResponseHeadersEnd(System.currentTimeMillis());
}
}
@Override
public void responseBodyStart(Call call) {
ProbeEventEntity entity = getEventEntity(call);
if (entity != null) {
entity.setResponseBodyStart(System.currentTimeMillis());
}
}
@Override
public void responseBodyEnd(Call call, long byteCount) {
ProbeEventEntity entity = getEventEntity(call);
if (entity != null) {
entity.setResponseBodyEnd(System.currentTimeMillis());
}
}
@Override
public void callEnd(Call call) {
ProbeEventEntity entity = getEventEntity(call);
if (entity != null) {
entity.setCallEnd(System.currentTimeMillis());
entity.setInvalidTime(System.currentTimeMillis() + DURATION_TO_INVALID);
}
}
@Override
public void callFailed(Call call, IOException ioe) {
ProbeEventEntity entity = getEventEntity(call);
if (entity != null) {
entity.setCallFailed(System.currentTimeMillis());
entity.setInvalidTime(System.currentTimeMillis() + DURATION_TO_INVALID);
}
}
工程化后的冗余处理
public class ProbeEventListener extends EventListener {
private static HandlerThread handlerThread;
private static Handler cleaner;
private ProbeEventListener() {
handlerThread = new HandlerThread("apm-network-monitor-cleaner-thread");
handlerThread.start();
cleaner = new Handler(handlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_DO_CLEAN) {
//Log.i(TAG, "cleaner, clean callMap:\n" + printMap(callMap));
// 定时清理已经结束的call的监控事件对象,防止内存泄漏
List<Integer> removeEvents = new ArrayList<>();
long curTimestamp = System.currentTimeMillis();
for (Map.Entry<Integer, ProbeEventEntity> entry : callMap.entrySet()) {
if (entry.getValue().getInvalidTime() <= curTimestamp) {
removeEvents.add(entry.getKey());
}
}
for (Integer key : removeEvents) {
callMap.remove(key);
}
removeMessages(MSG_DO_CLEAN);
sendEmptyMessageDelayed(MSG_DO_CLEAN, DURATION_TO_CLEAN);
}
}
};
cleaner.removeMessages(MSG_DO_CLEAN);
cleaner.sendEmptyMessageDelayed(MSG_DO_CLEAN, DURATION_TO_CLEAN);
}
}
核心时长定义
parseEvent(XXX.Builder builder, ProbeEventEntity entity) {
if (entity == null) {
return;
}
long dnsDuration = entity.getDnsEnd() - entity.getDnsStart();
if (dnsDuration < 0) {
dnsDuration = 0;
}
long wrrtDuration = entity.getResponseHeadersStart() - entity.getRequestBodyEnd();
if (wrrtDuration < 0) {
wrrtDuration = 0;
}
long responseDuration = entity.getResponseBodyEnd() - entity.getResponseHeadersStart();
if (responseDuration < 0) {
responseDuration = 0;
}
long requestDuration = entity.getDnsStart() - entity.getCallStart();
if (requestDuration < 0){
requestDuration = 0;
}
long tcpHandshake = entity.getSecureConnectStart() - entity.getConnectStart();
if (tcpHandshake < 0) {
tcpHandshake = 0;
}
long sslDuration = entity.getSecureConnectEnd() - entity.getSecureConnectStart();
if (sslDuration < 0) {
sslDuration = 0;
}
if (!TextUtils.isEmpty(entity.getTlsVersion())){
builder.setTlsVersion(entity.getTlsVersion());
}
builder.networkDNSTime(dnsDuration);
builder.networkWRRTime(wrrtDuration);
builder.networkResponseTime(responseDuration);
builder.networkRequestTime(requestDuration);
builder.networkCNNTime(tcpHandshake);
builder.networkSSLTime(sslDuration);
}
大数据结构整合
在Retrofit的json转换接口,addConverterFactory实现SignConverter;
异常请求在catch中获取
internal class SignConverter{
override fun responseBodyConverter(
type: Type, annotations: Array<Annotation>,
retrofit: Retrofit
): Converter<ResponseBody, *> {
return JacksonResponseBodyConverter<Any>(type, provider)
}
}
internal class JacksonResponseBodyConverter<T>(
private val returnType: Type,
private val provider: NetworkingProvider
) : Converter<ResponseBody, T> {
override fun convert(value: ResponseBody): T{
val response = (value as OkHttpCall.ExceptionCatchingResponseBody).response
httpCallListener.onResponse(
response, provider.url(), action, result.code.toString(),
result.msg, System.currentTimeMillis() - startTime.toLong()
)
}
}
public class ApmHttpCallListener extends HttpCallListener {
public void onResponse(Response response, String action, String code, String msg, long time){
...
NetXXXBean.Builder builder = NetXXXBean.Builder
.newBuilder()
.url(url)
.httpResponseCode(httpRespCode)
.responseCode(respCode)
.state(state)
.action(action)
.responseTime(time)
.signature(signature)
.errorMessage(Log.getStackTraceString(e))
.errorCode(e == null ? "" : e.getClass().getName());
parseProbeEvent(builder, ProbeEventListener.getEventEntity(response.request()));
...
}
}
总结要点
- 根据线索勇于阅读源码,最终找到Retrofit的RequestFactory这个核心突破口。
- tag的设计内部通过Map实现,支持多对数据的存储。
- 碰到问题结构化的思考,逐步找到线索。