Разработка
Как OkHttpClient повышает сетевую производительность
Замечательные абстракции, предоставляемые сетевой библиотекой OkHttp от компании Square, как правило, заслоняют собой оптимизацию, которая часто остается незамеченной разработчиками.
Замечательные абстракции, предоставляемые сетевой библиотекой OkHttp от компании Square, как правило, заслоняют собой оптимизацию, которая часто остается незамеченной разработчиками. А ведь библиотека использует стандартное соединение на основе TCP handshake для новых запросов и предлагает пул соединений по умолчанию из 5 соединений (каждое активно в течение 5 минут) для повторного использования ранее установленного TCP-соединения для последующих запросов.
class ConnectionPool internal constructor( internal val delegate: RealConnectionPool ) { constructor( maxIdleConnections: Int, keepAliveDuration: Long, timeUnit: TimeUnit ) : this(RealConnectionPool( taskRunner = TaskRunner.INSTANCE, maxIdleConnections = maxIdleConnections, keepAliveDuration = keepAliveDuration, timeUnit = timeUnit )) constructor() : this(5, 5, TimeUnit.MINUTES) ... }
Под капотом
Как пул соединений повышает производительность сети? Это можно проверить с помощью специфического EventListener, связанного с вызовом OkHttp.
private class OkHttpEventListenerFactory: EventListener.Factory { override fun create(call: Call): EventListener { return object: EventListener() { override fun callStart(call: Call) { Log.d("OkHttp", "CallStart") } override fun callEnd(call: Call) { Log.d("OkHttp", "CallEnd") } override fun dnsStart(call: Call, domainName: String) { Log.d("OkHttp", "dnsStart") } override fun dnsEnd( call: Call, domainName: String, inetAddressList: List<InetAddress> ) { Log.d("OkHttp", "dnsEnd") } override fun secureConnectStart(call: Call) { Log.d("OkHttp", "secureConnectStart") } override fun secureConnectEnd(call: Call, handshake: Handshake?) { Log.d("OkHttp", "secureConnectEnd") } override fun connectStart( call: Call, inetSocketAddress: InetSocketAddress, proxy: Proxy ) { Log.d("OkHttp", "connectStart") } override fun connectEnd( call: Call, inetSocketAddress: InetSocketAddress, proxy: Proxy, protocol: Protocol? ) { Log.d("OkHttp", "connectEnd") } override fun connectionAcquired(call: Call, connection: Connection) { Log.d("OkHttp", "connectionAcquired") } override fun connectionReleased(call: Call, connection: Connection) { Log.d("OkHttp", "connectionReleased") } override fun requestHeadersStart(call: Call) { Log.d("OkHttp", "requestHeadersStart") } override fun requestHeadersEnd(call: Call, request: Request) { Log.d("OkHttp", "requestHeadersEnd") } override fun requestBodyStart(call: Call) { Log.d("OkHttp", "requestBodyStart") } override fun requestBodyEnd(call: Call, byteCount: Long) { Log.d("OkHttp", "requestBodyEnd") } override fun responseHeadersStart(call: Call) { Log.d("OkHttp", "responseHeadersStart") } override fun responseHeadersEnd(call: Call, response: Response) { Log.d("OkHttp", "responseHeadersEnd") } override fun responseBodyStart(call: Call) { Log.d("OkHttp", "responseBodyStart") } override fun responseBodyEnd(call: Call, byteCount: Long) { Log.d("OkHttp", "responseBodyEnd") } } } }
Вышеуказанный слушатель событий регистрируется клиентом. Кэш отключен, чтобы исключить возможность попадания в кэш при последующих запросах.
private val client = OkHttpClient.Builder() .cache(null) .eventListenerFactory(OkHttpEventListenerFactory()) .build()
Этот клиент используется для того, чтобы обратиться к конечной точке несколько раз.
val request = Request.Builder() .url("https://www.google.com") .build() client.newCall(request)
Первое обращение занимает 676 миллисекунд и включает установку нового TCP-соединения, включая разрешение DNS, SSL Handshake и HTTP-запрос и ответ. Теперь это новое соединение добавляется в пул соединений, и, следовательно, connectionReleased
не вызывается.
19:57:03.488 D CallStart 19:57:03.524 D dnsStart 19:57:03.554 D dnsEnd 19:57:03.555 D connectStart 19:57:03.581 D secureConnectStart 19:57:03.640 D secureConnectEnd 19:57:03.646 D connectEnd 19:57:03.646 D connectionAcquired 19:57:03.646 D requestHeadersStart 19:57:03.648 D requestHeadersEnd 19:57:04.114 D responseHeadersStart 19:57:04.114 D responseHeadersEnd 19:57:04.115 D responseBodyStart 19:57:04.115 D responseBodyEnd 19:57:04.116 D requestHeadersStart 19:57:04.116 D requestHeadersEnd 19:57:04.164 D responseHeadersStart 19:57:04.164 D responseHeadersEnd
Однако последующее обращение обрабатывается всего за 506 миллисекунд (минуя поиск DNS) за счет использования существующего TCP-соединения из пула соединений.
19:58:20.041 D CallStart 19:58:20.043 D connectionAcquired 19:58:20.043 D requestHeadersStart 19:58:20.044 D requestHeadersEnd 19:58:20.502 D responseHeadersStart 19:58:20.502 D responseHeadersEnd 19:58:20.503 D responseBodyStart 19:58:20.503 D responseBodyEnd 19:58:20.503 D requestHeadersStart 19:58:20.504 D requestHeadersEnd 19:58:20.547 D responseHeadersStart 19:58:20.547 D responseHeadersEnd
Заключение
Хотя стандартный пул подключений достаточно хорош для большинства приложений, приложениям, использующим один и тот же OkHttpClient для нескольких конечных точек (через Retrofit), стоит подумать либо об увеличении пула подключений, либо об использовании специфических OkHttpClient для конечных точек (Service).