Réparer maintenant
- Déposez le fichier
- La réparation s'exécute en local
- Téléchargez le résultat
Les deux couches de persistance d'Android écrivent du SQLite ordinaire : Room génère son schéma par-dessus le même moteur que celui qu'utilise l'ancien SQLiteOpenHelper, et toutes deux déposent un unique fichier dans /data/data/<paquet>/databases/<nom>.db. Lorsque ce fichier est endommagé, Room lève android.database.sqlite.SQLiteDatabaseCorruptException: database disk image is malformed (code 11 SQLITE_CORRUPT) et l'app se ferme brutalement ou affiche un écran vide. Déposez ci-dessus le .db extrait — et ajoutez son fichier annexe -wal dans l'emplacement optionnel si vous l'avez — et l'outil analyse les pages, reconstruit le schéma et réinsère chaque ligne qu'il parvient encore à décoder dans une base de données neuve, le tout à l'intérieur de l'onglet de votre navigateur. Le fichier n'est jamais envoyé.
Où une app Android range sa base de données — et ce qu'elle contient
Sur n'importe quel appareil Android, la base de données réside dans le bac à sable privé de l'app, dans /data/data/<paquet>/databases/, généralement sous la forme de trois fichiers : le principal <nom>.db, un journal d'écriture anticipée <nom>.db-wal et un index en mémoire partagée <nom>.db-shm. Le fichier principal commence par la chaîne magique de 16 octets "SQLite format 3\000" ; la taille de page sur deux octets se trouve à l'offset 16, et les octets de version de lecture/écriture du format, aux offsets 18 et 19, valent 2 lorsque la base de données est en mode WAL — que Room active par défaut.
Room ajoute des empreintes qui vous permettent de confirmer que vous avez le bon fichier. Il crée une table android_metadata contenant une unique ligne de paramètres régionaux (par exemple en_US) — cette table est écrite par la classe SQLiteDatabase d'Android pour toute base de données d'app, qu'elle utilise Room ou non — et une room_master_table avec les colonnes id et identity_hash, où Room enregistre le hash du schéma sur la ligne fixe id = 42. Vos tables d'entités (annotées @Entity) et leurs index se trouvent à côté, toutes répertoriées dans la table de schéma sqlite_master (aliasée sqlite_schema dans les versions récentes de SQLite). Une app avec un SQLiteOpenHelper pur se passe de room_master_table, mais reste par ailleurs le même fichier SQLite standard.
Comment le fichier devient 'malformed' — et les erreurs exactes
Une base de données SQLite est un tableau plat de pages de taille fixe (4096 octets par défaut). La page 1 porte l'en-tête et le schéma ; chaque table et chaque index est un arbre B dont les pages internes contiennent des pointeurs de cellules qui descendent vers les pages feuilles, et les feuilles conservent vos enregistrements réels. Le moteur lit l'en-tête, trouve le schéma dans sqlite_master, puis suit ces pointeurs jusqu'aux lignes.
Les pointeurs sont le point faible. Lorsque le processus de l'app est tué par le low-memory killer d'Android au milieu d'une écriture, que le téléphone tombe en panne de batterie, que le stockage sature, ou que deux processus ouvrent le même fichier sans verrouillage correct, une seule page interne ou un champ de l'en-tête peut se retrouver incohérent. SQLite s'arrête alors au premier problème et rejette la totalité du fichier avec database disk image is malformed — code de résultat 11 (SQLITE_CORRUPT) — même si les pages feuilles situées au-delà de la cassure contiennent encore des enregistrements intacts et auto-descriptifs. Une défaillance apparentée, file is not a database — code de résultat 26 (SQLITE_NOTADB) — signifie que la chaîne magique de 16 octets de l'en-tête est incorrecte : soit ces octets d'en-tête ont été endommagés (les pages situées derrière sont souvent intactes et récupérables), soit le fichier est réellement autre chose, comme une base de données chiffrée avec SQLCipher qui se lit comme du bruit de haute entropie.
Un piège propre à Android rend cela urgent : le DefaultDatabaseErrorHandler.onCorruption() du framework supprime le fichier de la base de données lorsqu'il détecte une corruption à l'ouverture. Ainsi, une app qui a rencontré l'erreur « malformed » peut avoir déjà effacé le fichier lors de son démarrage suivant. Copiez la base de données hors de l'appareil — et travaillez sur cette copie — avant de rouvrir l'app.
Les fichiers annexes -wal et -shm : extrayez les trois
Room active le journal d'écriture anticipée par défaut (enableWriteAheadLogging()), ce qui signifie que les modifications validées récentes sont mises en attente dans le fichier voisin <nom>.db-wal jusqu'à ce qu'un checkpoint les reverse dans le .db principal. Après un plantage, vos lignes les plus récentes peuvent ne vivre que dans ce WAL. Extrayez le fichier principal sans lui et il manquera à la base récupérée tout ce qui a été écrit depuis le dernier checkpoint — une raison fréquente pour laquelle les gens pensent que « la moitié de mes données a disparu ».
Le WAL n'est pas la base de données principale : il commence par un en-tête de 32 octets dont le nombre magique est 0x377f0682 ou 0x377f0683 (le bit de poids faible choisit des sommes de contrôle de trame en big-endian ou en little-endian), suivi de trames composées chacune d'un en-tête de trame de 24 octets plus une image de page. Le fichier -shm n'est qu'un index en mémoire partagée vers le WAL ; il est régénéré automatiquement et peut être laissé sur place sans risque. Extrayez donc ensemble le .db principal et le -wal : déposez ci-dessus la base de données, puis ajoutez le -wal dans l'emplacement annexe optionnel ; ses trames validées sont superposées avant l'analyse, de sorte que la récupération reflète la dernière transaction validée plutôt que le dernier checkpoint.
Sortir la base de données de l'appareil en toute sécurité
Sur une build débogable, vous pouvez atteindre le dossier privé avec run-as : par exemple adb exec-out run-as <paquet> tar c ./databases | tar xv, ou copier les fichiers vers le stockage partagé avec run-as <paquet> cp databases/<nom>.db /sdcard/ puis adb pull. Les apps de release (non débogables) refusent run-as : il vous faut donc un appareil rooté ou la fonction d'export propre à l'app. L'ancienne voie adb backup -f backup.ab <paquet> a été dépréciée dans Android 12 et respecte android:allowBackup, ce qui la rend peu fiable pour cela. Quelle que soit la méthode, récupérez <nom>.db, <nom>.db-wal et <nom>.db-shm en une seule fois pour que le WAL corresponde au fichier principal.
Une fois la copie sur votre machine, la vérification classique est sqlite3 name.db "PRAGMA integrity_check;" (ou le plus rapide quick_check), et la réparation classique est la commande .recover propre à SQLite, qui lit chaque ligne décodable et l'écrit dans un fichier tout neuf. Cette page fait exactement cela dans le navigateur : elle parcourt les pages, reconstruit les arbres B, recourt à un balayage brut des pages pour les lignes que la chaîne de pointeurs n'atteint plus, et place tout ce qu'elle ne peut pas rattacher à une table connue dans une table lost_and_found plutôt que de l'écarter — ainsi rien n'est jeté et rien n'est envoyé.
Ce que cela peut et ne peut pas réparer
Peut réparer
- "database disk image is malformed (code 11 SQLITE_CORRUPT)" dû à une page ou à un pointeur de cellule d'arbre B endommagés, avec des pages feuilles intactes derrière
- Un en-tête erroné ou endommagé (champ de taille de page ou de nombre de pages incorrect) quand les pages qui suivent restent lisibles
- Des lignes sur des pages que la chaîne de pointeurs n'atteint plus, extraites par un balayage brut des pages vers une table lost_and_found
- Des lignes sans checkpoint issues d'un fichier annexe
.db-wal fourni (les données les plus récentes après un plantage) - Des bases de données Room et SQLiteOpenHelper copiées depuis l'appareil avant que le gestionnaire d'erreurs de l'app ne les supprime
Ne peut pas réparer
- Des lignes physiquement écrasées ou tronquées en fin de fichier — ces octets n'existent plus
- Des bases de données chiffrées avec SQLCipher (Signal, ou des apps Room utilisant une SupportFactory) sans la phrase secrète — les pages sont du texte chiffré
- Une base de données que DefaultDatabaseErrorHandler a déjà supprimée — c'est d'abord un problème de récupération de données de l'appareil
- Les affinités de colonne exactes : les valeurs reviennent sous leur classe de stockage sur disque (NULL/INTEGER/REAL/TEXT/BLOB)
- Un fichier .db de 0 octet — il n'y a rien sur le disque à décoder
Si une réparation échoue, nous vous expliquons pourquoi (données manquantes ou structure endommagée), et vous n'êtes jamais facturé pour une réparation échouée.