Crash es una terminación anormal de una aplicación móvil debido a una excepción no controlada o un fallo fatal del sistema. Según Firebase Crashlytics, aproximadamente el 2% de los usuarios experimentan crashes a diario, y cada caída reduce la retención entre un 10 y un 20%. Comprender las causas y los métodos de prevención de crashes es una habilidad esencial para cualquier desarrollador móvil.
Puntos clave
Crash es una terminación anormal de una aplicación causada por una excepción no controlada o una señal fatal del sistema que no fue manejada en el código de la aplicación. Cuando el sistema o la máquina virtual (JVM, ART) detecta una condición fatal — NullPointerException, IndexOutOfBoundsException, OutOfMemoryError — detiene inmediatamente el proceso y lo descarga de la memoria. El usuario ve un cierre repentino de la aplicación sin ninguna notificación de error del sistema. Según Google, las aplicaciones con una tasa libre de crashes inferior al 99% pierden hasta el 20% de los usuarios activos al mes.
En Android, el mecanismo de manejo de crashes difiere de los sistemas de escritorio. En lugar de un diálogo de depuración con stack trace, Android simplemente elimina el proceso sin guardar información detallada. La recopilación de información sobre crashes es tarea de bibliotecas de terceros (Crashlytics, Sentry, Bugsnag) que interceptan excepciones a través de Thread.setDefaultUncaughtExceptionHandler antes de que el proceso sea terminado.
iOS utiliza un mecanismo similar con NSException y Mach exceptions para manejar errores fatales. Cuando ocurre una excepción no controlada, el sistema termina la aplicación y el informe se guarda como un archivo .crash. La recopilación de crashes en iOS requiere integración con Crashlytics o el informe integrado a través de Xcode Organizer.
Cinco categorías de crashes cubren el 90% de todos los fallos en aplicaciones móviles. Comprender cada tipo ayuda a diagnosticar y corregir problemas en producción más rápidamente.
NullPointerException (NPE) es el tipo de crash más común en todas las aplicaciones Java/Kotlin. Ocurre al intentar llamar a un método o acceder a un campo de un objeto que es null. Escenarios típicos: campo de Activity no inicializado al rotar la pantalla, respuesta null del servidor durante la deserialización JSON, navegación descuidada por el adaptador de RecyclerView.
Kotlin resuelve el problema de NPE a nivel de lenguaje mediante tipos null-safe: String? no puede usarse sin una verificación explícita. Sin embargo, la compatibilidad con Java y Reflection aún generan riesgos. Use las anotaciones @NonNull y @Nullable y active strictNullChecks en las herramientas de análisis estático.
fun safeLength(text: String?): Int {
return text?.length ?: 0 // manejo seguro de null
}
IndexOutOfBoundsException ocurre al acceder a un índice inexistente de una lista o arreglo. Escenarios comunes: eliminación de un elemento de RecyclerView sin sincronización con el adaptador, modificación de ArrayList en múltiples hilos sin bloqueo, cálculo incorrecto de posición en ViewPager. ConcurrentModificationException es un pariente cercano al iterar y modificar colecciones simultáneamente.
Use CopyOnWriteArrayList para acceso multiproceso o colecciones Lock-free de java.util.concurrent. Para sincronización con la UI, use DiffUtil que calcula la diferencia entre listas antigua y nueva de forma segura y eficiente.
ClassCastException ocurre al convertir un objeto a un tipo incompatible. En Android, las causas típicas son: tipo incorrecto de ViewHolder en RecyclerView (diferentes tipos de celdas sin getItemViewType adecuado), conversión incorrecta de Fragment durante la navegación, objetos Serializable con diferentes versiones de clase.
Use el safe-cast de Kotlin mediante el operador as?, que devuelve null en caso de incompatibilidad de tipos. En Java — verifique con instanceof antes de convertir. Para objetos Parcelable, declare siempre CREATOR en cada clase.
IllegalStateException señala la llamada a un método en un estado inapropiado del objeto. Un ejemplo típico en Android — getSupportFragmentManager() después de onSaveInstanceState, cuando no se permite commit() de un fragment. Otro caso común — llamar a dismiss() en un diálogo ya cerrado.
Verifique el estado del ciclo de vida antes de operaciones con FragmentManager. Use commitAllowingStateLoss() solo cuando esté seguro de que la pérdida de estado no es crítica. En Kotlin, cree builders similares a DSL que eliminen estados inválidos a nivel de tipos.
Native Crash ocurre en código nativo C/C++ debido a violaciones de memoria: desreferencia de puntero null, double-free, desbordamiento de buffer en la pila. En Android, estos crashes ocurren en bibliotecas NDK, motores de juego (Unity, Unreal) y dependencias del sistema. Native Crash NO es interceptado por Thread.setDefaultUncaughtExceptionHandler — elimina el proceso instantáneamente.
Para diagnosticar native crashes, use archivos minidump (Breakpad) o tombstones de Android. Firebase Crashlytics admite la recopilación de crashes nativos a través del NDK SDK. En iOS, un problema similar se resuelve con PLCrashReporter.
Tres herramientas dominan el mercado de crash reporting móvil. Cada una proporciona recopilación de stack trace, agregación por versión de aplicación y notificaciones de nuevos crashes.
Crashlytics es el crash reporter más popular para aplicaciones móviles, parte del ecosistema Firebase. Recopila automáticamente stack traces, información del dispositivo, versión del SO y custom keys del usuario. La integración toma 10 minutos a través de Firebase Console y Gradle Plugin. Crashlytics también admite registros en tiempo real (Logcat) y seguimientos de usuario.
FirebaseCrashlytics.getInstance()
.setCustomKey("current_screen", "ProfileFragment")
FirebaseCrashlytics.getInstance()
.log("User tapped login button")
Sentry es una alternativa a Crashlytics con un sistema de filtrado más flexible y soporte para más de 90 plataformas. A diferencia de Firebase, Sentry proporciona un servidor autogestionado (self-hosted) para empresas con requisitos estrictos de datos. Sentry admite seguimiento distributivo, breadcrumbs e integración con pipelines CI/CD.
Bugsnag se destaca por su soporte de alertas basadas en severidad: clasifica los crashes en critical, error y warning. AppCenter de Microsoft es una herramienta gratuita con funcionalidad básica para proyectos pequeños. Ambos admiten Android, iOS, React Native y Flutter.
El análisis de un crash es el proceso de reconstruir la imagen completa de lo ocurrido. El stack trace solo muestra el último punto de fallo, pero no proporciona el contexto que llevó al problema. Un enfoque profesional incluye cuatro etapas.
La primera etapa es leer el stack trace. Identifique la clase, el método y la línea de código donde ocurrió la excepción. Siga la cadena de llamadas desde el marco superior al inferior: la última línea en la pila es la ubicación del crash, y las líneas superiores son la secuencia de llamadas. La desofuscación (mapeo ProGuard/R8) es obligatoria para las compilaciones de producción.
La segunda etapa es el contexto del dispositivo. Crashlytics muestra el modelo del dispositivo, la versión del SO, la memoria disponible y la versión de la aplicación. Por ejemplo, un crash solo en Samsung Galaxy S10 con Android 11 apunta a un problema con una versión específica de One UI, no a un error general de código.
La tercera etapa es la reproducción en un dispositivo de prueba. Si el crash no se reproduce de manera consistente, pida al usuario los pasos exactos o use Remote Config para registrar antes de la sección problemática de código. Las pruebas AB de la corrección en parte de la audiencia ayudan a confirmar la solución.
La cuarta etapa es el monitoreo posterior a la corrección. Después de publicar la corrección, monitoree la tasa de crashes durante 3 a 5 días. Si el crash desaparece por completo — la corrección funcionó. Si la frecuencia disminuyó pero no llegó a cero — existe un segundo escenario que requiere un análisis aparte.
Un enfoque sistemático para la prevención de crashes incluye herramientas de análisis estático, pruebas obligatorias de casos límite y un manejo adecuado de errores en todos los niveles de la aplicación.
Detekt (Kotlin) y Lint (Android) encuentran problemas potenciales en tiempo de compilación: variables no utilizadas, NPE potenciales, uso incorrecto de API. Incluya estas herramientas en el pipeline de CI con un umbral de errores. Por ejemplo, Detekt con una configuración de 30+ advertencias o cualquier bloqueo de errores no pasa la compilación.
La cobertura de escenarios clave de uso con pruebas unitarias es la protección básica contra crashes de regresión. Pruebe modelos de datos, ViewModel y capas de UseCase con casos límite: valores null, listas vacías, JSON inválido. Las pruebas de UI mediante Espresso o Compose Test cubren flujos críticos: autenticación, pago, onboarding.
Diseñe la aplicación de modo que un fallo en un módulo no derribe toda la pantalla. Use bloques catch a nivel de ViewModel con estado de respaldo: mostrar un marcador de posición en lugar de una lista, datos en caché cuando no hay conexión, una imagen de respaldo al cargar. Esto convierte un crash potencial en un escenario de UX controlado.
Los lanzamientos escalonados son una práctica estándar en Google Play y App Store: una nueva versión se distribuye al 5%, luego al 20% y luego al 100% de la audiencia con intervalos de 1 a 3 días. En cada etapa se monitorea la tasa de crashes: si la tasa libre de crashes cae por debajo del 99.5%, el lanzamiento se detiene automáticamente. Firebase Remote Config permite desactivar funciones problemáticas sin publicar una nueva versión.
Renovate o Dependabot en CI verifican automáticamente las bibliotecas en busca de vulnerabilidades conocidas y errores críticos. Actualizar una sola dependencia puede eliminar toda una clase de crashes. Sin embargo, pruebe las actualizaciones en el entorno de staging antes de implementarlas en producción — una nueva versión de biblioteca puede contener cambios incompatibles.
Preguntas frecuentes
No. Algunos crashes son causados por factores fuera del control del desarrollador: errores del sistema, problemas de hardware, incompatibilidad de firmware. El objetivo es reducir la tasa al 0.1% o menos y minimizar el tiempo de respuesta para los crashes restantes.
Un crash reporter recopila stack trace, estado de memoria e información del dispositivo en el momento del crash. La analítica recopila datos de comportamiento del usuario. Crashlytics combina ambos enfoques, proporcionando contexto del crash junto con custom keys del usuario.
ProGuard y R8 ofuscan el código para proteger la propiedad intelectual. Para la desofuscación, cargue el archivo de mapeo en Crashlytics durante la publicación. Sin un archivo de mapeo, el stack trace mostrará a.a(), b.b() en lugar de los nombres reales de clases y métodos.
A través de Thread.setDefaultUncaughtExceptionHandler en Android: la biblioteca registra su propio manejador, que recibe primero la excepción no controlada, guarda los datos y solo entonces termina el proceso. En iOS se usa NSSetUncaughtExceptionHandler para NSException y Mach exception handler para señales.
Fatal — la aplicación terminó. No fatal (excepción capturada) — el desarrollador capturó la excepción mediante try-catch, pero puede indicar un problema potencial. Crashlytics distingue estos tipos y permite filtrar los no fatales por separado para no saturar el panel.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también