Crash nello sviluppo mobile: cos'è, tipi e metodi di prevenzione

Autore: IT Sectr Pubblicato: 2026-03-29 Tempo di lettura: 9 min

Crash è una terminazione anomala di un'applicazione mobile dovuta a un'eccezione non gestita o a un errore fatale del sistema. Secondo Firebase Crashlytics, circa il 2% degli utenti riscontra crash quotidianamente e ogni crash riduce la retention del 10–20%. Comprendere le cause e i metodi di prevenzione dei crash è un'abilità essenziale per qualsiasi sviluppatore mobile.

Punti chiave

  • Crash — un'eccezione non gestita che porta alla terminazione anomala del processo
  • NullPointerException — il tipo di crash più comune nelle applicazioni Java/Kotlin
  • I crash reporter raccolgono stack trace, stato del dispositivo e dati utente
  • Firebase Crashlytics — lo strumento standard per il monitoraggio dei crash nello sviluppo mobile
  • La prevenzione include una corretta gestione degli errori, test e verifica della null-safety

Cos'è un Crash

Crash è una terminazione anomala di un'applicazione causata da un'eccezione non gestita o da un segnale fatale del sistema che non è stato gestito nel codice dell'applicazione. Quando il sistema o la macchina virtuale (JVM, ART) rileva una condizione fatale — NullPointerException, IndexOutOfBoundsException, OutOfMemoryError — interrompe immediatamente il processo e lo scarica dalla memoria. L'utente vede una chiusura improvvisa dell'app senza alcuna notifica di errore di sistema. Secondo Google, le app con un tasso crash-free inferiore al 99% perdono fino al 20% degli utenti attivi al mese.

Su Android, il meccanismo di gestione dei crash differisce dai sistemi desktop. Invece di un dialogo di debug con stack trace, Android semplicemente uccide il processo senza salvare informazioni dettagliate. La raccolta di informazioni sui crash è compito di librerie di terze parti (Crashlytics, Sentry, Bugsnag) che intercettano le eccezioni tramite Thread.setDefaultUncaughtExceptionHandler prima che il processo venga terminato.

iOS utilizza un meccanismo simile con NSException e Mach exceptions per gestire gli errori fatali. Quando si verifica un'eccezione non gestita, il sistema termina l'applicazione e il report viene salvato come file .crash. La raccolta di crash su iOS richiede l'integrazione con Crashlytics o il report integrato tramite Xcode Organizer.

Tipi principali di crash

Cinque categorie di crash coprono il 90% di tutti i fallimenti nelle applicazioni mobili. Comprendere ogni tipo aiuta a diagnosticare e risolvere i problemi in produzione più rapidamente.

NullPointerException — il re dei crash

NullPointerException (NPE) è il tipo di crash più comune in tutte le applicazioni Java/Kotlin. Si verifica quando si tenta di chiamare un metodo o accedere a un campo di un oggetto che è null. Scenari tipici: campo Activity non inizializzato durante la rotazione dello schermo, risposta null dal server durante la deserializzazione JSON, navigazione negligente attraverso l'adattatore RecyclerView.

Kotlin risolve il problema NPE a livello di linguaggio attraverso tipi null-safe: String? non può essere utilizzato senza un controllo esplicito. Tuttavia, la compatibilità Java e la Reflection creano ancora rischi. Utilizza le annotazioni @NonNull e @Nullable e attiva strictNullChecks negli strumenti di analisi statica.

kotlin
fun safeLength(text: String?): Int {
    return text?.length ?: 0 // gestione sicura di null
}

IndexOutOfBoundsException ed errori di raccolta

IndexOutOfBoundsException si verifica quando si accede a un indice inesistente di una lista o array. Scenari comuni: rimozione di un elemento da RecyclerView senza sincronizzazione con l'adattatore, modifica multi-thread di ArrayList senza blocco, calcolo errato della posizione in ViewPager. ConcurrentModificationException è un parente stretto quando si iterano e modificano le raccolte simultaneamente.

Utilizza CopyOnWriteArrayList per l'accesso multi-thread o raccolte senza blocco da java.util.concurrent. Per la sincronizzazione con l'UI, utilizza DiffUtil che calcola la differenza tra liste vecchia e nuova in modo sicuro ed efficiente.

ClassCastException — problemi di tipo

ClassCastException si verifica quando si esegue il cast di un oggetto a un tipo incompatibile. In Android, le cause tipiche sono: tipo ViewHolder errato in RecyclerView (diversi tipi di cella senza un getItemViewType appropriato), cast errato di Fragment durante la navigazione, oggetti Serializable con versioni di classe diverse.

Utilizza il safe-cast di Kotlin tramite l'operatore as?, che restituisce null in caso di incompatibilità di tipo. In Java — verifica con instanceof prima del cast. Per gli oggetti Parcelable, dichiara sempre CREATOR in ogni classe.

IllegalStateException ed errori logici

IllegalStateException segnala la chiamata di un metodo in uno stato inappropriato dell'oggetto. Un esempio tipico in Android — getSupportFragmentManager() dopo onSaveInstanceState, quando commit() di un fragment non è consentito. Un altro caso comune — chiamare dismiss() su un dialogo già chiuso.

Verifica lo stato del ciclo di vita prima delle operazioni con FragmentManager. Utilizza commitAllowingStateLoss() solo quando sei sicuro che la perdita di stato non sia critica. In Kotlin, crea builder simili a DSL che eliminano stati non validi a livello di tipo.

Native Crash (segnali SIGSEGV, SIGABRT)

Native Crash si verifica in codice nativo C/C++ a causa di violazioni di memoria: dereferenziazione di puntatore nullo, double-free, overflow del buffer di stack. In Android, questi crash si verificano in librerie NDK, motori di gioco (Unity, Unreal) e dipendenze di sistema. Native Crash NON viene intercettato da Thread.setDefaultUncaughtExceptionHandler — uccide il processo istantaneamente.

Per diagnosticare i crash nativi, utilizza file minidump (Breakpad) o tombstones Android. Firebase Crashlytics supporta la raccolta di crash nativi tramite NDK SDK. Su iOS, un problema simile viene risolto utilizzando PLCrashReporter.

Strumenti di crash reporting

Tre strumenti dominano il mercato del crash reporting mobile. Ognuno fornisce raccolta di stack trace, aggregazione per versione dell'app e notifiche di nuovi crash.

Firebase Crashlytics

Crashlytics è il crash reporter più popolare per applicazioni mobili, parte dell'ecosistema Firebase. Raccoglie automaticamente stack trace, informazioni sul dispositivo, versione del sistema operativo e chiavi personalizzate dell'utente. L'integrazione richiede 10 minuti tramite Firebase Console e Gradle Plugin. Crashlytics supporta anche log in tempo reale (Logcat) e tracciamenti utente.

kotlin
FirebaseCrashlytics.getInstance()
    .setCustomKey("current_screen", "ProfileFragment")

FirebaseCrashlytics.getInstance()
    .log("User tapped login button")

Sentry

Sentry è un'alternativa a Crashlytics con un sistema di filtraggio più flessibile e supporto per oltre 90 piattaforme. A differenza di Firebase, Sentry fornisce un server auto-ospitato (self-hosted) per aziende con requisiti rigorosi sui dati. Sentry supporta il tracciamento distributivo, breadcrumbs e l'integrazione con pipeline CI/CD.

Bugsnag e AppCenter

Bugsnag si distingue per il supporto di avvisi basati sulla gravità: classifica i crash in critical, error e warning. AppCenter di Microsoft è uno strumento gratuito con funzionalità di base per progetti piccoli. Entrambi supportano Android, iOS, React Native e Flutter.

Come analizzare un crash

L'analisi di un crash è il processo di ricostruzione del quadro completo di ciò che è accaduto. Lo stack trace mostra solo l'ultimo punto di fallimento ma non fornisce il contesto che ha portato al problema. Un approccio professionale include quattro fasi.

La prima fase — leggere lo stack trace. Identifica la classe, il metodo e la riga di codice in cui si è verificata l'eccezione. Segui la catena di chiamate dal frame superiore a quello inferiore: l'ultima riga nello stack è la posizione del crash e le righe superiori sono la sequenza di chiamate. La deoffuscazione (mapping ProGuard/R8) è obbligatoria per le build di produzione.

La seconda fase — il contesto del dispositivo. Crashlytics mostra il modello del dispositivo, la versione del sistema operativo, la memoria disponibile e la versione dell'app. Ad esempio, un crash solo su Samsung Galaxy S10 con Android 11 indica un problema con una versione specifica di One UI, non un errore generale del codice.

La terza fase — la riproduzione su un dispositivo di test. Se il crash non si riproduce in modo stabile, chiedi all'utente i passaggi esatti o utilizza Remote Config per registrare prima della sezione di codice problematica. Il test AB della correzione su parte del pubblico aiuta a confermare la soluzione.

La quarta fase — il monitoraggio post-correzione. Dopo aver pubblicato la correzione, monitora il tasso di crash per 3–5 giorni. Se il crash scompare completamente — la correzione ha funzionato. Se la frequenza è diminuita ma non è arrivata a zero — esiste un secondo scenario che richiede un'analisi separata.

Pratiche di prevenzione dei crash

Un approccio sistematico alla prevenzione dei crash include strumenti di analisi statica, test obbligatori dei casi limite e una corretta gestione degli errori a tutti i livelli dell'applicazione.

Analisi statica del codice

Detekt (Kotlin) e Lint (Android) trovano problemi potenziali al momento della compilazione: variabili inutilizzate, NPE potenziali, uso errato delle API. Includi questi strumenti nella pipeline CI con una soglia di errore. Ad esempio, Detekt con una configurazione di 30+ avvisi o qualsiasi blocco di errore non fa passare la build.

Test unitari e test dell'UI

La copertura degli scenari d'uso chiave con test unitari è la protezione di base contro i crash di regressione. Testa modelli dati, ViewModel e livelli UseCase con casi limite: valori null, liste vuote, JSON non valido. I test dell'UI tramite Espresso o Compose Test coprono flussi critici: autenticazione, pagamento, onboarding.

Degradazione graduale

Progetta l'applicazione in modo che un fallimento in un modulo non faccia crashare l'intero schermo. Utilizza blocchi catch a livello di ViewModel con stato di fallback: mostrare un placeholder invece di una lista, dati in cache quando offline, un'immagine di riserva in caso di errore di caricamento. Questo trasforma un potenziale crash in uno scenario UX controllato.

Rollout graduale con monitoraggio

I rollout graduali sono una pratica standard su Google Play e App Store: una nuova versione viene distribuita al 5%, poi al 20% e poi al 100% del pubblico con intervalli di 1–3 giorni. In ogni fase, il tasso di crash viene monitorato: se il tasso crash-free scende sotto il 99,5%, il rollout si ferma automaticamente. Firebase Remote Config consente di disabilitare funzionalità problematiche senza pubblicare una nuova versione.

Controllo delle versioni delle dipendenze

Renovate o Dependabot in CI controllano automaticamente le librerie per vulnerabilità note e bug critici. L'aggiornamento di una singola dipendenza può eliminare un'intera classe di crash. Tuttavia, testa gli aggiornamenti nell'ambiente di staging prima del rollout in produzione — una nuova versione della libreria potrebbe contenere modifiche incompatibili.

Domande frequenti

Si può prevenire il 100% dei crash?

No. Alcuni crash sono causati da fattori fuori dal controllo dello sviluppatore: errori di sistema, problemi hardware, incompatibilità del firmware. L'obiettivo è ridurre il tasso allo 0,1% o inferiore e minimizzare il tempo di risposta per i crash rimanenti.

In cosa differisce un crash reporter dall'analitica?

Un crash reporter raccoglie stack trace, stato della memoria e informazioni del dispositivo al momento del crash. L'analitica raccoglie dati comportamentali dell'utente. Crashlytics combina entrambi gli approcci, fornendo il contesto del crash insieme alle chiavi personalizzate dell'utente.

Perché lo stack trace è offuscato?

ProGuard e R8 offuscano il codice per proteggere la proprietà intellettuale. Per la deoffuscazione, carica il file di mapping in Crashlytics durante la pubblicazione. Senza il file di mapping, lo stack trace mostrerà a.a(), b.b() invece dei nomi reali di classi e metodi.

Come intercetta un crash reporter le eccezioni?

Tramite Thread.setDefaultUncaughtExceptionHandler su Android: la libreria registra il proprio gestore, che riceve per primo l'eccezione non gestita, salva i dati e solo allora termina il processo. Su iOS, NSSetUncaughtExceptionHandler viene utilizzato per NSException e Mach exception handler per i segnali.

Cosa sono i crash fatal e non fatal?

Fatal — l'applicazione è terminata. Non fatal (eccezione catturata) — lo sviluppatore ha catturato l'eccezione tramite try-catch, ma potrebbe indicare un problema potenziale. Crashlytics distingue questi tipi e consente di filtrare i non fatal separatamente per non ingombrare la dashboard.

Riepilogo

  • Crash — terminazione anomala dell'applicazione dovuta a un'eccezione non gestita o segnale fatale
  • NullPointerException rimane il tipo di crash più comune nelle applicazioni mobili
  • Firebase Crashlytics — lo strumento standard per raccogliere e analizzare i crash in produzione
  • L'analisi dei crash include la lettura dello stack trace, il contesto del dispositivo e la riproduzione in un ambiente di test
  • L'analisi statica (Detekt, Lint) previene alcuni crash al momento della compilazione
  • La degradazione graduale trasforma potenziali crash in scenari gestibili con dati di fallback
  • I file di mapping sono obbligatori per deoffuscare gli stack trace nelle build di produzione

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche