Интеграция WebSocket-соединения для чата мобильного приложения
WebSocket даёт полнодуплексное соединение с минимальными накладными расходами по сравнению с polling — это правильный выбор для реального чата. Но мобильная специфика добавляет слой сложности: приложение уходит в background, сеть меняется с Wi-Fi на LTE, пользователь блокирует экран. WebSocket-клиент, который хорошо работает на десктопе, может потерять соединение через 30 секунд после ухода приложения в фон на iOS из-за агрессивного управления ресурсами.
Управление соединением — основная сложность
iOS. Background execution для WebSocket официально не поддерживается. Если приложению нужно получать сообщения в фоне — единственный официальный путь это push-уведомления (APNs). WebSocket остаётся активным только пока приложение на переднем плане. Попытки держать соединение живым через URLSessionWebSocketTask с фоновым URLSessionConfiguration работают нестабильно и нарушают Guidelines.
На iOS используем URLSessionWebSocketTask (iOS 13+):
class WebSocketManager {
private var webSocketTask: URLSessionWebSocketTask?
func connect() {
let session = URLSession(configuration: .default, delegate: self, delegateQueue: .main)
webSocketTask = session.webSocketTask(with: URL(string: "wss://api.example.com/ws")!)
webSocketTask?.resume()
receiveMessage()
}
private func receiveMessage() {
webSocketTask?.receive { [weak self] result in
switch result {
case .success(let message):
self?.handleMessage(message)
self?.receiveMessage() // рекурсивно
case .failure(let error):
self?.scheduleReconnect()
}
}
}
}
Android. OkHttp WebSocket — де-факто стандарт. В фоне соединение может обрывать JobScheduler или Doze Mode на Android 6+. Решение: WorkManager для переодической синхронизации + FCM push для доставки сообщений в background. WebSocket держим живым только когда приложение в foreground, опционально — ForegroundService с уведомлением.
val client = OkHttpClient.Builder()
.pingInterval(30, TimeUnit.SECONDS) // heartbeat
.build()
val request = Request.Builder().url("wss://api.example.com/ws").build()
val ws = client.newWebSocket(request, object : WebSocketListener() {
override fun onMessage(webSocket: WebSocket, text: String) {
// handle message
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
scheduleReconnect()
}
})
pingInterval(30) важен — без heartbeat соединение разрывается промежуточными прокси после 60–90 секунд тишины.
Переподключение с экспоненциальным backoff
Переподключение при разрыве — обязательная логика. Простой retry через 1 секунду создаёт шторм запросов при падении сервера. Правильный подход — exponential backoff с jitter:
private var reconnectDelay = 1000L
fun scheduleReconnect() {
viewModelScope.launch {
delay(reconnectDelay + Random.nextLong(500))
reconnectDelay = minOf(reconnectDelay * 2, 30_000L)
connect()
}
}
fun onConnected() {
reconnectDelay = 1000L
}
Flutter: пакет web_socket_channel — обёртка над нативными реализациями. Для продакшн-уровня рекомендуем stomp_dart_client если сервер использует STOMP, или кастомный manager с теми же принципами reconnect.
Аутентификация
WebSocket-соединение аутентифицируется один раз при установке — через Authorization заголовок в handshake или первым сообщением после подключения (auth frame). JWT-токен может протухнуть за время сессии — нужна логика обновления токена и переподключения.
Что входит в работу
Реализуем WebSocket-клиент под платформу с reconnect-логикой, heartbeat, обработкой смены сети (используем NetworkCallback на Android, NWPathMonitor на iOS). Если нужен fallback на push — интегрируем APNs/FCM для доставки сообщений в фоне.
Срок: 4–8 дней для полноценной реализации с учётом edge-cases по сетевым условиям.







