Global Exception Handler — un mecanismo centralizado para capturar excepciones no manejadas que evita el cierre inesperado de aplicaciones móviles. Según Apple Developer, 2024, el manejo adecuado de excepciones reduce la cantidad de fallos en un 40–60% y mejora la experiencia del usuario. Sin dicho controlador, cualquier excepción no capturada en un hilo en segundo plano provoca el cierre inmediato de la aplicación.
Puntos Clave
Global Exception Handler — es un mecanismo centralizado para capturar excepciones que no fueron manejadas a nivel de funciones o módulos individuales de la aplicación. En el contexto del desarrollo móvil, dicho controlador actúa como la última línea de defensa antes de la terminación anormal del proceso.
iOS y Android proporcionan API integradas para establecer un controlador global. Apple utiliza NSSetUncaughtExceptionHandler para el entorno Objective-C, mientras que Google ofrece Thread.setDefaultUncaughtExceptionHandler en Java/Kotlin. Ambos mecanismos capturan excepciones que no fueron atrapadas por construcciones try-catch en todos los hilos de la aplicación.
Según Crashlytics (Google, 2024), aproximadamente el 25% de los fallos ocurren debido a excepciones no capturadas en hilos en segundo plano, un área donde el Global Exception Handler es especialmente crítico. Los desarrolladores a menudo se centran en el hilo de UI, olvidando las operaciones asincrónicas.
El uso de un controlador global no reemplaza el manejo local de errores, sino que lo complementa. La tarea principal es guardar la máxima información sobre el estado de la aplicación en el momento de la excepción y finalizar correctamente.
El mecanismo de funcionamiento de Global Exception Handler se basa en la intercepción de señales del sistema operativo o excepciones en tiempo de ejecución. Cuando el código lanza una excepción que no es capturada por ningún bloque try-catch, el control se transfiere a un controlador previamente registrado.
En iOS, el controlador se registra mediante NSSetUncaughtExceptionHandler y recibe un objeto NSException con un stack trace completo. En Android, se utiliza Thread.setDefaultUncaughtExceptionHandler que acepta Thread y Throwable, proporcionando acceso al tipo de excepción, mensaje y pila de llamadas.
Después de recibir los datos del fallo, el controlador realiza tres acciones obligatorias: escribir un registro en el almacenamiento local, enviar un informe a Crashlytics o Sentry, y finalizar correctamente la aplicación. Según Apple WWDC 2023, el tiempo de ejecución del controlador está limitado a 5 segundos; después de eso, el sistema finaliza el proceso de forma forzada.
Para aplicaciones Swift a partir de iOS 13, se ha introducido la Signals API que maneja no solo excepciones sino también señales del sistema operativo (SIGABRT, SIGSEGV, SIGBUS), ampliando la cobertura del controlador a errores de memoria de bajo nivel.
La implementación de un controlador global en iOS requiere configurar una función C mediante NSSetUncaughtExceptionHandler. El controlador se invoca de forma síncrona en el momento de una excepción no capturada y recibe el contexto completo del error.
void handleUncaughtException(NSException exception) {
NSDictionary userInfo = [exception userInfo];
NSArray stackTrace = [exception callStackSymbols];
NSString reason = [exception reason];
// Guardar registro de fallo en archivo local
NSString logPath = [NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
[exceptionLog writeToFile:logPath atomically:YES];
}
int main(int argc, char argv[]) {
NSSetUncaughtExceptionHandler(&handleUncaughtException);
return UIApplicationMain(argc, argv, nil, nil);
}
Una característica importante de la implementación en iOS: el controlador captura solo excepciones de Objective-C. Los errores de Swift que utilizan el mecanismo throw-catch no llegan a este controlador; requieren un manejo separado a través de Swift Error Handling. A partir de iOS 14, Apple recomienda combinar NSSetUncaughtExceptionHandler con Signals API para obtener la máxima cobertura.
Según Apple Technical Note TN2151, después de llamar al controlador, la aplicación debe finalizar en un plazo de 5 segundos. Cualquier intento de continuar la ejecución después de regresar del controlador provoca un comportamiento indefinido y un nuevo fallo.
Android proporciona un mecanismo más flexible para el manejo global de excepciones a través de Thread.setDefaultUncaughtExceptionHandler. El controlador recibe una referencia al hilo donde ocurrió la excepción y el objeto Throwable en sí.
class GlobalExceptionHandler : Thread.UncaughtExceptionHandler {
override fun uncaughtException(thread: Thread, throwable: Throwable) {
// Guardar registro de fallo en archivo
val stackTrace = throwable.stackTraceToString()
val crashLog = "CRASH: ${thread.name}\n${stackTrace}"
val file = File(context.cacheDir, "crash_log.txt")
file.writeText(crashLog)
// Enviar a Crashlytics
FirebaseCrashlytics.getInstance()
.recordException(throwable)
// Finalizar proceso
android.os.Process.killProcess(
android.os.Process.myPid()
)
}
}
// Configuración en Application.onCreate
class App : Application() {
override fun onCreate() {
super.onCreate()
Thread.setDefaultUncaughtExceptionHandler(
GlobalExceptionHandler()
)
}
}
Una diferencia clave de la implementación en Android: cada hilo tiene su propio controlador, y setDefaultUncaughtExceptionHandler establece el controlador para todos los hilos que no tienen uno individual asignado. Esto garantiza una cobertura global, desde el hilo de UI hasta AsyncTask en segundo plano y corrutinas.
En Android 12+, existe una limitación: después de llamar a uncaughtException, la aplicación debe finalizar en un plazo de 100 milisegundos. Si el controlador realiza operaciones prolongadas, el sistema puede eliminar el proceso antes de que se escriba el registro. Se recomienda utilizar un servicio en segundo plano para enviar informes de fallos.
La primera regla — no intentar restaurar el funcionamiento de la aplicación después de una excepción no capturada. El estado de la aplicación tras un fallo no está definido, y continuar la ejecución puede provocar la corrupción de los datos del usuario.
Límite de tiempo — la principal restricción técnica del Global Exception Handler. En iOS es de 5 segundos, en Android de 100 milisegundos. Dentro del controlador, solo se debe guardar un conjunto mínimo de datos: tipo de excepción, pila de llamadas y el estado de algunas variables clave.
El envío de solicitudes de red, la escritura en la base de datos y la serialización compleja deben diferirse a un mecanismo diferido, por ejemplo, guardar el registro en un archivo y enviarlo en el próximo inicio de la aplicación.
Servicios de informes de fallos — Firebase Crashlytics, Sentry, Bugsnag — establecen su propio controlador global. Si un desarrollador configura un controlador personalizado adicional, debe transferir el control al sistema de informes de fallos después de sus propias acciones. En Android se utiliza la composición de controladores: ejecutar la lógica propia y luego llamar al controlador anterior.
Para Firebase Crashlytics, se recomienda no configurar un Thread.setDefaultUncaughtExceptionHandler personalizado, ya que el SDK de Crashlytics lo hace automáticamente al inicializarse.
Contexto del usuario — además de la pila de llamadas estándar, es útil registrar la versión de la aplicación, la versión del SO, el tamaño de memoria disponible y el tiempo de actividad antes del fallo. Estos datos son críticamente importantes para reproducir y solucionar el problema.
En iOS, se puede usar NSSetUncaughtExceptionHandler no solo para escribir, sino también para almacenar datos temporalmente en NSUserDefaults con la bandera synchronize, lo que garantiza su persistencia incluso ante la finalización inmediata del proceso.
Pruebas obligatorias — el Global Exception Handler debe probarse en cada etapa del CI/CD. En iOS se puede iniciar una excepción de prueba mediante @throw NSException, en Android mediante throw RuntimeException(). Se verifica que el controlador se invoque, que el registro se guarde y que la aplicación finalice correctamente.
Según Google I/O 2023, más del 30% de los fallos en producción ocurren en dispositivos que el desarrollador no probó: diferentes versiones de Android, firmware personalizado, memoria limitada.
El primer error y el más común — intentar continuar la ejecución de la aplicación después de manejar una excepción. Después de llamar a uncaughtException, la aplicación se encuentra en un estado inestable, y cualquier operación adicional puede provocar errores en cascada y corrupción de datos.
El segundo error — realizar operaciones prolongadas dentro del controlador. Las solicitudes de red, la escritura de archivos grandes o los cálculos complejos no se completan antes de la finalización forzada del proceso. Según Apple Technical Q&A QA1468, intentar enviar una solicitud HTTP dentro del controlador es la principal causa de pérdida de informes de fallos.
El tercer error — ignorar los hilos en segundo plano. Un Global Exception Handler configurado solo para el hilo principal no protege contra fallos en corrutinas, DispatchQueue, AsyncTask o RxJava. En Android, cada hilo debe tener su propio controlador, y setDefaultUncaughtExceptionHandler solo resuelve esto para hilos sin un controlador individual.
El cuarto error — falta de respaldo para señales del SO. NSSetUncaughtExceptionHandler en iOS no captura SIGABRT, SIGSEGV ni SIGBUS. Estas señales requieren una configuración separada de controladores mediante la API sigaction. Los desarrolladores descubren esto solo cuando la aplicación falla sin un solo informe de fallo.
El quinto error — registro de datos confidenciales. En el registro de fallos pueden aparecer correos electrónicos, tokens de autorización o datos personales de los usuarios. Esto viola el GDPR y las Normas de Revisión de la App Store de Apple. Filtre siempre los datos transmitidos mediante expresiones regulares o una lista blanca de campos permitidos.
Preguntas Frecuentes
No — después de invocar un Global Exception Handler, el estado de la aplicación no está definido. Cualquier intento de continuar la ejecución puede provocar la corrupción de datos. La única acción correcta es guardar el registro del fallo y finalizar el proceso.
No todos — en iOS, NSSetUncaughtExceptionHandler captura solo excepciones de Objective-C. Los errores de Swift y las señales del SO (SIGSEGV, SIGABRT) requieren controladores separados. En Android, Thread.setDefaultUncaughtExceptionHandler captura todas las RuntimeException, pero no los errores de código nativo a través de JNI.
Guarde una referencia al controlador anterior mediante Thread.getDefaultUncaughtExceptionHandler() antes de configurar el suyo propio. Al final de su controlador, llame a previousHandler.uncaughtException(thread, throwable) — esto garantiza que Crashlytics o Sentry reciban sus datos.
Para código nativo, se requiere manejo de señales mediante sigaction() — SIGSEGV, SIGABRT, SIGBUS. En Android se puede usar Google Breakpad o Crashpad. En iOS a partir de la versión 13, está disponible la Signals API para manejar excepciones mach.
No — la configuración del controlador solo afecta el momento en que ocurre una excepción. En el funcionamiento normal de la aplicación, no hay sobrecarga. El único riesgo es una fuga de memoria si el controlador mantiene una referencia a una Activity o Context, impidiendo la recolección de basura.
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