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

