Skip to main content

5 posts tagged with "detect"

View All Tags

Production Memory Diagnostics: Tracking Leaks, OOMs, and Jetsam in Mobile Apps

Published: · 13 min read
Robin Alex Panicker
Cofounder and CPO, Appxiom

In local development, diagnosing a memory leak is a solved problem.

You fire up Android Studio Memory Profiler or Xcode Instruments Allocations, attach a debugger, record a trace, inspect the object graph, or let LeakCanary dump a heap snapshot. Within minutes, you can pinpoint the exact retain cycle or static reference pinning an Activity or UIViewController in RAM.

In production, that luxury vanishes.

Running a heap dump in a live production app is an engineering non-starter. A standard Android heap dump (Debug.dumpHprofData()) freezes the main thread for 10 to 30 seconds, creates an uncompressed 500MB+ .hprof file on user storage, risks exfiltrating unredacted Personally Identifiable Information (PII), and frequently triggers operating system watchdogs. On iOS, you cannot capture memory graphs or attach Instruments to App Store binaries at all.

Even worse, Out-Of-Memory (OOM) events are the most silent killers in mobile engineering.

When an iOS device runs out of physical RAM, Apple's Jetsam kernel daemon terminates your process with a SIGKILL. On Android, the LowMemoryKiller daemon (lmkd) sends a silent kill to reclaim pages.

Neither operating system gives your application time to execute an uncaught exception handler. Traditional crash reporting SDKs register zero crash logs. To your backend and analytics dashboards, the user simply disappeared - or worse, the app restarts cleanly on next launch, disguising catastrophic memory exhaustion as ordinary user session churn.

Here is how modern engineering teams move beyond development-only profilers to implement Production Memory Diagnostics - tracking runaway allocations, memory leaks, and OS termination events without degrading app performance.

Production Memory Diagnostics is the practice of continuously monitoring runtime memory health (Resident Set Size, dirty physical footprint, system memory pressure warnings, and screen-by-screen memory deltas) on live user devices without capturing heavy heap dumps. By correlating memory growth with user journeys and lifecycle events, production diagnostics identify retain cycles, native cache bloat, and LowMemoryKiller (LMK)/Jetsam terminations before they cause silent churn.

The Anatomy of Silent Memory Kills​

To diagnose production memory issues, you must first understand how modern mobile operating systems enforce memory limits and terminate processes.

1. iOS: The Jetsam Mechanism (FOOM)​

iOS devices operate without a traditional disk swap file. To manage memory pressure, iOS relies on memory compression and aggressive process termination enforced by the kernel daemon memorystatus (commonly known as Jetsam).

When an app's dirty physical memory footprint crosses the system limit (which varies by device model and memory tier - often between 1.2GB and 2.2GB on modern iPhones, but as low as 350MB on older iPads or memory-constrained extensions), Jetsam sends an uncatchable SIGKILL.

The Virtual Memory Trap​

Many developers attempt to track memory in Swift by reading task_basic_info.virtual_size. This metric is virtually useless: virtual size includes clean mapped read-only files (such as .dylib frameworks and asset catalogs) that the OS can evict at zero cost.

Jetsam only cares about Physical Footprint - dirty pages that cannot be compressed or paged out.

Low-Overhead Physical Footprint Telemetry in Swift​

To capture actual memory pressure in production, you must query the Mach kernel's task_vm_info:

import Foundation
import MachO

public struct MemoryDiagnostics {

/// Returns the exact physical memory footprint (in Megabytes) tracked by Jetsam.
public static func getPhysicalMemoryFootprintMB() -> Double? {
var vmInfo = task_vm_info_data_t()
var count = mach_msg_type_number_t(MemoryLayout<task_vm_info_data_t>.size / MemoryLayout<integer_t>.size)

let result = withUnsafeMutablePointer(to: &vmInfo) { ptr in
ptr.withMemoryRebound(to: integer_t.self, capacity: Int(count)) { intPtr in
task_info(mach_task_self_, task_flavor_t(TASK_VM_INFO), intPtr, &count)
}
}

guard result == KERN_SUCCESS else { return nil }

// phys_footprint is the exact metric used by iOS Jetsam to trigger kills
let footprintBytes = Double(vmInfo.phys_footprint)
return footprintBytes / (1024.0 * 1024.0)
}

/// Queries remaining memory available to the process before system intervention
public static func getAvailableMemoryMB() -> Double {
return Double(os_proc_available_memory()) / (1024.0 * 1024.0)
}
}

By querying os_proc_available_memory() upon entering critical funnels, your app can dynamically evict caches before Jetsam strikes.

2. Android: The LowMemoryKiller (LMK) & The OOM Paradox​

On Android, memory management is split across two layers:

  1. The ART (Android Runtime) Java/Kotlin Virtual Heap: Subject to garbage collection with an upper limit defined by Runtime.getRuntime().maxMemory().
  2. The Linux Kernel & Native Allocations: Governed by the lmkd daemon, which monitors system-wide RAM and kills processes based on their oom_score_adj priority.

The "Straw That Broke the Camel's Back" Fallacy​

When an Android app crashes with a fatal java.lang.OutOfMemoryError, developers almost always misdiagnose the root cause by staring at the stack trace:

java.lang.OutOfMemoryError: Failed to allocate a 32784 byte allocation with 12582912 free bytes and 12MB until OOM
at java.lang.StringFactory.newStringFromChars(StringFactory.java:328)
at java.lang.StringBuilder.toString(StringBuilder.java:407)
at com.example.app.ui.cart.CartAdapter.onBindViewHolder(CartAdapter.kt:42)

The stack trace points to a 32KB string allocation inside CartAdapter. Developers spend days optimizing StringBuilder usages in the cart screen.

In reality, the cart adapter was blameless. Five minutes earlier, a background image pipeline leaked five 50MB uncompressed Bitmap instances because an Activity failed to unsubscribe from an EventBus. The heap was already at 99.8% capacity; the 32KB string just happened to be the unlucky allocation that tripped the wire.

Production Memory Telemetry in Kotlin​

To monitor real memory consumption and intercept OS warnings before LMK strikes:

package com.appxiom.diagnostics

import android.app.ActivityManager
import android.content.ComponentCallbacks2
import android.content.Context
import android.content.res.Configuration
import android.os.Debug
import android.os.Process

class ProductionMemoryObserver(private val context: Context) : ComponentCallbacks2 {

fun captureMemorySnapshot(): MemorySnapshot {
val runtime = Runtime.getRuntime()
val javaHeapUsedMB = (runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024)
val javaHeapMaxMB = runtime.maxMemory() / (1024 * 1024)

// Native allocation tracking (Bitmaps, C++ libs, OpenGL/Vulkan buffers)
val nativeHeapAllocatedMB = Debug.getNativeHeapAllocatedSize() / (1024 * 1024)

// PSS (Proportional Set Size): The most accurate representation of app footprint
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val memoryInfo = ActivityManager.MemoryInfo()
activityManager.getMemoryInfo(memoryInfo)

return MemorySnapshot(
javaHeapUsedMB = javaHeapUsedMB,
javaHeapMaxMB = javaHeapMaxMB,
nativeHeapAllocatedMB = nativeHeapAllocatedMB,
systemAvailableMemMB = memoryInfo.availMem / (1024 * 1024),
isSystemLowMemory = memoryInfo.lowMemory
)
}

override fun onTrimMemory(level: Int) {
when (level) {
ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {
// The app is running, but device is critically low on memory.
// Evict non-essential bitmap caches immediately.
}
ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> {
// UI moved to background. Free heavy UI resources to lower LMK kill priority.
}
}
}

override fun onConfigurationChanged(newConfig: Configuration) {}
override fun onLowMemory() {
// Legacy callback: system is running out of memory
}
}

data class MemorySnapshot(
val javaHeapUsedMB: Long,
val javaHeapMaxMB: Long,
val nativeHeapAllocatedMB: Long,
val systemAvailableMemMB: Long,
val isSystemLowMemory: Boolean
)

3. Flutter & React Native: The Split-Heap Blindspot​

Cross-platform frameworks introduce a dangerous architectural duality: The VM Heap vs. The Native Graphics Engine.

In Flutter, Dart manages object lifecycle on the Dart VM Heap. However, images, shaders, Skia/Impeller render pipelines, and texture backings live in native C++ memory.

// DANGEROUS: Decodes a 4032x3024 12MB camera photo into raw RGBA memory
// Dart heap allocates only a tiny metadata pointer (~120 bytes)
// Skia / Native heap allocates 4032 * 3024 * 4 bytes = ~48.7 MB of uncompressed bitmap!
Image.network(
"https://cdn.example.com/products/4k_photo.jpg",
width: 150,
height: 150,
)

// SAFE: Resizes bitmap during decode step, saving 95% native RAM
Image.network(
"https://cdn.example.com/products/4k_photo.jpg",
width: 150,
height: 150,
cacheWidth: 300, // 2x density for 150dp
cacheHeight: 300,
)

If a list view displays 30 unconstrained images, the Dart VM reports a healthy 45MB heap, while the native iOS/Android process footprint spikes past 1.4GB. Within seconds, Jetsam or LMK kills the app. A standard Dart crash handler will never capture a stack trace.

Why Traditional Monitoring Tools Fail in Production​

CapabilityLocal Dev Profilers (Xcode / Android Studio)LeakCanaryStandard Crash Reporters (Crashlytics, Sentry)Production Memory Diagnostics (Appxiom)
Run in Production App Store / Play Store❌ No (Debug builds only)❌ Dangerous (5-30s freeze, PII risk)✅ Yes✅ Yes
Zero UI Jitter / Overhead❌ High overhead❌ High pause time✅ Low✅ Zero-overhead passive telemetry
Capture Jetsam / FOOM Kills❌ N/A❌ N/A❌ Misses SIGKILL terminations✅ Correlates last known footprint
Capture Native vs Managed Heap✅ Yes❌ Java heap only❌ None✅ Complete physical footprint
Attribution to User Action⚠️ Manual profiling session❌ Static reference path only❌ Single stack frame at crash✅ Full historical Activity Trail

The Appxiom Solution: Memory Activity Trail​

Diagnosing production memory leaks does not require capturing every allocated byte in RAM. It requires understanding Memory Delta Attribution.

A leak is not defined by high memory usage; it is defined by memory that is allocated during a user journey and fails to reclaim after that journey terminates.

Correlating Memory Deltas with User Journeys​

The Appxiom SDK records lightweight memory snapshots attached to each discrete step in the Activity Trail. By comparing memory footprint across screen transitions, network invocations, and user interactions, Appxiom calculates the exact memory delta ($\Delta$) per step.

Activity Trail Trace: Diagnosing a Retain Cycle in Production​

Consider this telemetry log captured by Appxiom immediately before an iOS Jetsam event:

TimestampScreen / Lifecycle EventAction / PayloadPhysical RAMMemory Delta ($\Delta$)Status
14:22:01.100AppLaunchCold start from springboard98 MB+98 MBNormal
14:22:04.250HomeScreenFeed fetch (GET /v2/feed - 200)132 MB+34 MBNormal
14:22:15.800CatalogScreenGrid populated (48 products)168 MB+36 MBNormal
14:22:28.400ARProductViewerCamera initialized + 3D model load495 MB+327 MBHigh Allocation
14:22:42.100HomeScreenUser navigates BACK to Home490 MB-5 MB (LEAK ALERT)CRITICAL
14:22:49.000CartScreenUser taps Cart icon510 MB+20 MBWarning
14:22:53.600CheckoutScreenTap "Pay Now"542 MB+32 MBJETSAM KILL

The Root Cause Revealed​

Without Appxiom, your team would be looking at an unhandled session termination during checkout. You might assume the payment gateway SDK crashed.

With the Appxiom Activity Trail, the bug is obvious in five seconds:

  1. When the user entered ARProductViewer, RAM jumped from 168 MB to 495 MB (+327 MB).
  2. When the user navigated back to HomeScreen, memory should have dropped back toward 168 MB. Instead, memory remained pinned at 490 MB (only -5 MB reclaimed).
  3. The ARProductViewer controller or its Metal texture buffers had a strong retain cycle (e.g., an uncancelled closure capture or unreleased camera delegate), keeping 320MB of native resources permanently alive in background memory.
  4. When the user reached Checkout, the normal memory requirement of the payment sheet pushed the app over the device's Jetsam threshold.

Connecting Memory Diagnostics to Business Health​

Memory diagnostics is not merely an infrastructure concern; it directly impacts revenue and customer retention.

1. Goal Friction Impact (GFI)​

When an app terminates due to an OOM during an onboarding or checkout flow, users rarely re-enter payment credentials - they abandon the transaction. Appxiom's Goal Friction Impact (GFI) quantifies the precise financial loss attributable to memory degradation, allowing engineering leads to prioritize memory fixes with hard ROI data.

2. Quality Score (QS)​

Rather than relying solely on the deceptive crash-free rate, Appxiom calculates a holistic Quality Score (QS). Devices experiencing excessive memory pressure warnings (TRIM_MEMORY_RUNNING_CRITICAL or didReceiveMemoryWarningNotification) trigger a score penalty, giving you an early warning system days before crashes escalate.

3. Version Analytics​

During staged rollouts, Appxiom's Version Analytics compares memory distributions across app versions:

  • v2.4.0: 90th percentile physical footprint = 185 MB.
  • v2.5.0: 90th percentile physical footprint = 340 MB (+83% regression detected in first 2% rollout).

You can halt the release before the regression compromises millions of devices.

Actionable Production Memory Hardening Checklist​

Follow these engineering best practices to eliminate memory leaks and build resilience against LMK and Jetsam:

iOS (Swift / SwiftUI)​

  • Enforce Weak Capture in Async Closures: Audit all Task { [weak self] in ... } blocks and delegates to prevent retain cycles holding view controllers in memory.
  • Drain Autorelease Pools in Heavy Loops: Wrap batch data transforms or file processing in autoreleasepool { ... } so intermediate objects are reclaimed immediately.
  • Listen to Memory Pressure: Implement UIApplication.didReceiveMemoryWarningNotification to flush image memory caches and clear transient view states.
  • Audit Background Tasks: Ensure background tasks end promptly with UIApplication.shared.endBackgroundTask(). Pinned background allocations are the #1 trigger for Jetsam priority kills.

Android (Kotlin / Jetpack Compose)​

  • Observe ComponentCallbacks2: Evict Glide/Coil bitmap caches upon receiving TRIM_MEMORY_RUNNING_CRITICAL or TRIM_MEMORY_RUNNING_LOW.
  • Beware of Static View/Context References: Never store a Context or View in a companion object, singleton, or long-lived coroutine scope.
  • Bind Coroutines to Lifecycle: Use viewLifecycleOwner.lifecycleScope in Fragments and rememberCoroutineScope() in Compose to ensure allocations cancel with UI teardown.
  • Optimize Large Bitmaps: Always decode images using BitmapFactory.Options.inSampleSize or rely on modern vector drawables and hardware-accelerated rendering.

Cross-Platform (Flutter / React Native)​

  • Constrain Image Cache Dimensions: Never use unconstrained Image.network() without specifying cacheWidth and cacheHeight.
  • Dispose Controllers Explicitly: Always invoke .dispose() on AnimationController, TextEditingController, FocusNode, and ScrollController in the widget dispose() lifecycle.
  • Verify Native Bridge Cleanup: Ensure custom native platform channels (MethodChannels) release native listeners and texture handles when widgets unmount.

Eliminate Memory Blindspots with Appxiom​

You cannot fix what you cannot measure, and you cannot debug production memory with development-only profilers.

By combining low-overhead physical footprint telemetry with the Appxiom Activity Trail, your engineering team gains complete visibility into every memory spike, retain cycle, and silent OOM kill across iOS, Android, and Flutter.

Stop losing users to invisible memory kills. Explore Appxiom's Activity Trail or start monitoring your mobile applications today.

Silent Failures, ANRs, and App Hangs: How to Detect and Fix the Bugs That Never Crash

Published: · 15 min read
Don Peter
Cofounder and CTO, Appxiom

Your crash reporting dashboard displays a pristine 99.9% crash-free session rate.

Your engineering team celebrates a successful deployment. The release metrics look flawless. But on the App Store and Google Play, your reviews tell a completely different story:

"The app freezes every time I tap the checkout button. Completely unusable."
"It just spins forever on the login screen. Had to force close and uninstall."
"Buttons become unresponsive after opening my transaction history."

Meanwhile, your product manager pulls up analytics: checkout funnel completion has plummeted by 22%, and customer churn is spiking. Yet your error monitor reports zero fatal crashes.

How can an application be catastrophic for users while registering as 100% healthy on your APM dashboard?

Welcome to the blind spot of modern software monitoring: Silent Failures, Application Not Responding (ANR) events, and iOS App Hangs.

The Dangerous Illusion of the Crash-Free Rate​

For more than a decade, mobile engineering teams have used Crash-Free Session Rate (CFSR) as their primary release quality benchmark.

The flaw with CFSR is structural: it measures process survivability, not user experience.

When a mobile app freezes, the process is still running. When an API call fails and leaves a progress indicator spinning indefinitely, no unhandled exception is thrown. When a user force-closes the app in frustration, the operating system registers a user termination, not a crash.

To your crash logger, that session was a victory. To your user, it was an infuriating failure.

Deconstructing the Three Horsemen of Silent Decay​

To eliminate silent failures, engineering teams must understand the three distinct technical mechanisms that cause them:

1. Android ANRs (Application Not Responding)​

On Android, the operating system monitors UI responsiveness via the system server. If your application cannot process input events or lifecycle callbacks within designated timeframes, Android displays the dreaded Application Not Responding (ANR) dialog:

app is not responding

Close app
Wait

What Triggers an ANR?​

  • Input Dispatching Timeout: The app fails to respond to an input event (e.g., key press or screen touch) within 5 seconds.
  • BroadcastReceiver Timeout: A BroadcastReceiver fails to finish executing within 10 seconds in foreground mode.
  • Service Execution Timeout: A Service executed by the foreground app fails to complete its lifecycle tasks within 20 seconds.

The Business Penalty: Google Play Vitals​

Google Play monitors your app's User-Perceived ANR Rate (ANRs occurring when your app is in the foreground). If your ANR rate exceeds Google's bad behavior threshold of 0.47% overall (or 8% on specific phone models):

  • Google demotes your app in Play Store search rankings.
  • Your app is disqualified from store featuring and promotional carousels.
  • Google Play displays a warning directly on your store listing: "Recent data shows that this app may stop working on your device."

2. iOS App Hangs​

iOS does not display an ANR dialog. Instead, when the main run loop is blocked, the interface simply stops accepting touch input. Tapping buttons produces no ripple effect, scrolling locks up, and animations stutter to a halt.

What Triggers an App Hang?​

The iOS main thread operates as an event run loop (CFRunLoop). When an operation performs synchronous disk I/O, heavy computation, or waits on a dispatch semaphore on the main thread, the run loop cannot process new touch events.

  • Micro-Hangs (250ms – 500ms): Perceived by users as "jank," dropped frames, or sluggishness.
  • Severe Hangs (> 2,000ms): Users perceive the app as completely frozen. Over 70% of users will force-quit an app if an interaction takes longer than 3 seconds without visual feedback.

Apple Xcode Organizer Telemetry​

Apple tracks your Hang Rate (measured as milliseconds of hang time per hour of active use). High hang rates directly suppress your visibility in App Store search results and elevate user churn.

3. Silent Functional Failures & Swallowed Exceptions​

Unlike ANRs and App Hangs, silent functional failures often involve zero thread contention. Instead, they are caused by defensive programming gone wrong:

// The Classic "Silent Killer" Pattern:
try {
val response = paymentApi.confirmPurchase(orderId)
updateUiWithReceipt(response)
} catch (e: Exception) {
// Exception caught and logged to console or an unmonitored analytics tag
Log.e("Payment", "Purchase failed", e)
// The spinner continues to animate, or the screen never transitions!
}

The network request times out, or the backend returns a 503 Service Unavailable. Because the exception was caught, no crash occurred. But the user is left looking at an unresponsive checkout button. They tap it three more times, get no feedback, and leave your app forever.

Why Traditional APMs Fail to Diagnose ANRs and Hangs​

Most engineering teams rely on crash loggers to debug ANRs. When an ANR occurs, these tools capture a point-in-time thread dump. But in production, these thread dumps are notoriously misleading:

1. The "Innocent Bystander" Stack Trace​

When an operating system triggers an ANR dump at second 5, the thread dump records whatever code happens to be executing at that exact millisecond. Frequently, this is an innocent operating system wait call:

"main" prio=5 tid=1 Native
#00 pc 000000000004b344 /apex/com.android.runtime/lib64/bionic/libc.so (syscall+28)
#01 pc 00000000001ae4f8 /apex/com.android.art/lib64/libart.so (art::ConditionVariable::WaitHoldingLocks)

The stack trace tells you the main thread is waiting for a lock. But which thread holds the lock? And what action caused the lock to be acquired in the first place? The thread dump cannot answer either question.

2. Lack of Preceding User Interaction Context​

Did the user tap a filter button? Did they background the app and return? Did their network connection drop from Wi-Fi to cellular right before the freeze? Standard thread dumps provide zero historical sequence.

3. No Correlation to Business Impact​

If an ANR occurs during app startup on an obsolete Android 8 device, it is an annoyance. If an App Hang occurs every time a high-value customer taps "Complete Purchase" on iOS 18, it is a business catastrophe. Traditional tools treat both events as isolated technical entries.

How Appxiom Solves Silent Failures: Continuous Forensics​

Appxiom tackles silent failures by bridging the gap between technical thread execution and user experience:

1. Proactive Main-Thread Watchdogs​

Appxiom continuously monitors UI thread responsiveness and runloop latency. If a task stalls the UI thread for longer than your defined threshold, Appxiom captures the event before the user force-closes the application.

2. The Activity Trail: Reconstructing the Crime Scene​

Instead of an isolated thread dump, Appxiom attaches the complete Activity Trail to every ANR and App Hang report:

APPXIOM ANR REPORT

Issue: ANR Detected (Main thread blocked for 5,240ms)
Version: v3.4.1 (Build 92)  |  Device: Samsung Galaxy S23  |  OS: Android 14

TIME

TYPE

DETAILS

FREE MEM

14:10:02.115LIFECYCLEOrdersActivity:onResume52.1%
14:10:03.450USER ACTIONClicked "Sync Offline Orders"51.8%
14:10:03.480CUSTOMlocal_db_query_started51.8%
14:10:03.490HTTPPOST /api/v2/orders/sync → 200 OK (310ms)51.2%
14:10:03.810CUSTOMparsing_5000_json_items49.5%
14:10:08.730ISSUE

ANR: Main thread stalled during JSON parsing for 5,240ms

48.9%

What this tells the engineer:​

In 15 seconds, the developer sees what happened:

  1. The user tapped 'Sync Offline Orders'.
  2. The network request succeeded quickly (200 OK in 310ms).
  3. The response contained 5,000 JSON items.
  4. The JSON parsing and database insertion were executed synchronously on the main thread instead of offloaded to a background coroutine or thread pool.
  5. The UI froze for 5.2 seconds, triggering the ANR.

3. Goal Friction Impact (GFI)​

Through Goal Friction Impact, Appxiom correlates silent ANRs and hangs directly to user drop-offs in critical funnels (such as checkout, signup, or subscription).

If a silent freeze occurs in a settings menu, its GFI score is low (e.g., 1.2/10). If an App Hang occurs during credit card validation, its GFI spikes to 9.6/10, immediately alerting your team to a revenue-critical blocker.

4. Appxiom Quality Score (QS)​

Unlike binary crash-free rates, the Appxiom Quality Score evaluates version health on a unified 0–10 scale that penalizes ANRs, App Hangs, and slow screen loads alongside fatal crashes.

Technical Best Practices: Preventing Main-Thread Freezes​

Eliminating ANRs and App Hangs requires architectural discipline across your codebase. Here are the core patterns to enforce:

Android: Moving Work Off the Main Thread​

1. Enforce Background Dispatchers for I/O​

Never perform disk reads, SharedPreferences writes, Room queries, or network operations on Dispatchers.Main:

// BAD: Runs on Main Thread, causes ANR under heavy DB load
fun loadUserTransactions() {
val transactions = database.transactionDao().getAll() // BLOCKS UI THREAD
updateUi(transactions)
}

// GOOD: Offloaded to Dispatchers.IO with Kotlin Coroutines
fun loadUserTransactions() = viewModelScope.launch {
val transactions = withContext(Dispatchers.IO) {
database.transactionDao().getAll()
}
updateUi(transactions)
}

2. Enable StrictMode in Debug Builds​

Catch disk and network violations during development before they ever reach production:

if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build()
)
}

iOS: Eliminating Runloop Blocking in Swift​

1. Leverage Swift 6 Concurrency & Actor Isolation​

Ensure heavy computations are decoupled from the @MainActor:

// BAD: Synchronous heavy parsing on the main actor
@MainActor
func processOrderHistory(data: Data) {
let decoder = JSONDecoder()
// Heavy JSON parsing locks the main runloop
let orders = try? decoder.decode([Order].self, from: data)
self.displayOrders(orders)
}

// GOOD: Background task processing
@MainActor
func processOrderHistory(data: Data) async {
let orders = await Task.detached(priority: .userInitiated) {
let decoder = JSONDecoder()
return try? decoder.decode([Order].self, from: data)
}.value

self.displayOrders(orders)
}

2. Avoid Synchronous Semaphore Waits on the Main Queue​

Never call dispatch_semaphore_wait or DispatchGroup.wait() on the main thread while waiting for a background completion handler. If the background thread requires main-thread access to complete, your app enters an immediate deadlock.

Flutter: Isolates for Heavy Computation​

In Flutter, the Dart engine runs on a single event loop. If you perform heavy operations on that loop, you freeze the UI across both Android and iOS simultaneously:

// BAD: Blocks Flutter root isolate
void parseLargeJson(String rawJson) {
final data = jsonDecode(rawJson); // Drops frames or triggers ANR
setState(() => items = data);
}

// GOOD: Offload to background Isolate using compute()
Future<void> parseLargeJson(String rawJson) async {
final data = await compute(jsonDecode, rawJson);
setState(() => items = data);
}

Comparison: Fatal Crash Monitoring vs. Appxiom Observability​

CapabilityTraditional Crash Loggers (Crashlytics, Bugsnag)Appxiom Performance & Error Platform
Fatal Exception Tracking✅ Yes✅ Yes
Android ANR Detection⚠️ Post-mortem thread dumps only✅ Real-time watchdog + Activity Trail
iOS App Hang Detection❌ Limited or omitted✅ Micro-hang & severe hang tracking
Silent API & State Failures❌ Omitted (process stays alive)✅ Automatic 4xx/5xx & flow block detection
User Interaction Footsteps❌ Generic breadcrumbs✅ Auto-tracked taps, gestures & lifecycles
Business Impact Metric❌ Occurrence count only✅ Goal Friction Impact (0–10 GFI Score)
Holistic Version Quality❌ Binary Crash-Free Rate✅ Appxiom Quality Score (0–10 QS)

The End of Invisible Bugs​

A crash that affects 10 users in an obscure background service is easy to spot on your dashboard.

An App Hang that freezes 5,000 customers during checkout is far more devastating—yet traditional monitoring platforms let it slip by in complete silence.

Stop relying on vanity metrics that disguise broken user experiences as healthy releases.

With Appxiom, you detect the failures that matter most:

  • Pinpoint the exact line of code causing your ANRs and App Hangs.
  • Retrace the user footsteps that led to the freeze.
  • Protect your App Store rankings and Google Play Vitals.
  • Fix release regressions before they cost you revenue.

Stop Letting Silent Failures Drain Your Revenue​

See how Appxiom gives mobile engineering teams the clarity they need to eliminate ANRs, App Hangs, and invisible regressions.

Start Your Free Appxiom Trial Today or explore our types of bugs detected in mobile and web apps.

State of Bugs in Mobile Apps - Appxiom Report for Year 2020

Published: · Last updated: · 2 min read
Robin Alex Panicker
Cofounder and CPO, Appxiom

Year 2020 started with the fear of pandemic induced economic recession. But it seems technology domain was less unaffected than expected. Reason for this is the increased dependency on technology. This increase in demand reflected in the number of bug reports Appxiom handled in 2020. Appxiom captured 464 Million bug reports which is 45 times the count for 2019.

We analysed the bug types to get an understanding about the common issues reported in mobile apps. We decided to share this data to help developers to focus more on such issues and to improve the quality of mobile apps.

Bug stats for iOS apps​

​

In 2020 Appxiom captured 138 Million bug reports from 624 iOS apps built on Objective C or Swift. This is from an install base of 1.3M devices.

API related bugs are the most common bugs in iOS and contribute 42.9% of all issues reported, followed by memory leaks at 25%. Crashes contribute 14.3% of bugs. Frame skips came next at 9.5%.

Bugs in iOS apps

Bug stats for Android apps​

​

Appxiom captured 324 Million bug reports in 2020 from 1853 android apps built on Java or Kotlin. Total install base is 2.1M devices.

Memory leaks are the most common bugs in android and contribute 29.7% of all issues reported, followed by screen load delays at 25.4%. Crashes contribute 23.7%% of bugs. API bugs are at 9.5%.

Bugs in Android apps

Conclusion​

​

While we do not claim this to be an exact representation of state of mobile app bugs across millions of apps out there, this data has some clear indicators. Higher density of memory related bugs is a major concern, and one of the reasons is that a good number of the memory leaks go unnoticed during development phase. API bugs and crashes are issues that could be identified before going in production, but looks like a not so small number of them make it to live apps.

Handling Bugs That Can Be Anticipated in Android Apps.

Published: · Last updated: · 3 min read
Don Peter
Cofounder and CTO, Appxiom

Let us consider a scenario where your app user initiates a payment process in your Android app. You must have an integrated payment processing platform like Stripe in your app. The outcome of the transaction can result in either success or failure. In a good number of such cases, you already know situations in the code base that can fail.

public class MyPaymentSessionListenerimplements 
PaymentSession.PaymentSessionListener {

// Called whenever the PaymentSession's data changes.
@Override
public void onPaymentSessionDataChanged(@NonNull
PaymentSessionData data) {
}

// Update UI to indicate that network communication is in
// progress or complete.
@Override
public void onCommunicatingStateChanged(boolean
isCommunicating) {
}

// Error callback which should be reported
// using a bug reporting tool.
@Override
public void onError(int errorCode, @NotNull String
errorMessage) {
}
}

Code snippet | Response callbacks from Stripe payment processing platform​

If these issues that can be anticipated are unreported, it will surely make your heads scratch. So you rely on bug reporting tools like Bugsnag, Firebase or Appxiom to make your lives easier.

How to identify the right tool?​

Even though developers know the location of these issues beforehand, a detailed bug report with relevant data points will help us identify the root cause and fix them fast.

Ability to set a textual description for the issue.​

First we need a basic API to report such issues with a title and a detailed description of the issue.

API.collectIssue("Issue Title", "Detailed Issue Description")

Ability to collect device information​

As Android ecosystem is highly fragmented, the need to get in depth device specific information is important. Bug reporting tool that you choose should be able to collect device information like brand and model of the device, screen resolution and much more to aid developers in fixing the issue.

Identifying how your users were interacting with the app.​

As these issues are generic, an ideal way to figure out the root cause is to know how the user was interacting with the app. For that, we need to be aware of the list of user events that happened before the issue.

​

How Appxiom handles such anticipated issues​

This is where the activity trail in Appxiom comes in handy. It is a chronologically ordered list of events that occurred a minute prior to the issue.

By integrating a bug reporting tool like Appxiom, you will be able to report such anticipated failure scenarios with the help of a single line Ax.reportIssue() API.

Ax.reportIssue(context, "Payment Failed", reason);

All bug reports generated by Appxiom will have relevant data points like textual description of the issue, activity trail along with device and device state information.

Activity trail (app events) captured by AppxiomDevice information captured by AppxiomFor more information, visit the documentation page for Android (Java & Kotlin), iOS (Objective-C & Swift) and watchOS (Objective-C & Swift) platforms.

Visit appxiom.com to register with Appxiom. Detailed documentation is available at docs.appxiom.com.

How to Detect and Report Bugs in Android Apps

Published: · Last updated: · 6 min read
Don Peter
Cofounder and CTO, Appxiom

One of the challenges mobile app developers face is collecting as much relevant data to reproduce bugs reported during testing and more so production. In production even detecting bug occurrences becomes a challenge. Manual bug detection and data collection in Android mobile apps during testing and development environment is time consuming and in production it is near impossible. Here is where tools like Appxiom aid the developers.

Appxiom is an automated, light-weight bug reporting SDK for Android apps that helps developers to reduce the bug fixing time by providing clear and crisp issue reports with detailed data points for effective root cause analysis.

First things first, Appxiom has an easy three step integration process.

  • Register with Appxiom using the ‘Get Started’ button in https://appxiom.com and login to the dashboard. 

  • Use ‘Add App’ to link your Android app to Appxiom.

  • Integrate Appxiom SDK to your app as a gradle library.

For a more detailed integration guide, visit our documentation pages for Android (Java & Kotlin).

Appxiom is capable of reporting multiple bug types in Android apps. Some of the main types of bugs in Android apps are,

Types of Issues captured and reported by Appxiom​

​

Memory Leaks​

​

Memory Leaks occur in Android apps when unused objects are not cleared from the device’s memory. Appxiom SDK has out of the box capability to detect memory leaks in Android apps. and reports it to the dashboard with data points like class stack, activity trail, device and device state info. 

Memory Leak in Android

Crashes​

​

App Crashes are fatal and will affect the user experience, which may lead to user’s uninstalling the app. 

App Crash in AndroidWith basic integration, Appxiom SDK is capable of reporting crashes along with data points like device and device state data, activity trail and full stack trace.

​

API Failures​

With a single line of code, Appxiom SDK is capable of reporting API issues like status code errors, delayed API call executions or even duplicate API calls. Appxiom reports API failures with detailed data points including network parameters like full URL, request-response headers and request body. 

API Issue in Android

Abnormal memory usage​

​

When an app consumes a high amount of device’s memory, Appxiom SDK immediately reports it. Activity trail in issue report will enable developers to identify the reason for the spike in memory usage.

Abnormal Memory Usage in Android

Function failures​

​

Issues in code level functions like unexpected return value, delay in function execution are called function failures. Appxiom SDK provides a single line API to track code level function failures. Data points like function parameters are sent along with each issue report with will aid developers in fixing the issue fast.

Function Failure in Android

Feature failures​

​

Using the function tracking API, SDK allows developers to chain multiple functions. Once chained Appxiom will monitor if the second function is executed within the stipulated time (can be set by the developer) after the execution of the first function. If not, then Appxiom SDK reports it as an issue. This will enable developers to report any app feature failures.

Feature Failure in AndroidBug reports will include data points like activity trail, function parameters and return values.

​

Custom issues​

​

Appxiom SDK provides a single line API for developers to report any custom issues. 

Custom Issue report in Android

ANR​

​

ANR or App Not Responding occurs due to frame rate issues in Android, that block the UI thread for more than 5 seconds. By tracing the Activity trail events, developers can reproduce and can fix ANR issues fast. 

ANR report in Android

Screen loading delay​

​

Delay in loading of any activity or fragment will be automatically reported by Appxiom SDK. 

Screen Loading delay in AndroidNow, let us take a look at the data points in bug reports of Appxiom that help developers to reproduce the bugs effectively. 

Data Points Explained​

​

Error insights​

​

Error insights help the developers to find commonalities across occurrences for faster root cause analysis. Error insights consist of information like bandwidth strength, battery level, low memory warning, OS versions, device names, country name and timezone offsets. 

Error Insights from an issue report in Android

Activity Trail​

​

Activity trail is a list of chronologically ordered app events, that helps developers to identify where the issue occurred. It consists of three components, 

  • The Activity segment, that has the details of the app event.

  • The timestamp segment, that has time at which the event got recorded.

  • The memory segment, that shows the percentage of free memory.

Activity-trail from an issue report in Android

Stack trace​

​

Stack trace helps the developers to track the exact line of code where the crash occurred.

Stack trace from an issue report in Android

Network Parameters​

Network parameters provide in-depth information on network request and response data. Network parameters will have request headers, request body, request body size, response headers, status code and response body size. 

Network parameters from an issue report in Android

Device & Device State info​

​

Device & Device state info gives the developer information about the device and the state of the device at the time of bug occurrence.

Device and Device State data from an issue report in Android

Function Parameters​

​

Function parameters section of data points will contain parameter value and data type of all the parameters passed to the function being tracked during runtime.

Function parameters from an issue report in Android

Data Security & Data Privacy​

​

Appxiom SDK is architectured in such a way that all our app users remain anonymous to the system as we do not collect any PII which ensures data privacy. All data collected, sent and stored by Appxiom are secured with end to end encryption. Appxiom is also GDPR compliant.

Call back mechanism​

​

For every issue that is raised by the Appxiom SDK, a callback function will be triggered, which allows developers to handle the issue gracefully. You can read more about the same in our Java and Kotlin documentation pages.

Appxiom is also available as Objective-C and Swift frameworks for iOS and watchOS. Visit appxiom.com to know more about Appxiom Detailed documentation is available at docs.appxiom.com.