Blocchi nello sviluppo — essenza, cause e prevenzione

Autore: IT Sectr Pubblicato: 2026-07-28 Tempo di lettura: 9 min

Blocco (congelamento) è uno stato in cui un'applicazione mobile smette di rispondere a qualsiasi azione dell'utente per un periodo prolungato. A differenza dei lag (rallentamento) e dei glitch (comportamento errato), il blocco blocca completamente l'UI: i tocchi non vengono elaborati, l'animazione si ferma, lo schermo si “congela”. La causa è il blocco del thread principale da un'operazione sincrona, un deadlock in codice multithread o una garbage collection anormalmente lunga. Secondo la Documentazione Apple Main Thread Checker, oltre il 40% dei crash report iOS è correlato al blocco del thread principale. Su Android, una situazione simile porta ad ANR — il dialogo di sistema “L'applicazione non risponde”.

Punti chiave

  • Blocco — blocco completo dell'UI per un periodo prolungato (secondi e decine di secondi), diverso da lag e glitch
  • Cause principali — blocco del thread principale da I/O, deadlock tra thread, ciclo infinito e perdita di memoria con GC lungo
  • Diagnosi include Main Thread Checker su iOS, log ANR /data/anr/traces.txt su Android e analisi dei dump dei thread
  • Eliminazione — scaricare tutte le operazioni potenzialmente lunghe su thread in background, utilizzare Structured Concurrency ed evitare synchronized nel thread UI
  • Prevenzione — StrictMode, Main Thread Checker nello schema Debug, analisi statica per deadlock e test periodici che misurano il tempo di risposta

Cos'è un blocco nello sviluppo mobile

Blocco (congelamento) in un'applicazione mobile è uno stato in cui l'app smette di elaborare eventi di input e aggiornare l'interfaccia per diversi secondi o più. Tecnicamente, ciò significa che il thread principale è bloccato e non può eseguire la successiva iterazione del ciclo di esecuzione.

Differenza tra blocco, lag e ANR

Un lag è un ritardo fino a 500 ms in cui l'utente nota un rallentamento ma l'app continua a funzionare. Il blocco dura da 1 secondo a decine di secondi. ANR su Android è un caso speciale di blocco che è durato più di 5 secondi ed è stato rilevato dal sistema. Non tutti i blocchi portano ad ANR, ma ogni ANR è un blocco documentato dal sistema.

Conseguenze dei blocchi

Su Android, un blocco di oltre 5 secondi attiva un dialogo ANR che offre di chiudere l'app. Su iOS, il sistema ha un watchdog — se l'app non risponde agli eventi per 10–20 secondi, il Watchdog termina il processo con il codice 0x8badf00d (ate bad food). L'utente vede solo l'app chiudersi improvvisamente verso la schermata principale.

Cause dei blocchi su Android e iOS

Qualsiasi operazione che richieda più di 100 ms e sia lanciata sul thread principale può potenzialmente causare un blocco. Vediamo le principali fonti di blocchi.

I/O sincrono nel thread UI

Leggere un file grande, una richiesta di rete senza asincronia, salvare dati in SharedPreferences con il metodo sincrono apply seguito da commit — tutte queste operazioni bloccano il thread principale. Su Android, leggere in modo sincrono un file di 10 MB può richiedere 200–500 ms a seconda della velocità della memoria flash. Su iOS, un caricamento URLSession sincrono senza completionHandler blocca l'UI per il tempo di risposta del server.

Deadlock in codice multithread

Quando due thread attendono il rilascio di risorse trattenute reciprocamente, si verifica un deadlock. Nelle applicazioni mobili, uno scenario tipico è che il thread A blocca Lock1 e aspetta Lock2, mentre il thread B blocca Lock2 e aspetta Lock1. Entrambi i thread si bloccano per sempre. Se uno di essi è il thread principale, l'applicazione si blocca completamente.

Ciclo infinito o ricorsione

Un errore di logica — ad esempio, while(true) senza condizione di uscita o ricorsione senza caso base — porta a un'esecuzione infinita sul thread principale. Android lo rileva tramite ANR dopo 5 secondi, iOS — tramite Stackshot, che cattura uno stack di chiamate infinitamente ripetuto.

  • Android — Cursor senza chiusura, richiesta sincrona tramite execute() invece di enqueue(), FileInputStream.read() nel thread UI
  • iOS — performSelectorOnMainThread:withObject:waitUntilDone:YES, avvio di NSURLConnection sendSynchronousRequest, caricamento di un'immagine con dataWithContentsOfURL
  • Multipiattaforma — Flutter compute senza un isolate dedicato, React Native NativeModule sincrono

Come diagnosticare i blocchi

La diagnosi dei blocchi richiede strumenti in grado di catturare lo stato di tutti i thread al momento del blocco.

Log ANR su Android

Ad ogni ANR, il sistema Android salva un file /data/anr/traces.txt contenente un dump dello stack di ogni thread dell'app. Analizzare questo file è il metodo principale di diagnosi: trovare il thread main e vedere su quale metodo si è fermato. Se lo stack termina con Thread.sleep, InputStream.read o Lock.lock — la causa è trovata.

Stackshot su iOS

Xcode può catturare uno Stackshot — un'istantanea degli stack di tutti i thread — quando l'applicazione si blocca (segnale SIGSTOP). Abilitare “Logging” → “Include Stackshot Logs” nello schema. In caso di crash con codice 0x8badf00d, estrarre il log di crash da Devices & Simulators e trovare il thread com.apple.main-thread con lo stack bloccato.

Main Thread Checker in Xcode

Main Thread Checker rileva automaticamente chiamate UIKit da thread in background durante l'esecuzione dell'app. Abilitarlo nello schema (Diagnostics → Main Thread Checker). Ogni avviso è una causa potenziale di blocco, specialmente se si verifica in una closure completionHandler di una richiesta di rete.

Esempio di rilevamento di blocchi tramite StrictMode su Android:

kotlin
class App : Application() {
    override fun onCreate() {
        super.onCreate()
        StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder()
            .detectLeakedSqlLiteObjects()
            .detectLeakedClosableObjects()
            .penaltyLog()
            .build())
        StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder()
            .detectAll()
            .penaltyDeath()
            .build())
    }
}

Metodi per eliminare i blocchi UI

Eliminare i blocchi inizia spostando tutte le operazioni potenzialmente lunghe su thread in background. Vediamo tecniche specifiche per ogni piattaforma.

Concorrenza strutturata con coroutine

Kotlin Coroutines con viewModelScope.launch(Dispatchers.IO) garantiscono che le operazioni di rete o le letture del database vengano eseguite in un thread in background. Dispatchers.Main viene utilizzato solo per gli aggiornamenti UI. Importante: tutte le funzioni suspend devono essere strutturate — le coroutine figlie vengono cancellate quando il genitore viene cancellato, prevenendo perdite di thread.

Code asincrone su iOS

Grand Central Dispatch con DispatchQueue.global(qos: .userInitiated) per attività in background e DispatchQueue.main.async per aggiornamenti UI è il modello standard. Evitare sync() sulla coda principale — questo è un deadlock garantito. Usare async/await (Swift 5.5+) per codice asincrono più leggibile con ritorno automatico al thread principale tramite MainActor.

Evitare synchronized nel thread UI

I blocchi synchronized in Kotlin e @synchronized in Swift sul thread principale sono pericolosi: se un altro thread ha già acquisito questo blocco, il thread principale si bloccherà in attesa. Utilizzare tipi atomici (AtomicInteger, proprietà atomiche in Swift) o code seriali invece dei blocchi.

Esempio di caricamento asincrono di dati con coroutine su Android:

kotlin
class DataViewModel : ViewModel() {
    private val _data = MutableStateFlow<List<Item>>(emptyList())
    val data: StateFlow<List<Item>> = _data.asStateFlow()

    fun loadData() {
        viewModelScope.launch(Dispatchers.IO) {
            val result = fetchFromNetwork()
            _data.emit(result)
        }
    }
}

Prevenzione dei blocchi in fase di sviluppo

Una combinazione di strumenti, principi architetturali e processi di revisione del codice aiuta a prevenire sistematicamente i blocchi.

StrictMode con penaltyDeath

Configurare StrictMode con penaltyDeath per le politiche dei thread — ciò causerà un crash immediato dell'app quando viene rilevata una chiamata di rete o I/O su disco sul thread principale. Lo sviluppatore non può ignorare il problema. Nelle build di produzione, utilizzare penaltyLog per raccogliere statistiche senza crash.

Main Thread Checker nello schema Debug

Su iOS, abilitare Main Thread Checker nello schema Debug e configurare CI per eseguire test con questa opzione. Se un test contiene una chiamata UIKit da un thread in background — deve fallire. Questo è l'unico modo affidabile per identificare il problema prima dell'invio a TestFlight.

Revisione tra pari con controllo del multithreading

Aggiungere un punto obbligatorio al processo di revisione del codice: verificare che qualsiasi chiamata di rete, operazione su file, accesso al database o calcolo pesante venga eseguito in un thread in background. Il Deadlock può essere rilevato con analizzatori statici: Infer di Facebook e Thread Safety Checker di Xcode trovano blocchi potenziali prima dell'esecuzione.

  • Android — StrictMode, Infer, Android Lint Multithread, Kotlin Coroutines con viewModelScope
  • iOS — Main Thread Checker, TSAN (Thread Sanitizer), Xcode Analyze, Swift async/await con MainActor
  • Multipiattaforma — Flutter compute isolate, React Native interaction manager con requestAnimationFrame

Domande frequenti

Qual è la differenza tra blocco e ANR?

ANR (Application Not Responding) è una notifica di sistema Android che appare quando il thread principale si blocca per più di 5 secondi. Il blocco è un concetto più ampio: qualsiasi blocco UI di qualsiasi durata. iOS non ha ANR, ma ha un Watchdog con un timeout di 10–20 secondi.

Come leggere traces.txt su Android?

Il file si trova in /data/anr/traces.txt. L'accesso richiede privilegi di root o adb shell: eseguire adb shell cat /data/anr/traces.txt \> traces.txt con diritti di root. Nello stack, trovare il thread “main” — l'ultimo metodo chiamato indica la causa del blocco.

Perché l'app si blocca su iOS ma non crasha?

Se il blocco dura meno di 10 secondi, il Watchdog non si attiva e l'app semplicemente “rimane bloccata” fino al completamento dell'operazione bloccante. L'utente non vede un crash ma prova frustrazione. Per rilevare tali casi, utilizzare MetricKit con trace personalizzate del tempo di esecuzione.

Come testare un'app per i blocchi?

Utilizzare test UI con verifica che lo schermo si apra in < 1 secondo. Aggiungere al CI la misurazione del tempo tra il tocco e la comparsa dello schermo successivo. Su Android, utilizzare Espresso con IdlingResource per attendere operazioni asincrone. Su iOS, utilizzare XCTest con XCTWaiter per verificare il tempo di caricamento.

SwiftUI può causare blocchi?

SwiftUI stesso non causa blocchi, ma calcoli complessi nella proprietà body sì. Se il calcolo di body richiede 500 ms a causa di operazioni pesanti, l'UI si blocca. La soluzione è scaricare i calcoli in Task.detached e aggiornare @State in modo asincrono sull'attore principale.

Riepilogo

  • Blocco — blocco completo dell'UI per secondi o decine di secondi, causato da blocco del thread principale, deadlock o ciclo infinito
  • Diagnosi — /data/anr/traces.txt su Android, Stackshot e Main Thread Checker su iOS
  • Cause principali — I/O sincrono, deadlock tra thread, ricorsione infinita, GC lungo
  • Eliminazione — coroutine con i dispatcher corretti, async/await con MainActor, scaricare tutte le operazioni I/O su thread in background
  • Prevenzione — StrictMode con penaltyDeath, Main Thread Checker, analisi statica per deadlock (Infer, TSAN)
  • Su Android blocco > 5 s = ANR; su iOS > 10–20 s = Watchdog crash (0x8badf00d)
  • Raccomandazione: abilitare Thread Sanitizer nello schema Debug e configurare CI per eseguire test con TSAN per rilevare data race e deadlock

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