StrictMode est un outil de développement intégré au SDK Android qui détecte et signale en temps réel les opérations d'E/S accidentelles et les appels réseau sur le thread principal de l'application. Il ne corrige pas les erreurs mais agit comme un détecteur — lève des exceptions ou écrit dans LogCat lorsque les politiques configurées sont violées. Selon Google, 2024, une configuration appropriée de StrictMode permet de détecter jusqu'à 80 % des problèmes de performance avant la publication de l'application.
Points clés
StrictMode est une API incluse dans le SDK Android depuis le niveau d'API 9 (Android 2.3 Gingerbread). Sa tâche est de détecter à l'exécution l'exécution accidentelle d'opérations lourdes sur le thread principal (UI) qui pourraient bloquer le rendu de l'interface. Le thread principal gère le traitement des entrées utilisateur, le calcul de la mise en page et le rendu — tout blocage de plus de 16 ms entraîne une perte d'image.
StrictMode suit le principe « d'échec rapide » — détecter le problème le plus tôt possible, idéalement au moment de sa première apparition. Au lieu d'attendre les plaintes des utilisateurs concernant les ralentissements, le développeur reçoit un signal (journaux, boîte de dialogue ou crash) directement au stade du développement. L'outil ne nécessite pas de bibliothèques supplémentaires ni de configuration Gradle — seulement quelques lignes de code dans Application.onCreate, et il fonctionne automatiquement sur tous les appareils.
StrictMode est conçu pour tous les développeurs Android, quelle que soit leur expérience. Pour les débutants, il aide à former de bonnes habitudes (ne pas faire de requêtes réseau dans le thread UI) ; pour les développeurs expérimentés, il automatise le contrôle qualité dans le pipeline CI/CD. Les grands projets (Google, Uber, Spotify) incluent StrictMode dans les builds de débogage avec penaltyDeath, et le désactivent dans les builds de version via la vérification BuildConfig.DEBUG.
StrictMode intercepte les appels système qui pourraient bloquer le thread et les compare à l'ensemble des politiques actives. Si un appel correspond à une politique et est exécuté sur le thread principal, StrictMode applique la pénalité spécifiée. Le mécanisme d'interception est implémenté via un hook intra-processus — il n'utilise pas la réflexion et fonctionne avec une surcharge minimale.
Lorsque la politique StrictMode est activée, elle injecte son gestionnaire dans les points d'entrée des appels système (FileInputStream, FileOutputStream, Socket, URLConnection). Lorsque l'application appelle, par exemple, URLConnection.openStream sur le thread principal, StrictMode vérifie le thread actuel — s'il s'agit du thread principal, l'outil se déclenche. Dans Android 6.0+, le mécanisme est renforcé : les appels réseau sur le thread principal génèrent NetworkOnMainThreadException même sans StrictMode, mais StrictMode permet également de contrôler les E/S disque.
Chaque politique peut avoir son propre type de pénalité ou combinaison : penaltyLog — écrit dans LogCat avec une trace de pile, penaltyDialog — affiche une boîte de dialogue à l'utilisateur (débogage uniquement), penaltyDeath — lève une exception et fait planter l'application, penaltyDropBox — enregistre les données dans DropBoxManager pour analyse ultérieure. Pour les pipelines CI/CD, penaltyDeath est recommandé — il garantit qu'aucun merge avec des violations ne passe inaperçu.
class App : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.penaltyDeath()
.build()
)
}
}
}
StrictMode divise les politiques en deux niveaux : ThreadPolicy (niveau thread — ce qui ne peut pas être fait sur le thread principal) et VmPolicy (machine virtuelle — fuites mémoire et de ressources). Les deux niveaux sont configurés indépendamment et fonctionnent en parallèle.
Au niveau du thread, StrictMode contrôle quatre types de violations : lectures disque (detectDiskReads), écritures disque (detectDiskWrites), opérations réseau (detectNetwork) et appels lents personnalisés (detectCustomSlowCalls). disk_read se déclenche lors de toute lecture de SharedPreferences, SQLite ou fichiers sur le thread principal. network se déclenche lors de requêtes HTTP, WebSocket et connexions Socket. Dans Android 11+, detectUnbufferedIO a été ajouté pour détecter les E/S non tamponnées.
VmPolicy contrôle les fuites au niveau de la machine virtuelle ART : detectActivityLeaks (activités qui n'ont pas été détruites), detectLeakedClosableObjects (Cursor, Stream, Socket non fermés), detectLeakedRegistrationObjects (BroadcastReceiver, ServiceConnection non désenregistrés). Si VmPolicy détecte qu'une Activity a été créée mais pas détruite après l'appel à onDestroy, il affiche une trace de pile complète — ce qui permet d'économiser des heures de débogage de fuites mémoire.
| Politique | Niveau | Ce qu'elle détecte |
|---|---|---|
| detectDiskReads | Thread | Lecture de SharedPrefs, SQLite, fichiers dans le thread UI |
| detectDiskWrites | Thread | Écriture dans SharedPrefs, SQLite, fichiers dans le thread UI |
| detectNetwork | Thread | Toute opération réseau dans le thread UI |
| detectActivityLeaks | VM | Activités survivant à onDestroy |
| detectLeakedClosableObjects | VM | Cursor, Stream, Socket non fermés |
Grâce à detectCustomSlowCalls, vous pouvez marquer vos propres méthodes comme «suspectes» et recevoir un avertissement lorsqu'un seuil spécifié est dépassé. Par exemple, si votre méthode loadUserProfile() prend normalement 5 ms mais parfois 200 ms — enveloppez-la dans StrictMode.noteSlowCall(«loadUserProfile»). Si la durée dépasse le seuil (par défaut 2000 ms), StrictMode générera une pénalité. Le seuil est configuré via setSlowCallDurationThreshold.
La configuration de base de StrictMode prend 10 lignes de code et s'effectue dans la méthode onCreate d'une classe Application personnalisée. La règle principale : StrictMode est activé uniquement dans les builds de débogage — dans les builds de version, il ralentit l'application et peut créer des faux positifs.
Créez une classe étendant Application, enregistrez-la dans AndroidManifest.xml via l'attribut android:name et ajoutez la configuration StrictMode. ThreadPolicy.Builder inclut tous les détecteurs et tous les types de pénalité (sauf dialog — il fonctionne uniquement lorsqu'un débogueur est attaché). VmPolicy.Builder ajoute des détecteurs pour les fuites d'Activity et les objets Closable. Pour les grands projets (100+ écrans), il est recommandé de configurer VmPolicy avec penaltyDeath sur le détecteur de fuites d'Activity — c'est strict mais efficace.
class App : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectActivityLeaks()
.detectLeakedClosableObjects()
.detectLeakedRegistrationObjects()
.penaltyLog()
.penaltyDeath()
.build()
)
}
}
}
Pour un contrôle automatique dans CI/CD, utilisez penaltyDeath — si un test viole la politique, l'application plantera avec une exception. Combinez avec Android Test Orchestrator pour que chaque test s'exécute dans un processus propre. Pour les tests UI (Espresso, Compose Test), écrivez une TestRule personnalisée qui intercepte les violations StrictMode et les transforme en échecs d'assertion. Exemple : dans @Before, activez StrictMode, et dans @After, vérifiez qu'il n'y a pas eu de violations.
Par défaut, le seuil pour customSlowCall est de 2000 ms, pour disk_read et disk_write — aucun seuil (toute opération déclenche). Via setSlowCallDurationThreshold et setSlowIoDurationThreshold, vous pouvez définir vos propres valeurs en millisecondes. Si votre application lit légitimement SharedPreferences sur le thread principal (petite configuration), augmentez le seuil à 10–20 ms — cela filtrera les lectures rapides tout en conservant les lentes.
StrictMode est un outil puissant mais capricieux. Une configuration incorrecte entraîne des millions de faux positifs, ce qui fait que les développeurs cessent d'y prêter attention. Voici des pratiques éprouvées recueillies auprès de l'expérience de grandes équipes Android.
C'est une règle absolue : StrictMode ne doit JAMAIS être actif dans les builds de version. Utilisez le flag BuildConfig.DEBUG ou un buildConfigField personnalisé. Dans les builds de version, de nombreuses bibliothèques tierces effectuent légitimement des opérations sur le thread principal (initialisation SDK, écriture de cache), et StrictMode créera des faux positifs. De plus, penaltyDialog dans un build de version affichera une boîte de dialogue à l'utilisateur final — ce qui est inacceptable.
Pour les petits projets (1–10 écrans), configurez penaltyLog — les journaux suffisent pour une analyse manuelle. Pour les projets moyens (10–50 écrans), ajoutez penaltyDeath sur le réseau et customSlowCalls. Pour les grands projets (50+ écrans), activez l'ensemble complet des politiques avec penaltyDeath dans CI/CD, et penaltyLog pour le développement local. Cette gradation évite de surcharger les développeurs avec de faux crashes tout en contrôlant strictement la qualité dans le pipeline.
Certaines bibliothèques (Firebase, Crashlytics, Adjust) effectuent légitimement des opérations en arrière-plan que StrictMode pourrait détecter incorrectement. Solutions : ajoutez la bibliothèque à une liste blanche via penaltyListener, mettez à jour la bibliothèque vers une version avec commutation explicite vers un thread d'arrière-plan, ou utilisez StrictMode.vmPolicy. Dans Android 11+, StrictMode.OnVmViolationListener a été introduit pour le filtrage programmatique des violations par trace de pile.
// Filtrage des faux positifs via penaltyListener
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyListener { violation ->
val stack = violation.stackTraceToString()
if ("com.google.firebase" !in stack) {
logViolation(violation)
}
}
.build()
)
StrictMode n'est pas le seul outil de contrôle qualité dans l'écosystème Android. Pour comprendre sa place, comparons-le avec Android Lint, Android Profiler et Perfetto selon des critères clés : temps de vérification, profondeur d'analyse et automatisation.
| Critère | StrictMode | Android Lint | Profiler / Perfetto |
|---|---|---|---|
| Temps de vérification | Runtime (pendant l'exécution de l'application) | Temps de compilation (avant le lancement) | Runtime (post-mortem) |
| Ce qu'il vérifie | Disque, réseau, fuites | XML, code, ressources | CPU, mémoire, réseau, énergie |
| Automatisation | CI/CD via penaltyDeath | Tâche Gradle + lint-baseline | Nécessite une analyse manuelle |
| Profondeur | Thread UI et fuites uniquement | Analyse statique de code | Image complète des performances |
| Faux positifs | Moyens (dépendent des bibliothèques) | Faibles (règles configurées) | Aucun (mesures réelles) |
La meilleure stratégie consiste à combiner les trois approches : Android Lint détecte les erreurs évidentes au moment de la compilation (par exemple, un IdleHandler oublié), StrictMode détecte les problèmes à l'exécution, et Android Profiler / Perfetto est utilisé pour une analyse approfondie lorsque les deux premiers outils ne fournissent pas de réponses. Dans les projets réels (Google Maps, Instagram), StrictMode est introduit à la deuxième semaine de développement — juste après la mise en place de l'architecture de base.
Examinons deux scénarios réels où StrictMode aide à détecter et corriger les problèmes de performance : la lecture de SharedPreferences sur le thread principal et la fuite d'Activity via un callback non désenregistré.
Au démarrage de l'application, StrictMode avec la politique detectDiskReads détectera la lecture de SharedPreferences sur le thread principal. Solution : charger la configuration de manière asynchrone via CoroutineScope ou la mettre en cache en mémoire au démarrage. SharedPreferences lit de manière synchrone un fichier XML depuis le disque — même avec un petit fichier (1–2 Ko), l'opération prend 1–5 ms, et jusqu'à 20 ms sur les appareils bon marché, ce qui peut entraîner une perte d'image.
// ❌ Code problématique — lecture de SharedPrefs dans le thread UI
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// StrictMode detectDiskReads → VIOLATION !
prefs = getSharedPreferences("config", MODE_PRIVATE)
}
}
// ✅ Code corrigé — lecture via Coroutine
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loadConfigAsync()
}
}
StrictMode avec VmPolicy.detectActivityLeaks détectera une Activity qui a quitté la pile (finish a été appelé) mais dont l'objet Activity reste en mémoire en raison d'une référence statique ou d'un callback non désenregistré. Scénario typique : enregistrement d'EventBus ou de LocationListener dans onResume sans appeler unregister dans onPause. VmPolicy affichera une trace de pile indiquant la ligne où la référence a été créée.
// ❌ Fuite — callback non annulé
private var locationCallback: LocationCallback? = null
override fun onResume() {
super.onResume()
locationCallback = LocationCallback(this::onLocationUpdated)
locationManager.register(locationCallback) // StrictMode → FUITE !
}
override fun onPause() {
super.onPause()
// Oubli : locationManager.unregister(locationCallback)
}
Foire aux questions
StrictMode ajoute effectivement une petite surcharge — chaque appel système est vérifié par rapport aux politiques. L'impact sur les performances est de 1–3 % dans les builds de débogage et est absent dans les builds de version (où StrictMode est désactivé). Lors de l'activation de detectAll sur les anciens appareils (Android 6–8), la surcharge peut atteindre 5 %, il est donc recommandé de configurer uniquement les politiques nécessaires.
Oui, StrictMode est entièrement compatible avec Jetpack Compose. Les politiques de disque et de réseau fonctionnent au niveau du framework, indépendamment du framework UI. De plus, dans Compose, la criticité des blocages UI est plus élevée — Compose redessine les images à 120 FPS sur les appareils à taux de rafraîchissement élevé, donc 5 ms supplémentaires sur la lecture de fichier deviennent plus perceptibles.
À partir d'Android 8.1 (API 27), SharedPreferences peut utiliser la mise en cache en mémoire — si le fichier a déjà été lu, une relecture ne déclenchera pas StrictMode. Assurez-vous d'appeler getSharedPreferences pour la première fois (lecture à froid) et que la politique detectDiskReads est active. Vérifiez également que StrictMode n'a pas été remplacé dans un fragment sans parent.
Dans les tests JUnit, utilisez StrictMode.allowThreadDiskReads() et StrictMode.allowThreadDiskWrites() dans @Before, et restaurez les paramètres dans @After via StrictMode.enableDefaults(). Pour les tests d'instrumentation, utilisez un TestRunner personnalisé avec préservation temporaire de la politique d'origine. Dans les tests Espresso, il est pratique d'envelopper le code sensible à StrictMode dans une IdlingResource.
StrictMode fonctionne uniquement sur la plateforme Android via le SDK Android. Dans Kotlin Multiplatform (KMP), le code commonMain ne peut pas utiliser StrictMode, mais pour androidMain, vous pouvez l'ajouter comme d'habitude. Pour la partie iOS, utilisez un équivalent — l'assertion DispatchQueue.main.async pour le thread principal.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi