Разработка
Как 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.
xxxxxxxxxx
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")
}
}
}
}
Вышеуказанный слушатель событий регистрируется клиентом. Кэш отключен, чтобы исключить возможность попадания в кэш при последующих запросах.
xxxxxxxxxx
private val client = OkHttpClient.Builder()
.cache(null)
.eventListenerFactory(OkHttpEventListenerFactory())
.build()
Этот клиент используется для того, чтобы обратиться к конечной точке несколько раз.
xxxxxxxxxx
val request = Request.Builder()
.url("https://www.google.com")
.build()
client.newCall(request)
Первое обращение занимает 676 миллисекунд и включает установку нового TCP-соединения, включая разрешение DNS, SSL Handshake и HTTP-запрос и ответ. Теперь это новое соединение добавляется в пул соединений, и, следовательно, connectionReleased
не вызывается.
xxxxxxxxxx
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-соединения из пула соединений.
xxxxxxxxxx
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).
-
Новости1 неделя назад
Видео и подкасты о мобильной разработке 2025.14
-
Видео и подкасты для разработчиков3 недели назад
Javascript для бэкенда – отличная идея: Node.js, NPM, Typescript
-
Новости3 недели назад
Видео и подкасты о мобильной разработке 2025.12
-
Разработка3 недели назад
«Давайте просто…»: системные идеи, которые звучат хорошо, но почти никогда не работают