Mobile Application Network Profiling
An application works fine on office Wi-Fi, but users complain about slow loading. Root cause is revealed only through profiling under real conditions: 47 synchronous requests when opening a feed, of which 12 are duplicate requests for the same data, and 8 are requests that could have been avoided entirely (data exists in local cache). This is only visible in a network profiler.
Network Profiling Tools
Charles Proxy / Proxyman
Both intercept all HTTP/HTTPS traffic from the device. Charles Proxy is the cross-platform standard; Proxyman is a native macOS tool with better UX for iOS developers. Setup: install root certificate on the device, set proxy in Wi-Fi settings.
What to look for in Charles:
- Duplicate requests — the same URL called multiple times over a short period
- Response size — endpoints returning clearly unnecessary data (10 KB where 500 bytes suffice)
- Response time — slow server responses vs client-side latency
- Errors and retries — how many requests fail and how retry is handled
Throttling in Charles (Proxy → Throttle Settings) — simulates 3G, Edge, slow Wi-Fi. Essential step: test the application at 400 Kbps before release. Behavior on poor connections is often untested and contains serious bugs.
Android Network Profiler
Built into Android Studio. Shows requests chronologically, request/response body, DNS resolution time, SSL handshake, waiting, downloading. Especially useful is Connection View — shows how many parallel connections are open and whether there's a waiting queue.
For OkHttp, add EventListener for precise metrics:
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 — built-in iOS mechanism for collecting metrics from each request:
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!)")
}
}
This provides breakdown: DNS lookup, TCP connect, TLS handshake, TTFB (Time to First Byte), transfer time. If TLS handshake takes 300 ms on each request — either HTTP persistent connections are missing or Certificate Pinning is configured incorrectly without session reuse.
What We Analyze and Fix
HTTP/2 multiplexing. Check: is HTTP/2 used or does the application work on HTTP/1.1 with 6 parallel connections? URLSession and OkHttp support HTTP/2 automatically if server supports it. Visible in Charles: Protocol: h2 vs http/1.1.
Compression. Server should return Content-Encoding: gzip or br (Brotli) for JSON. If not — JSON responses go uncompressed. Difference for typical API responses: 3–5x in size.
Connection reuse. TLS handshake — expensive operation (50–200 ms). Persistent connections reuse established connection. If each request starts with new handshake — problem is in URLSession configuration (multiple instances instead of shared) or in server keepalive timeout.
Case: 800 ms on API requests
Profiling through Charles showed: each request to api.example.com had DNS lookup 120–180 ms. Reason — DNS was not cached due to short TTL (60 seconds) and URLSession did not reuse DNS resolution between sessions. Solution: URLSessionConfiguration.urlCache with custom DNS prefetch + switch to single URLSession.shared instead of creating new instance in each service class.
Timeframe
Network profiling and report preparation — 1–2 days. Fixing identified issues — 2–5 days.







