Quick answer: Mobile games capture crashes using a combination of platform-provided crash reporters and in-app signal handlers. On iOS, crashes are captured by the system and reported through App Store Connect or third-party SDKs.

This guide covers mobile game crash reporting best practices in detail. Mobile games crash. They crash because the OS killed them for using too much memory. They crash because the device overheated and throttled the GPU mid-frame. They crash because a player on a five-year-old Android phone with 2 GB of RAM tried to load a level designed for 6 GB. The difference between a mobile game that maintains a 4.5-star rating and one that drops to 3 stars is not whether it crashes — it is whether the developers know about the crashes and fix them fast. That starts with solid crash reporting.

Why Mobile Crash Reporting Is Different

Mobile crash reporting faces constraints that desktop games do not. Battery life matters: a crash reporter that aggressively uploads data over cellular will drain the battery and use the player’s data plan. Network availability is intermittent: a crash that happens on the subway cannot be reported until the player has connectivity again. And the OS is actively hostile to background processes: if your crash handler takes too long, iOS will terminate it before it finishes writing the crash data.

Additionally, mobile games are killed by the OS far more often than they crash from bugs. An out-of-memory (OOM) kill looks like a crash to the player, but it does not generate a crash dump because the OS simply terminates the process without calling any signal handlers. Detecting and reporting these silent kills requires a different approach than traditional crash handling.

Capturing Native and Managed Crashes

Mobile games built with Unity, Unreal, or custom engines have both native code (C/C++/Objective-C) and managed code (C#/Java/Kotlin). Each layer crashes differently and requires its own handler.

Native crashes on iOS are caught by the Mach exception handler or Unix signal handler. On Android, they are caught by signal handlers registered for SIGSEGV, SIGBUS, SIGFPE, and SIGABRT. These handlers must be minimal — you are running inside a crashed process, and any allocation or complex operation can deadlock or double-fault.

// Android native crash handler (C++)
// Register this early in your app's JNI_OnLoad

#include <signal.h>
#include <unistd.h>

static struct sigaction old_handlers[32];
static char crash_buffer[4096];
static int crash_fd = -1;

static void crash_signal_handler(int sig, siginfo_t* info,
    void* context)
{
    // Write minimal crash data to pre-opened file descriptor
    // No heap allocation allowed here
    int len = snprintf(crash_buffer, sizeof(crash_buffer),
        "CRASH sig=%d addr=%p\n", sig, info->si_addr);
    write(crash_fd, crash_buffer, len);

    // Write breadcrumb ring buffer
    write(crash_fd, g_breadcrumbs, g_breadcrumb_size);
    fsync(crash_fd);

    // Re-raise with default handler to generate tombstone
    sigaction(sig, &old_handlers[sig], NULL);
    raise(sig);
}

void install_crash_handler(const char* crash_file_path)
{
    crash_fd = open(crash_file_path,
        O_WRONLY | O_CREAT | O_TRUNC, 0644);

    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_sigaction = crash_signal_handler;
    sa.sa_flags = SA_SIGINFO;

    int signals[] = {SIGSEGV, SIGBUS, SIGFPE, SIGABRT};
    for (int i = 0; i < 4; i++)
    {
        sigaction(signals[i], &sa, &old_handlers[signals[i]]);
    }
}

Managed exceptions in C# (Unity) or Java/Kotlin (Android) are simpler to catch. In Unity, register a handler on Application.logMessageReceived that captures exceptions. On Android, set a Thread.UncaughtExceptionHandler that writes the stack trace and exception message to a file before the process terminates.

Detecting Out-of-Memory Kills

OOM kills are the most common cause of mobile game “crashes” and the hardest to detect. When the OS kills your process for exceeding the memory budget, no crash handler runs. The process simply stops existing. Your crash reporting SDK sees nothing.

The standard detection technique is a sentinel file. At app startup, create a file called session_active. On clean shutdown (the player exits normally), delete the file. On the next launch, check for the file. If it exists, the previous session ended abnormally. Check whether a crash file was also written — if not, the termination was likely an OOM kill or a system-level kill.

// Unity OOM detection via sentinel file
public class OOMDetector : MonoBehaviour
{
    private string _sentinelPath;

    void Awake()
    {
        _sentinelPath = Path.Combine(
            Application.persistentDataPath, "session_active");

        if (File.Exists(_sentinelPath))
        {
            bool hasCrashFile = File.Exists(
                Path.Combine(Application.persistentDataPath, "crash.log"));

            if (!hasCrashFile)
            {
                // Previous session was killed without a crash
                // Likely OOM or system kill
                ReportSuspectedOOM();
            }
        }

        // Mark this session as active
        File.WriteAllText(_sentinelPath, DateTime.UtcNow.ToString("o"));
    }

    void OnApplicationQuit()
    {
        // Clean shutdown: remove sentinel
        if (File.Exists(_sentinelPath))
            File.Delete(_sentinelPath);
    }
}

Combine sentinel detection with memory usage logging. Periodically record the current memory usage to a file. If the session ended abnormally and the last recorded memory usage was near the device’s limit, you can be reasonably confident it was an OOM kill. This data helps prioritize memory optimization for the devices that are running out.

Battery and Bandwidth Considerations

A crash report that drains the player’s battery or consumes their mobile data creates a worse experience than the crash itself. Design your upload strategy with these constraints in mind.

Store crash data locally when the crash occurs. On the next launch, check the network and power state before uploading. If the device is on WiFi and charging, upload immediately. If on cellular, defer until WiFi is available or until the player explicitly opens the game again. If the battery is below 20%, defer entirely.

Keep crash payloads small. A structured JSON report with stack trace, breadcrumbs, and device metadata should be under 50 KB. Compress it with gzip before upload. Do not attach full memory dumps from mobile devices — they are too large and contain too much noise. The stack trace, breadcrumbs, and device info are sufficient for 95% of mobile crash investigations.

Store Compliance and Privacy

Both Apple and Google have policies about crash data collection that you must follow. Apple requires that any crash reporting data collection be disclosed in your App Privacy details. Google requires disclosure through the Data Safety section. If you collect device identifiers (even anonymized ones), state this clearly.

Do not collect data that can identify individual players unless they consent. Device model, OS version, and app version are fine. IDFA, GAID, or email addresses require explicit opt-in under both GDPR and the platforms’ own policies. When in doubt, hash identifiers and avoid storing raw personal data in crash reports.

Both stores also monitor your app’s crash rate. Apps with crash-free session rates below 99% may be deprioritized in store search results. Apple’s App Store Connect and Google’s Play Console both provide crash analytics dashboards — monitor these alongside your own crash reporting to ensure nothing is being missed.

“On mobile, the crashes you cannot detect are worse than the ones you can. OOM kills account for more player-visible crashes than actual exceptions, and they leave no trace unless you build detection yourself.”

Symbol Management for Mobile Builds

Mobile builds strip symbols by default for binary size. Without symbols, crash reports show addresses instead of function names. Upload dSYM files for every iOS build through App Store Connect or your crash reporting dashboard. For Android, upload ProGuard/R8 mapping files and native symbol tables (the unstripped .so files).

Automate this in your CI pipeline. Every build that could reach a player — TestFlight, internal testing track, or production — must have symbols archived and uploaded. Tag each symbol set with the exact build version and commit hash so you can match symbols to crash reports months later.

Related Issues

For mobile-specific error logging techniques, see our guide on how to set up error logging for mobile games. For automated bug reporting from mobile SDKs, read automated bug reporting for mobile games. For general crash reporting setup, check how to set up crash reporting for indie games.

Track your crash-free session rate weekly. If it drops below 99.5%, stop feature work and fix crashes. A stable game with fewer features will always outperform a feature-rich game that crashes on every tenth session.