An Android game that closes with no error logs often did not crash in the sense a crash reporter understands. The process was ended by the system, and a process the system ends does not get the chance to report on itself. Android's games memory guidance states the gap directly: if your game is terminated in the background or killed because it violated individual process memory limits, standard Java or native crash dump mechanisms such as Firebase Crashlytics don't register the event.
The fix for the blind spot is not a different crash reporter. Android keeps its own record of how each recent process run ended, and your game can read that record the next time it starts. ApplicationExitInfo is available on Android 11 (API level 30) or higher and provides information about the reason for application exit, including ANRs, low memory, app crashes, excessive CPU usage, user interruptions, system interruptions, and runtime permission changes.
The order that works is read the exit reason first, then branch on what it says. Everything below assumes you have not yet done that, because the reason changes which of the remaining sections applies to you.
Why Some Android Game Crashes Leave No Logs
Crash reporters record what happens inside a process. On Android, crash handling for Unity applications works as a chain of crash handlers, with Unity acting as the first crash handler in the chain by default; Unity processes crashes and forwards them to the next handler. An unhandled C# exception or a native signal travels that chain, and something on it writes the report you later read.
A kill from outside the process does not enter that chain at all. The system reclaims the process, and the code that would have written a report is gone at the moment there is something to report. From the game's point of view there is no failure to observe, which is why the resulting silence looks identical to a crash reporter that was never configured.
The low memory killer daemon monitors the memory state of a running Android system and reacts to high memory pressure by killing the least essential processes. Background apps are killed first and system processes last. When the game being killed is the one on screen, the same page notes that to the end user it looks like the app crashed, often bypassing standard lifecycle state-saving mechanisms and resulting in lost user progress.
How to Find Out Why Your Android Game Closed
Call ActivityManager.getHistoricalProcessExitReasons() at startup to retrieve the exit reasons of recent sessions. Unity exposes the same records through its own wrapper, ApplicationExitInfoProvider.GetHistoricalProcessExitInfo(string packageName, int pid, int maxNum), which returns an array of records sorted by date.
using UnityEngine;
using UnityEngine.Android;
// pid: process ID
// maxNum: maximum number of records to return
IApplicationExitInfo[] records =
ApplicationExitInfoProvider.GetHistoricalProcessExitInfo(Application.identifier, pid, maxNum);
foreach (IApplicationExitInfo record in records)
{
Debug.Log($"exit reason {record.reason}: {record.description}");
}
Every property on IApplicationExitInfo mirrors the matching Android getter, so reason, description, status, pss, rss and timestamp carry whatever the platform put there. Logging reason alongside description is more useful than branching on the numeric value, because the description field carries the cause the system supplied in its own words.
The reasons below are the ones worth routing on. The descriptions come from the platform source for ApplicationExitInfo, except REASON_MEMORY_LIMITER, which is described in Android's games memory guidance. The full constant list lives in the API reference.
| Exit reason | What it means | Recorded by crash reporters? | Where to look next |
|---|---|---|---|
REASON_LOW_MEMORY |
Killed by the system low memory killer, meaning the system was under memory pressure at the time of the kill | Not when the game was terminated in the background | LMK rate in Play Console; peak memory per device tier |
REASON_MEMORY_LIMITER |
Exceeded the cgroup memory limit (RssAnon + VmSwap) assigned by the platform's Memory Limiter | No | Your own memory ceiling on Android 17 and higher |
REASON_SIGNALED |
Died as the result of an OS signal; status specifies the signal number |
— | The signal number — SIGKILL here can be a memory kill in disguise |
REASON_CRASH_NATIVE |
Died because of a native code crash | — | Tombstones and a symbolized stack; the signal handler chain |
REASON_ANR |
Killed due to being unresponsive | — | ANR traces on the device; the ANR rate in Android vitals |
REASON_EXIT_SELF |
Exited normally by itself, for example via System.exit; status specifies the exit code |
— | Your own shutdown paths |
REASON_LOW_MEMORY is not reported on every device. The platform source states that not all devices support reporting it, and that on a device without that support a process killed under memory pressure reports REASON_SIGNALED with a status of SIGKILL instead. ActivityManager.isLowMemoryKillReportSupported() is the check for whether the device reports the specific reason. A dashboard that counts only REASON_LOW_MEMORY will undercount memory kills across a real device population.
Sending these records to your own backend on the next launch closes the gap that crash reporting leaves. That is an engineering choice rather than a documented recommendation, and the payload is small: a reason, a description, a timestamp, and the resident size at the time.
Low Memory Kills and the LMK Rate in Play Console
Android publishes a fleet-level view of this exact failure. The user-perceived LMK rate sits under the Stability section in Android vitals and reflects the percentage of your daily active users who experienced at least one user-perceived LMK — one likely to have been noticed, such as an LMK that happens while your app is displaying an activity or running as a foreground service. Data is available for existing apps starting from 28 Jan 2025.
An LMK rate above 1% indicates a critical need for immediate action. The more useful half of that guidance runs the other way: a lower LMK rate does not necessarily mean the app's memory usage is healthy, because the lower rate might mean the LMK daemon is frequently killing processes while they are in the background, which degrades warm start performance and multitasking fluidity. A game that loads slowly every time a player comes back to it may be paying for memory it never gets blamed for.
Games are a stated target of this mechanism rather than incidental victims of it. Android caches apps and games to make them load quickly, and when memory becomes limited the system kills the most memory-intensive apps and games to free up memory for normal device operation.
Android vitals is also where frame pacing data lives, under a different metric: Slow Sessions covers stutter, the LMK rate covers being killed. Both describe the same build from the player's side, and neither one surfaces in a crash report.
Killed With Memory to Spare: Android 17's Memory Limiter
Device-wide memory pressure is not the only way to lose a process. Android 17 and higher includes the Memory Limiter, a system service that monitors and limits memory use through Linux cgroup v2. It selects a limit set based on the device's available RAM, and visible processes receive a more generous memory limit while background processes operate under a more restrictive one.
Crossing that limit is graduated rather than immediate. When an app crosses its memory.high limit the kernel reclaims memory from it, and the app might experience temporary execution throttling but continues to run. If the app keeps allocating after swap space is exhausted, it fails to allocate memory and is terminated. The Memory Limiter does not support allowlists to exempt specific apps.
What makes this hard to diagnose from a bug report is that the device can look fine. Android's games memory guidance notes that a kill for exceeding the assigned cgroup limit can happen even if there's ample physical memory remaining on the device, which is a direct violation of individual process limits rather than a symptom of a loaded device. A tester holding a half-empty phone can reproduce it while insisting there is plenty of RAM left.
Reducing the Chance of a Low Memory Kill
Two platform-level habits come before any engine-specific work. Look at the total physical RAM per device for fine granularity and adjust game memory usage based on it, rather than shipping one memory profile to every handset. Use the trim callbacks — TRIM_MEMORY_UI_HIDDEN specifically — to reduce memory usage when the game's UI is no longer visible, and do not register for the deprecated ones: Android deprecated every trim callback other than TRIM_MEMORY_UI_HIDDEN and TRIM_MEMORY_BACKGROUND because they were not helpful at preventing low-memory kills. Android has no APIs for detecting native memory pressure events when the system is running into memory limits, so there is no runtime signal to wait for.
On the Unity side, Google publishes a debugging guide specifically for LMKs in Unity games. It splits memory into managed memory, C# unmanaged memory, and native memory, and it starts by obtaining a memory snapshot before changing anything. Asset management is where it points first: that is the most impactful and actionable part of memory consumption, and the guidance is to profile as early as possible. Compressing textures with ASTC formats reduces the memory footprint, and experimenting with a higher block rate such as 8x8 goes further.
Keeping the gain is a separate problem from making it. The same guide treats a memory budget as crucial and recommends a memory consumption analytics or reporting system so each new release stays inside it, with Play Mode tests wired into CI to verify memory consumption in specific areas of the game.
Other Silent Exits That Look Like Android Game Crashes
Not every missing log is a memory kill, and the exit reason is what tells them apart. A native crash that reports REASON_CRASH_NATIVE did go through the process, which means a handler should have seen it — and the handler chain is the thing to check when one did not. Setting -androidChainedSignalHandlerBehavior disabled makes Unity ignore native crashes and lets Android forward them directly to the next handler, and Unity's own warning is that Unity services such as Unity Cloud Diagnostics then no longer handle crashes and won't report them.
An ANR is a different failure wearing the same clothes. An ANR is triggered when the UI thread of an app is blocked for too long — for example, when the app has not responded to an input event within 5 seconds — and the system shows a dialog that lets the user force quit. Android stores trace information when it experiences an ANR, in /data/anr/anr_* files on current releases, reachable over adb as root or inside a bug report.
bugreport is worth knowing about for the memory cases specifically. Its output is much more detailed than logcat, and Android's games guidance describes it as useful for showing whether your game crashed because it ran out of memory or was killed by the LMK.
Android Game Crash FAQ
Why doesn't Crashlytics show these crashes?
Crash reporters record failures that happen inside the process. When a game is terminated in the background or killed for violating individual process memory limits, standard Java or native crash dump mechanisms such as Firebase Crashlytics don't register the event, because the process is ended from outside rather than failing from within.
Can I detect a low memory kill while the game is running?
Not at the moment it happens. Android has no APIs for detecting native memory pressure events when the system is running into memory limits, and every trim callback other than TRIM_MEMORY_UI_HIDDEN and TRIM_MEMORY_BACKGROUND is deprecated because they were not helpful at preventing low-memory kills. The practical options are to shed memory when the UI is hidden and to read the exit reason on the next launch.
Does a low LMK rate mean my game's memory is fine?
No. A lower LMK rate might mean the LMK daemon is frequently killing processes while they are in the background, which degrades warm start performance and multitasking fluidity without ever registering as a user-perceived LMK.
Does this work on older Android versions?
ApplicationExitInfo is available on Android 11 (API level 30) or higher, so there is no equivalent record on older releases. On devices that do run it, the specific reason REASON_LOW_MEMORY is still not reported everywhere; ActivityManager.isLowMemoryKillReportSupported() is the check, and unsupported devices report REASON_SIGNALED with SIGKILL.
Sources
- Monitor memory usage — Android game development
- Low memory killers — Android app quality
- Low memory killers in Android vitals — Android game development
- ANRs — Android app quality
ApplicationExitInfo— Android API referenceApplicationExitInfo.java— Android platform source- Debug LMKs in Unity games — Android game development
- Memory Limiter — Android Open Source Project
ApplicationExitInfoProvider.GetHistoricalProcessExitInfo— Unity Scripting APIIApplicationExitInfo— Unity Scripting API- Android crash handling — Unity Manual