Профилирование сети мобильного приложения
Приложение «работает нормально» на офисном Wi-Fi, но пользователи жалуются на медленную загрузку. Причина выясняется только при профилировании на реальных условиях: 47 секундных запросов при открытии ленты, из которых 12 — дублирующиеся запросы одних и тех же данных, 8 — запросы, которые можно было не делать вообще (данные есть в локальном кэше). Это видно только в сетевом профилировщике.
Инструменты профилирования сети
Charles Proxy / Proxyman
Перехватывают весь HTTP/HTTPS-трафик устройства. Charles Proxy — кросс-платформенный стандарт, Proxyman — нативный macOS-инструмент с лучшим UX для iOS-разработчиков. Настройка: установить корневой сертификат на устройство, выставить прокси в настройках Wi-Fi.
Что ищем в Charles:
- Дублирующиеся запросы — один и тот же URL несколько раз за короткий период
- Размер ответов — эндпоинты, отдающие явно лишние данные (10 KB там, где нужно 500 байт)
- Время ответа — медленные серверные ответы vs клиентская задержка
- Ошибки и ретраи — сколько запросов завершается ошибкой и как обрабатывается retry
Throttling в Charles (Proxy → Throttle Settings) — симуляция 3G, Edge, медленного Wi-Fi. Обязательный шаг: проверить приложение на 400 Kbps перед релизом. Поведение на плохом соединении часто не тестируется и содержит серьёзные баги.
Android Network Profiler
Встроен в Android Studio. Показывает запросы в хронологии, тело запроса/ответа, время DNS resolution, SSL handshake, waiting, downloading. Особенно полезен Connection View — видно, сколько параллельных соединений открыто и есть ли очередь ожидания.
Для OkHttp добавляем EventListener для точных метрик:
val client = OkHttpClient.Builder()
.eventListener(object : EventListener() {
override fun connectStart(call: Call, inetSocketAddress: InetSocketAddress, proxy: Proxy) {
Log.d("NET", "connectStart: ${call.request().url}")
}
override fun responseBodyEnd(call: Call, byteCount: Long) {
Log.d("NET", "responseBodyEnd: $byteCount bytes")
}
})
.build()
Xcode Network Instruments + URLSessionTaskMetrics
URLSessionTaskMetrics — встроенный механизм iOS для сбора метрик каждого запроса:
func urlSession(_ session: URLSession, task: URLSessionTask,
didFinishCollecting metrics: URLSessionTaskMetrics) {
for transaction in metrics.transactionMetrics {
print("DNS: \(transaction.domainLookupEndDate! - transaction.domainLookupStartDate!)")
print("TLS: \(transaction.secureConnectionEndDate! - transaction.secureConnectionStartDate!)")
print("TTFB: \(transaction.responseStartDate! - transaction.requestStartDate!)")
}
}
Это даёт breakdown: DNS lookup, TCP connect, TLS handshake, TTFB (Time to First Byte), transfer time. Если TLS handshake занимает 300 мс на каждом запросе — нет HTTP persistent connections или неправильно настроен Certificate Pinning без session reuse.
Что анализируем и исправляем
HTTP/2 multiplexing. Проверяем: используется ли HTTP/2 или приложение работает на HTTP/1.1 с 6 параллельными соединениями. URLSession и OkHttp поддерживают HTTP/2 автоматически если сервер его поддерживает. Видно в Charles: Protocol: h2 vs http/1.1.
Compression. Сервер должен возвращать Content-Encoding: gzip или br (Brotli) для JSON. Если нет — JSON-ответы идут в сыром виде. Разница для типичных API-ответов: 3–5x по размеру.
Connection reuse. TLS handshake — дорогая операция (50–200 мс). Persistent connections переиспользуют установленное соединение. Если каждый запрос начинается с нового handshake — проблема в конфигурации URLSession (несколько инстансов вместо shared) или в серверном keepalive timeout.
Кейс: 800 мс на запросы к API
Профилирование через Charles показало: каждый запрос к api.example.com имел DNS lookup 120–180 мс. Причина — DNS не кэшировался из-за короткого TTL (60 секунд) и URLSession не переиспользовал DNS resolution между сессиями. Решение: URLSessionConfiguration.urlCache с кастомным DNS prefetch + переход на единый URLSession.shared вместо создания нового инстанса в каждом сервисном классе.
Сроки
Профилирование сети и подготовка отчёта — 1–2 дня. Исправление выявленных проблем — 2–5 дней.







