Récupérer la base de données SQLite corrompue d'une app Android

Les apps Android conservent leur état dans SQLite : un plantage au démarrage se résume donc le plus souvent à une seule page ou à un pointeur cassés dans un fichier .db — pas à des données perdues. Room et SQLiteOpenHelper écrivent une base de données SQLite 3 standard, et les lignes sont presque toujours encore présentes dans leurs pages. Les reconstruire à partir d'une copie que vous avez extraite de l'appareil n'implique jamais de confier cette base de données à un serveur.

Votre fichier ne quitte jamais votre appareil — la réparation s'exécute dans votre navigateur. 0 octet téléversé.

Your data is the big block — usually intact. What breaks is the small index. Repair rebuilds it.

Drop a file here or browseTap to choose a file

ZIP · Office · PDF · video · JPG · PNG · RAR/7z · SQLite — repaired right here in your browser. Nothing is uploaded

0 bytes uploadedFree: 3 repairs/day — up to 500 MB video, 100 MB docs & archives, 50 MB photos. Free download. Single repair €5.90. No account required.

Réparer maintenant

  1. Déposez le fichier
  2. La réparation s'exécute en local
  3. 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.

Questions fréquentes

Mon app a supprimé sa propre base de données après le plantage, pourquoi ?

Le DefaultDatabaseErrorHandler.onCorruption() d'Android supprime le fichier lorsqu'il détecte SQLITE_CORRUPT à l'ouverture, si bien qu'un second démarrage peut l'effacer. C'est pourquoi la solution consiste à copier d'abord le .db (et son -wal) hors de l'appareil avec adb run-as et à récupérer depuis la copie — jamais depuis le fichier en direct que l'app est encore en train d'ouvrir.

J'ai extrait le .db mais des données récentes manquent. Que s'est-il passé ?

Room fonctionne en mode WAL par défaut : les lignes validées les plus récentes se trouvent donc dans <nom>.db-wal, pas encore reversées dans le fichier principal. Extrayez les trois fichiers — .db, -wal et -shm — et ajoutez le -wal dans l'emplacement annexe optionnel ; ses trames validées sont superposées avant l'analyse afin que la dernière transaction soit incluse.

Est-ce que ça marche avec une base de données Room, ou seulement avec du SQLite brut ?

Avec les deux — Room est du SQLite standard en dessous. La récupération reconstruit android_metadata, room_master_table (y compris l'identity_hash enregistré sur id = 42) et vos tables @Entity. Si le hash du schéma parvient intact, Room rouvre le fichier reconstruit sans se plaindre de ne pas pouvoir vérifier l'intégrité des données.

Chez moi l'erreur dit plutôt 'file is not a database'. Est-ce récupérable ?

Parfois. Il s'agit de SQLITE_NOTADB (code 26) : la chaîne magique de 16 octets de l'en-tête est incorrecte. Si seuls les octets de l'en-tête ont été endommagés, les pages situées derrière sont en général intactes et récupérables. Si tout le fichier est du bruit de haute entropie, c'est une base de données chiffrée (SQLCipher) ou pas une base de données du tout — cela ne peut pas être récupéré sans la clé. Le diagnostic distingue les deux cas.

Ma base de données est-elle envoyée pour être réparée ?

Non. La base de données d'une app est le fichier que vous voudriez le moins voir sur le serveur de quelqu'un d'autre — elle peut contenir chaque enregistrement d'utilisateur, chaque message et chaque jeton que l'app a stockés. Ici, le .db est lu depuis votre disque et reconstruit dans votre navigateur ; vous pouvez observer l'onglet Réseau et confirmer que 0 octet sort.

Associés : Réparer une base de données SQLite (toute origine) · Réparer une vidéo Android · Réparer un classeur Excel · Vérifier la promesse du zéro envoi