Замечательные абстракции, предоставляемые сетевой библиотекой 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).