Потеря соединения — одно из самых частых и раздражающих явлений в мобильных приложениях. Пользователь теряет доступ к данным, прерывается операция, приложение зависает или вылетает. По данным Google Android Developer Blog, 70% пользователей удаляют приложение, если оно вылетает или зависает дважды. Разберём причины потери соединения и способы построения отказоустойчивых коммуникаций.
Главное
Отваливается — пользовательский термин, описывающий ситуацию, когда приложение теряет соединение с сервером, перестаёт отвечать на действия или завершается с ошибкой. В техническом смысле это может быть: сетевая ошибка (таймаут, DNS failure), ANR (блокировка UI-потока), crash (необработанное исключение) или race condition (состояние гонки).
Для пользователя все эти сценарии выглядят одинаково: приложение перестаёт работать. Разница для разработчика — в подходе к диагностике и исправлению. Сетевые ошибки решаются retry-механизмами, ANR — выносом операций из UI-потока, crash — exception handling.
По данным Crittercism (now Apteligent), в среднем мобильное приложение теряет 1-2% пользователей при каждом падении. Для приложения с 1 млн пользователей это 10-20 тысяч потерянных установок на один баг. Особенно критично для приложений в финансовом и медицинском секторах.
Нестабильная сеть — мобильные устройства постоянно переключаются между Wi-Fi и мобильной сетью, заходят в зоны без покрытия (метро, лифт, подвал). Каждое переключение вызывает временную потерю соединения, которую приложение должно обрабатывать корректно.
Таймауты — если сервер не отвечает в течение установленного таймаута (обычно 10-30 секунд), клиент бросает SocketTimeoutException. Долгие таймауты без обратной связи воспринимаются пользователем как зависание. Рекомендуется устанавливать таймаут не более 15 секунд.
val client = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build()
Состояние гонки (race condition) — возникает, когда несколько потоков одновременно читают и пишут одни и те же данные без синхронизации. Например, загрузка данных из кеша в UI-потоке параллельно с обновлением кеша из сети может привести к отображению устаревших или некорректных данных.
Offline-first — архитектурный паттерн, при котором локальное хранилище (Room, CoreData) является единственным источником истины. Сеть используется для синхронизации данных в фоне. Пользователь всегда видит актуальные данные из локального кеша, даже при отсутствии сети.
Repository pattern — единая точка входа для данных, которая решает, брать данные из сети или из кеша. Репозиторий абстрагирует источник данных от ViewModel и UI. При сетевой ошибке репозиторий автоматически переключается на локальный источник.
class UserRepository(
private val api: UserApi,
private val dao: UserDao
) {
suspend fun getUsers(): Result<List<User>> {
return try {
val remote = api.fetchUsers()
dao.insertAll(remote)
Result.success(remote)
} catch (e: IOException) {
val cached = dao.getAll()
if (cached.isNotEmpty()) {
Result.success(cached) // return cache on network error
} else {
Result.failure(e)
}
}
}
}
Circuit Breaker — паттерн защиты сервера от лавины запросов при недоступности. После N последовательных ошибок выключатель размыкается, и все запросы сразу возвращают ошибку без попытки соединения. Через заданный таймаут выключатель переходит в полуоткрытое состояние для пробного запроса.
Exponential backoff — стандартный механизм retry. После первой неудачи подождать 1 секунду, после второй — 2 секунды, затем 4, 8, 16. Ограничьте максимальное количество попыток (обычно 3-5), чтобы не перегружать сервер и батарею.
User feedback — при сетевой ошибке покажите понятное сообщение: «Нет соединения», «Сервер временно недоступен», «Проверьте интернет». Используйте Snackbar или Inline State View. Никогда не показывайте технические ошибки (HTTP 500, SocketException) пользователю.
ConnectivityManager — Android API для мониторинга сети. Разрешите приложение реагировать на изменения: при потере сети показывать placeholder, при восстановлении — автоматически обновлять данные. В iOS используйте NWPathMonitor из Network framework.
class NetworkMonitor(private val context: Context) {
private val manager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
fun isOnline(): Boolean {
val network = manager.activeNetwork ?: return false
val caps = manager.getNetworkCapabilities(network) ?: return false
return caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
}
}
Crashlytics (Firebase) — стандартный crash-reporting инструмент для мобильных приложений. Собирает stacktrace всех необработанных исключений, версию ОС, модель устройства и время падения. Позволяет группировать ошибки и назначать ответственных за фиксы.
Sentry — альтернатива Crashlytics с поддержкой performance monitoring. Позволяет трейсить конкретные транзакции (например, «авторизация пользователя») и видеть, на каком шаге произошла ошибка. Performance tracing помогает отличать сетевые таймауты от багов в логике приложения.
Timber — библиотека логирования для Android с автоматическим добавлением тегов по классу. В debug-сборке логгируйте все сетевые запросы и ответы. В release-сборке — только ошибки и предупреждения через Crashlytics.setCustomLog.
| Инструмент | Тип | Когда использовать |
|---|---|---|
| Crashlytics | Crash reporting | Всегда в release — автоматический сбор крашей |
| Sentry | Crash + Performance | Когда нужно профилировать конкретные пользовательские сценарии |
| Timber | Logging | Debug: полное логгирование; Release: только ошибки |
| HTTP Toolkit | Network debug | Локальный перехват и анализ HTTP-трафика |
По данным Firebase Summit 2023, приложения, внедрившие Crashlytics + Performance Monitoring, сокращают среднее время обнаружения и исправления критических багов с 3 дней до 4 часов. Рекомендуется настроить алерты на каждый crash с частотой >0.1% от активных пользователей.
Часто задаваемые вопросы
Если crash не ловится в Crashlytics, проверьте native crash (SIGSEGV, SIGABRT) — они не обрабатываются Java/Kotlin exception handler. В Android это может быть утечка native memory из JNI, в iOS — EXC_BAD_ACCESS. Используйте Breakpad (Android) или PLCrashReporter (iOS) для сбора native crash stacktrace.
Используйте Network Link Conditioner (встроен в iOS, для Android есть Facebook Network Connection Class или настройки Developer Options > Network > Select network type). Установите задержку 500-3000 ms и потерю пакетов 5-30%. Также можно использовать Charles Proxy или mitmproxy для эмуляции сетевых задержек и разрывов.
ANR возникает, если UI-поток заблокирован дольше 5 секунд. Сетевые запросы должны выполняться в фоновом потоке: корутины (viewModelScope.launch(Dispatchers.IO)), RxJava (subscribeOn(Schedulers.io)) или WorkManager для синхронизации. Всегда устанавливайте таймауты на HTTP-клиенте — отсутствие таймаута может привести к вечной блокировке.
Race condition — ситуация, когда результат операции зависит от порядка выполнения потоков. Например, пользователь быстро нажимает кнопку «Отправить» дважды, и запрос уходит дважды. Решение: используйте Mutex, single-threaded executors или state machine (отключите кнопку после первого нажатия). В Kotlin используйте Mutex из корутин или @Synchronized аннотацию.
Применяйте Chaos Engineering для мобильных приложений: отключайте сеть во время операций, симулируйте высокую задержку, переключайте между Wi-Fi и мобильной сетью, убивайте процесс системой. Инструменты: Facebook Network Connection Class, Charles Proxy, iOS Network Link Conditioner. В CI/CD добавляйте UI-тесты с разными сетевыми условиями через AndroidTest Orchestrator.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также