Imagine: a user opens an app, scrolls through the feed—and the screen freezes for half a second. Touches don't register, animation stutters. After a couple of seconds, everything returns to normal, but the impression lingers. The repeat session rate drops, while Crashlytics remains silent—no formal crash. This is a classic App Hang (iOS) or UI Freeze (Android) scenario. We help configure monitoring for such hangs, identify bottlenecks, and eliminate them. Budget savings from reduced freezes: up to 30% reduction in main thread load.
Why Standard Crash Reports Miss Hangs
App Hangs and UI Freezes don't throw exceptions—the app doesn't crash, but stops responding to user actions. The iOS Watchdog terminates the process only after 4–8 seconds of hanging, and Android ANR after 5 seconds. Short freezes of 200–500 ms go undetected but ruin the user experience. Standard crash reporters (Crashlytics, Firebase Crashlytics) only capture exceptions and signals, so separate tracking is needed to detect hangs. We use three approaches: watchdog thread, frame metrics, and task duration tracking.
What Hang Threshold to Set for Production
We recommend 250 ms for iOS and 200 ms for Android. A lower threshold (100 ms) will generate noise from random delays, while a higher threshold (500 ms) will miss noticeable freezes. The threshold can be adjusted per screen if needed—for example, complex lists can use 300 ms. Sentry and Datadog allow per-threshold configuration. The setup cost pays off through improved user experience.
Detection Tools
| Tool | Platform | Threshold | Latency | Notes |
|---|---|---|---|---|
| MetricKit | iOS | 250 ms | 24 hours | Built into the OS, no SDK required, but not real-time |
| Sentry App Hang | iOS, Android | from 100 ms | real-time | Watchdog thread, call stack recording |
| FrameMetricsAggregator | Android | >16 ms | on request | Janky frame statistics in production |
| Datadog RUM Long Task | iOS, Android | from 100 ms | real-time | Dashboards and grouping by screen |
Sentry detects hangs in real-time, while MetricKit has a 24-hour delay. Sentry identifies freezes 100 times faster, which is critical for hotfixes. MetricKit Documentation confirms the up to 24-hour delay.
Common Causes of Hangs
| Platform | Typical Cause | Duration |
|---|---|---|
| iOS | Image decoding on main thread | 30–80 ms |
| iOS | Synchronous data loading | 100–300 ms |
| Android | Unnecessary recomposition in Compose | 50–200 ms |
| Android | GC pauses | 10–50 ms |
These data points are based on real projects with audiences of 100k+ users.
iOS — Sentry App Hang Detection
SentrySDK.start { options in options.dsn = "https://[email protected]/project" options.enableAppHangTracking = true options.appHangTimeoutInterval = 0.25 // 250ms } Sentry launches a watchdog thread that pings the main thread every 100 ms. If no response within appHangTimeoutInterval, it captures the stack via backtrace_thread and submits it as an Issue.
Android — FrameMetricsAggregator
val frameMetrics = FrameMetricsAggregator(FrameMetricsAggregator.JANK_DATA) frameMetrics.add(activity) // Later: val metrics = frameMetrics.metrics metrics?.get(FrameMetricsAggregator.JANK_INDEX)?.let { jankArray -> val jankyFrames = jankArray.size } FrameMetricsAggregator is suitable for production: lightweight, provides janky frame statistics. For deep local diagnostics, use Perfetto.
Configuring Monitoring in Datadog
RUM.enable(with: RUM.Configuration( applicationID: "your-rum-app-id", longTaskThreshold: 0.1 // 100ms )) In the Datadog dashboard, build a widget:
count:rum.long_task{env:production,service:ios-app} group_by: @view.name visualize_as: top_list This reveals which screens have the most hangs.
Where Hangs Come From
On iOS, the most common source of short freezes is synchronous calls on the main thread in response to a UI event:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "ProductCell", for: indexPath) as! ProductCell cell.imageView?.image = UIImage(data: product.imageData) return cell } UIImage(data:) synchronously decodes JPEG/PNG. On an iPhone SE with a 1900x1200 image, this blocks the main thread for 30–80 ms.
On Android, a main culprit on Compose screens is unnecessary recomposition in LazyColumn:
@Composable fun ProductList(products: List<Product>) { LazyColumn { items(products) { product -> ProductCard(product) } } } Replacing List<Product> with ImmutableList<Product> (from kotlinx.collections.immutable) or using the @Stable annotation eliminates unnecessary recompositions.
What's Included in Our Work
- Integration of Sentry with
enableAppHangTrackingand a 250 ms threshold - Configuration of a MetricKit subscriber for daily diagnostics
- Enabling Datadog RUM Long Task tracking with a 100 ms threshold
- On Android, setting up FrameMetricsAggregator for key Activities
- Building a dashboard of screens with the highest number of Long Tasks
- Analysis of stack traces to identify specific culprits
- A written report with optimization recommendations
Our Experience
We have extensive experience with mobile projects. We've set up monitoring for 20+ apps with audiences of 100k+ users. We use modern approaches: MetricKit, Sentry, Datadog, Apple App Hang, Android ANR.
Timeline
Basic setup via Sentry and Datadog: 4–8 hours. Full diagnostics with MetricKit and custom per-screen metrics: 1–2 days. Pricing is determined after analysis.
Contact us to select the best tools for your project. Request monitoring setup—and get a performance guarantee.







