Quick answer: The most common cause is IL2CPP code stripping removing classes your game needs at runtime. Unity's managed code stripping aggressively removes unused code to reduce APK size, but it can strip classes accessed only via reflection or late binding. Add a link.xml file to preserve critical assemblies.
Here is how to fix Unity build crashes on Android device. Your Unity game runs perfectly in the editor and even in a standalone Windows build — but the moment you deploy it to an Android device, it crashes on launch, crashes during gameplay, or freezes and gets killed by the OS. Android builds introduce an entirely different set of failure modes that do not exist on desktop. This guide walks through every common cause and gives you a systematic approach to diagnosing and fixing each one.
The Symptom
You build your Unity project for Android, deploy the APK or AAB to a physical device or emulator, and one of the following happens: the app opens and immediately closes, the splash screen appears but the game never loads, or the game runs for a few minutes before freezing and being killed. In some cases you see a brief black screen before Android shows the "App has stopped" dialog.
The Unity editor console shows no errors during the build process. The build succeeds, the APK is generated, and everything looks normal. But on-device, something is fundamentally wrong. Without crash logs, you are left guessing, which is why the first step is always to capture those logs.
This problem is especially frustrating because it can appear suddenly after upgrading Unity versions, changing a single Player Setting, or adding a new plugin. The build process itself does not warn you about most of these issues — they only manifest at runtime on actual hardware.
Capturing Crash Logs with adb logcat
Before you can fix anything, you need to see the actual error. Android crash logs tell you exactly what went wrong, and adb logcat is the tool that gives them to you. Without this step, you are debugging blind.
First, make sure you have the Android SDK platform tools installed. Unity typically installs these alongside the Android build support module. You can find them at Editor/Data/PlaybackEngines/AndroidPlayer/SDK/platform-tools/ on Windows, or install them separately via Android Studio.
Connect your Android device via USB and enable USB debugging in Developer Options. Then open a terminal and run:
// Terminal command to capture Unity-specific Android logs
// Run this BEFORE launching your game on the device
adb logcat -s Unity ActivityManager DEBUG
This filters the log stream to show only Unity engine messages, Android activity lifecycle events, and native crash dumps. Launch your game on the device while logcat is running, and watch for the crash output.
The crash log will typically contain one of these patterns:
// Signal 11 (SIGSEGV) - Native crash, often IL2CPP or plugin related
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
// Java exception - Managed code or Android API error
java.lang.UnsatisfiedLinkError: No implementation found for method
// Stripping error - IL2CPP removed something it should not have
Unable to find method void SomeClass::SomeMethod()
// Out of memory - Too many textures, meshes, or audio loaded
OutOfMemoryError: Failed to allocate
Each of these patterns points to a different root cause. A SIGSEGV at address 0x0 is a null pointer dereference in native code. An UnsatisfiedLinkError means a native library failed to load. The "Unable to find method" message is the hallmark of IL2CPP stripping. And OutOfMemoryError means your game is using more RAM than the device has available.
If logcat produces too much noise, you can also write logs to a file and search them afterward:
// Save logcat output to a file for later analysis
adb logcat -d > android_crash_log.txt
IL2CPP Code Stripping
IL2CPP is Unity's default scripting backend for Android. It converts your C# code to C++ and compiles it to native ARM code. As part of this process, Unity runs a managed code stripping pass that removes classes, methods, and fields that appear to be unused. This reduces APK size significantly, but it can remove things your game actually needs at runtime.
Stripping is especially dangerous when you use reflection, Type.GetType(), JSON serialization libraries, or any pattern where types are referenced by string name rather than direct code reference. The stripping tool cannot see string-based references, so it assumes those types are unused and removes them.
The fix has two parts. First, reduce the stripping aggressiveness. In Player Settings > Other Settings > Optimization, set Managed Stripping Level to Minimal instead of High or Medium:
// In your build script, you can set stripping level programmatically
using UnityEditor;
public class BuildConfig
{
[MenuItem("Build/Configure Android")]
public static void ConfigureAndroid()
{
PlayerSettings.SetManagedStrippingLevel(
BuildTargetGroup.Android,
ManagedStrippingLevel.Minimal
);
}
}
Second, create a link.xml file in your Assets folder to explicitly preserve assemblies and types that must not be stripped:
<!-- Assets/link.xml -->
<!-- Preserve assemblies that IL2CPP might incorrectly strip -->
<linker>
<!-- Preserve the entire assembly used by your JSON serializer -->
<assembly fullname="Newtonsoft.Json" preserve="all"/>
<!-- Preserve specific types used via reflection -->
<assembly fullname="Assembly-CSharp">
<type fullname="MyGame.SaveData" preserve="all"/>
<type fullname="MyGame.PlayerProfile" preserve="all"/>
</assembly>
<!-- Preserve System.Reflection for dynamic type loading -->
<assembly fullname="mscorlib">
<type fullname="System.Reflection.MemberInfo" preserve="all"/>
</assembly>
</linker>
If you are unsure which types are being stripped, you can enable stripping diagnostics. When you build with IL2CPP, Unity generates a report at Temp/StagingArea/Il2Cpp/il2cppOutput/Symbols/ that lists which types and methods were preserved or stripped. Search this report for the class names from your crash log.
A common pattern is to add a dummy method that references all the types you need, ensuring the stripping tool sees them as used:
// Force IL2CPP to preserve types used by reflection or serialization
public class PreserveTypes
{
// This method is never called, but its references
// prevent IL2CPP from stripping these types
[UnityEngine.Scripting.Preserve]
private static void UsedOnlyForAOTCodeGeneration()
{
var a = new SaveData();
var b = new PlayerProfile();
var c = new List<InventoryItem>();
var d = new Dictionary<string, QuestState>();
}
}
Vulkan Graphics API Fallback
Unity defaults to Vulkan as the graphics API on Android for better performance. However, not all Android devices support Vulkan, and some that claim to support it have buggy drivers. If your game crashes with a SIGSEGV during initialization or when loading the first scene, the graphics API is a likely culprit.
The fix is to configure Unity to try Vulkan first but automatically fall back to OpenGL ES 3.0 on devices where Vulkan is unavailable or broken. Go to Player Settings > Other Settings > Rendering, uncheck Auto Graphics API, and add both APIs in this order: Vulkan first, then OpenGLES3.
// Programmatically configure graphics API fallback
using UnityEditor;
using UnityEngine.Rendering;
public class GraphicsConfig
{
[MenuItem("Build/Set Android Graphics APIs")]
public static void SetGraphicsAPIs()
{
PlayerSettings.SetUseDefaultGraphicsAPIs(BuildTarget.Android, false);
PlayerSettings.SetGraphicsAPIs(BuildTarget.Android, new[]
{
GraphicsDeviceType.Vulkan,
GraphicsDeviceType.OpenGLES3
});
}
}
If you want to test specifically whether Vulkan is the problem, you can temporarily remove it from the list and build with OpenGLES3 only. If the crash disappears, you know the issue is Vulkan-related. Common Vulkan crash triggers include custom shaders that use features not available on all Vulkan implementations, compute shaders with workgroup sizes that exceed device limits, and render texture formats not supported by the device's Vulkan driver.
You can also detect the graphics API at runtime and log it for diagnostics:
using UnityEngine;
public class GraphicsDiag : MonoBehaviour
{
void Start()
{
Debug.Log("Graphics API: " + SystemInfo.graphicsDeviceType);
Debug.Log("GPU: " + SystemInfo.graphicsDeviceName);
Debug.Log("Vulkan supported: " + SystemInfo.supportsVulkan);
Debug.Log("Max texture size: " + SystemInfo.maxTextureSize);
}
}
Android Permissions and Manifest Issues
Android requires apps to declare permissions for hardware and OS features they use. If your game accesses the internet, camera, microphone, file storage, or vibration motor without declaring the corresponding permission, the call will either fail silently or crash the app with a SecurityException.
Unity automatically adds some permissions based on your project settings, but it cannot detect every case. If you use third-party plugins, native Android libraries, or call Android APIs via JNI, you may need to add permissions manually.
Create or modify a custom Android manifest at Assets/Plugins/Android/AndroidManifest.xml:
<!-- Assets/Plugins/Android/AndroidManifest.xml -->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Network access for multiplayer, analytics, ads -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- File storage for save games -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- Vibration for haptic feedback -->
<uses-permission android:name="android.permission.VIBRATE" />
<application android:isGame="true">
<activity android:name="com.unity3d.player.UnityPlayerActivity"
android:configChanges="orientation|screenSize|keyboard">
</activity>
</application>
</manifest>
On Android 6.0 and later, some permissions require runtime requests in addition to manifest declarations. Unity provides an API for this:
using UnityEngine;
using UnityEngine.Android;
public class PermissionRequester : MonoBehaviour
{
void Start()
{
// Request storage permission at runtime on Android 6.0+
if (!Permission.HasUserAuthorizedPermission(
Permission.ExternalStorageWrite))
{
Permission.RequestUserPermission(
Permission.ExternalStorageWrite);
}
}
}
A subtle crash trigger is the android:targetSdkVersion setting. If your target SDK is 30 or higher, Android enforces scoped storage, which means WRITE_EXTERNAL_STORAGE no longer grants broad file system access. If your game or a plugin writes files to paths that are no longer accessible, it will crash. The fix is to either use Unity's Application.persistentDataPath (which always points to your app's private storage) or add android:requestLegacyExternalStorage="true" to the application tag for SDK 29 compatibility.
ProGuard and R8 Minification Errors
When you build with Minify enabled in Unity's Publishing Settings, the build process runs ProGuard (or its successor R8) to shrink and obfuscate Java bytecode. This is primarily relevant when you have Java/Kotlin plugins or use Android libraries. If ProGuard removes or renames a class that Unity's JNI bridge calls by name, the game crashes with a ClassNotFoundException or NoSuchMethodError.
The fix is to add ProGuard rules that preserve the classes your game needs. Create a file at Assets/Plugins/Android/proguard-user.txt:
# Assets/Plugins/Android/proguard-user.txt
# Keep all classes in your game's Java package
-keep class com.yourcompany.yourgame.** { *; }
# Keep Unity's JNI bridge classes
-keep class com.unity3d.player.** { *; }
# Keep Firebase classes if using Firebase
-keep class com.google.firebase.** { *; }
# Keep ad SDK classes
-keep class com.google.android.gms.ads.** { *; }
# Don't obfuscate anything called via JNI
-keepnames class * {
@android.webkit.JavascriptInterface <methods>;
}
If you are unsure which classes are being removed, you can temporarily disable minification entirely to confirm it is the cause. In Player Settings > Publishing Settings, set both Use R8 and Minify options to false. If the crash disappears, re-enable minification and add specific keep rules until the crash is resolved.
JNI Errors and Native Plugin Crashes
JNI (Java Native Interface) is the bridge between Unity's C++ runtime and Android's Java layer. When native plugins, ad SDKs, or analytics libraries crash, they often produce JNI-related errors. These crashes are harder to debug because they cross the managed/native boundary.
Common JNI crash patterns include calling a Java method from the wrong thread, passing null where an object is expected, and using a local JNI reference after the method that created it has returned. The logcat output will typically show:
// Common JNI crash signatures in logcat
JNI DETECTED ERROR IN APPLICATION: JNI CallVoidMethod called with pending exception
JNI DETECTED ERROR IN APPLICATION: use of deleted local reference
FATAL EXCEPTION: UnityMain - java.lang.NullPointerException
If you are calling Android Java APIs from C#, make sure you do it on the correct thread. Many Android APIs must be called on the UI thread, not Unity's main thread:
using UnityEngine;
public class AndroidBridge : MonoBehaviour
{
public void ShowNativeToast(string message)
{
// This must run on the Android UI thread
using (var unityPlayer = new AndroidJavaClass(
"com.unity3d.player.UnityPlayer"))
{
var activity = unityPlayer.GetStatic<AndroidJavaObject>(
"currentActivity");
activity.Call("runOnUiThread", new AndroidJavaRunnable(() =>
{
using (var toast = new AndroidJavaClass(
"android.widget.Toast"))
{
var toastObj = toast.CallStatic<AndroidJavaObject>(
"makeText", activity, message, 0);
toastObj.Call("show");
}
}));
}
}
}
Always wrap AndroidJavaObject and AndroidJavaClass in using statements to ensure JNI references are properly disposed. Leaked references accumulate and eventually cause the JNI reference table to overflow, crashing the app.
Gradle Build Configuration Issues
Unity uses Gradle to compile and package the Android build. Gradle configuration issues can cause builds to fail outright or produce APKs that crash on specific devices. Common problems include dependency version conflicts, duplicate library inclusions, and NDK version mismatches.
If you see Gradle-related errors during the build or crashes related to missing native libraries at runtime, check your custom Gradle templates. In Player Settings > Publishing Settings, you can enable custom templates for the main Gradle file, the launcher, and the base settings. Unity generates these at Assets/Plugins/Android/.
// Common Gradle fix: resolve duplicate dependency conflicts
// In Assets/Plugins/Android/mainTemplate.gradle
android {
packagingOptions {
// Prevent duplicate .so files from crashing the build
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
pickFirst 'lib/arm64-v8a/libc++_shared.so'
}
}
dependencies {
// Force a specific version to resolve conflicts
implementation 'com.google.android.gms:play-services-base:18.2.0'
// Exclude a transitive dependency that conflicts
implementation('com.some.sdk:library:1.0') {
exclude group: 'com.google.android.gms'
}
}
Another common issue is the minimum API level. If your game uses APIs that require Android 7.0 (API 24) but your minimum API level is set to API 22, the game will crash on older devices when those APIs are called. Always check the minimum SDK version your plugins require and set your project's minimum API level accordingly in Player Settings > Other Settings > Identification.
If you encounter the dreaded "Failed to install the following Android SDK packages" error during the build, make sure your Android SDK tools are up to date. Open the Unity Hub, go to Installs, select your Unity version, and add or update the Android SDK & NDK Tools module.
Memory and Performance Crashes
Android devices have significantly less RAM than desktop computers, and Android is aggressive about killing apps that use too much memory. If your game crashes after running for several minutes rather than immediately on launch, memory pressure is the most likely cause.
You can monitor memory usage at runtime to detect approaching limits:
using UnityEngine;
using UnityEngine.Profiling;
public class MemoryMonitor : MonoBehaviour
{
void Update()
{
if (Time.frameCount % 300 == 0) // Log every 5 seconds at 60fps
{
long totalMemory = Profiler.GetTotalAllocatedMemoryLong();
long totalReserved = Profiler.GetTotalReservedMemoryLong();
long gfxMemory = Profiler.GetAllocatedMemoryForGraphicsDriver();
Debug.Log(string.Format(
"Memory - Allocated: {0:F1}MB, Reserved: {1:F1}MB, GFX: {2:F1}MB",
totalMemory / 1048576f,
totalReserved / 1048576f,
gfxMemory / 1048576f));
}
}
}
Common memory offenders on Android include uncompressed textures (use ASTC compression for Android), audio clips loaded entirely into memory (use streaming for long clips), and instantiating too many GameObjects without pooling. If your game loads multiple scenes, make sure you are calling Resources.UnloadUnusedAssets() after scene transitions to free memory held by assets from the previous scene.
Thermal throttling can also cause apparent crashes. When a device overheats, the OS may aggressively kill background and foreground apps. If your game runs the GPU at maximum capacity for extended periods, the device will heat up and eventually start throttling or killing your app. Use frame rate capping and adaptive quality settings to stay within thermal limits.
Related Issues
If your build succeeds but the game shows visual glitches instead of crashing, see our guide on fixing pink/magenta materials in Unity, which covers shader compilation failures on different platforms. For games that crash specifically during scene loads on Android, fixing Unity scene loading freezes covers memory spikes during async loading. And if your Android build produces Gradle errors before you even get an APK, the TextMeshPro not showing guide covers a related TMP asset build issue that affects Android.
Always check logcat first. The crash log tells you exactly what broke.