监听网络请求

Android线上统计网络时长好做吗

Posted by River on January 8, 2022

前言

移动端线上监控体系包括网络请求的部分,Android上实际开发起来并不是一件容易的事情。
ios因为有系统提供的NSURLProtocol,相对较容易实现;鸭梨山大。

需求背景

1.虽然高版本的okhttp提供了EventListener监听请求的各个时机回调,应用到线上并不容易
2.难点在于把每个请求的回调能区分识别出来。
3.网络库实现基于Retrofit实现,无缝对接kotlin协程。
okhttp流程图

核心突破点,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()));
        ...
        
    }

}


总结要点

  1. 根据线索勇于阅读源码,最终找到Retrofit的RequestFactory这个核心突破口。
  2. tag的设计内部通过Map实现,支持多对数据的存储。
  3. 碰到问题结构化的思考,逐步找到线索。