web-dev-qa-db-fra.com

Android P - "SQLite: pas d'erreur de table" après la copie de la base de données à partir d'actifs

Une base de données est enregistrée dans le dossier des ressources de mes applications et je la copie à l'aide du code ci-dessous lors de la première ouverture de l'application.

inputStream = mContext.getAssets().open(Utils.getDatabaseName());

        if(inputStream != null) {

            int mFileLength = inputStream.available();

            String filePath = mContext.getDatabasePath(Utils.getDatabaseName()).getAbsolutePath();

            // Save the downloaded file
            output = new FileOutputStream(filePath);

            byte data[] = new byte[1024];
            long total = 0;
            int count;
            while ((count = inputStream.read(data)) != -1) {
                total += count;
                if(mFileLength != -1) {
                    // Publish the progress
                    publishProgress((int) (total * 100 / mFileLength));
                }
                output.write(data, 0, count);
            }
            return true;
        }

Le code ci-dessus fonctionne sans problème, mais lorsque vous essayez d'interroger la base de données, vous obtenez une exception SQLite: No table.

Ce problème ne survient que sous Android P, toutes les versions antérieures d'Android fonctionnent correctement.

Est-ce un problème connu avec Android P ou quelque chose a changé?

26
Michael J

Ce problème semble conduire à un crash beaucoup plus souvent sur Android P que sur les versions précédentes, mais ce n'est pas un bug sur Android P lui-même.

Le problème est que votre ligne où vous attribuez la valeur à votre String filePath ouvre une connexion à la base de données qui reste ouverte lorsque vous copiez le fichier à partir d'actifs.

Pour résoudre le problème, remplacez la ligne

String filePath = mContext.getDatabasePath(Utils.getDatabaseName()).getAbsolutePath();

avec le code pour obtenir la valeur du chemin du fichier, puis fermez la base de données:

MySQLiteOpenHelper helper = new MySQLiteOpenHelper();
SQLiteDatabase database = helper.getReadableDatabase();
String filePath = database.getPath();
database.close();

Et ajoutez également une classe d'aide interne:

class MySQLiteOpenHelper extends SQLiteOpenHelper {

    MySQLiteOpenHelper(Context context, String databaseName) {
        super(context, databaseName, null, 2);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}
19
rmtheis

Avait un problème similaire, et résolu ceci en ajoutant ceci à mon SQLiteOpenHelper

    @Override
    public void onOpen(SQLiteDatabase db) {
        super.onOpen(db);
        db.disableWriteAheadLogging();
    }

Apparemment, Android P différencie le journal PRAGMA. Toujours aucune idée si aura des effets secondaires, mais semble fonctionner!

34
Ramon Canales

Mes problèmes avec Android P ont été résolus en ajoutant 'This.close ()' après this.getReadableDatabase () dans la méthode createDataBase () comme ci-dessous.

private void createDataBase() throws IOException {
    this.getReadableDatabase();
    this.close(); 
    try {           
        copyDataBase();            
    } catch (IOException e) {           
        throw new RuntimeException(e);
    }
}
14
aaru

J'ai rencontré un problème similaire. Je copiais une base de données mais pas d'un actif. Ce que j'ai trouvé, c'est que le problème n'avait rien à voir avec le code de copie de mon fichier de base de données. Cela ne concernait pas non plus les fichiers laissés ouverts, non fermés, vidés ou synchronisés. Mon code remplace généralement une base de données existante non ouverte. Ce qui semble être nouveau/différent avec Android Pie et différent des versions précédentes d’Android, c’est que lorsque Android Pie crée une base de données SQLite, il définit journal_mode sur WAL (enregistrement anticipé), par défaut. Je n'ai jamais utilisé le mode WAL et la documentation SQLite indique que journal_mode doit être DELETE par défaut. Le problème est que si j'écrase un fichier de base de données existant, appelons-le mon.db, le journal d'écriture anticipée, my.db-wal, existe toujours et "remplace" le contenu du fichier my.db récemment copié. Lorsque j'ai ouvert ma base de données, la table sqlite_master ne contenait généralement qu'une ligne pour Android_metadata. Toutes les tables que je m'attendais manquaient. Ma solution consiste simplement à redéfinir journal_mode sur DELETE après l'ouverture de la base de données, en particulier lors de la création d'une nouvelle base de données avec Android Pie.

PRAGMA journal_mode = DELETE;

Peut-être que WAL est mieux et qu'il existe probablement un moyen de fermer la base de données pour que le journal d'écriture anticipée ne gêne pas, mais je n'ai pas vraiment besoin de WAL et je n'en ai pas eu besoin pour toutes les versions précédentes d'Android. 

6
KGBird

Malheureusement, la réponse acceptée ne fait que "fonctionner" dans des cas très concrets, mais elle ne donne pas un conseil de travail constant pour éviter une telle erreur dans Android 9.

C'est ici:

  1. Avoir une seule instance de la classe SQLiteOpenHelper dans votre application pour accéder à votre base de données.
  2. Si vous devez réécrire/copier la base de données, fermez la base de données (et toutes les connexions à cette base de données) à l'aide de la méthode SQLiteOpenHelper.close () de cette instance ET n'utilisez plus cette instance SQLiteOpenHelper.

Après avoir appelé close (), non seulement toutes les connexions à la base de données sont fermées, mais des fichiers journaux de base de données supplémentaires sont vidés dans le fichier .sqlite principal et supprimés. Vous n’avez donc qu’un fichier database.sqlite prêt à être réécrit ou copié.

  1. Après avoir copié/réécrité etc., créez un nouveau singleton du SQLiteOpenHelper, méthode que getWritableDatabase () renverra une nouvelle instance de la base de données SQLite! Et utilisez-le jusqu'à la prochaine fois que vous aurez besoin de copier/réécrire votre base de données ...

Cette réponse m'a aidé à comprendre cela: https://stackoverflow.com/a/35648781/297710

J'ai eu ce problème dans Android 9 dans mon application AndStatus https://github.com/andstatus/andstatus qui dispose d'une suite de tests automatisés assez volumineuse qui reproduit systématiquement "SQLiteException: aucune table" dans l'émulateur Android 9 auparavant. commit: https://github.com/andstatus/andstatus/commit/1e3ca0eee8c9fbb8f6326b72dc4c393143a70538 Donc, si vous êtes vraiment curieux, vous pouvez exécuter Tous les tests avant et après cela commettent une différence.

1
yvolk

Solution sans désactiver le WAL

Android 9 introduit un mode spécial de SQLiteDatabase appelé Compatibility WAL (loggin d'écriture différée) qui permet à une base de données d'utiliser "journal_mode = WAL" tout en préservant le maintien d'une connexion maximale par base de données.

En détail ici:
https://source.Android.com/devices/tech/perf/compatibility-wal

Le mode SQLite WAL est expliqué en détail ici:
https://www.sqlite.org/wal.html

À partir de la documentation officielle, le mode WAL ajoute un fichier de base de données second appelé databasename et "-wal". Donc, si votre base de données s'appelle "data.db", elle s'appelle "data-wal.db" dans le même répertoire.

La solution consiste maintenant à enregistrer et à restaurer LES DEUX FICHIERS (data.db et data-wal.db) sur Android 9.

Ensuite, cela fonctionne comme dans les versions précédentes.

1
chrisonline

Problème similaire, seul le périphérique Android P concerné. Toutes les versions précédentes pas de problèmes.

Désactiver la restauration automatique sur les appareils Android 9.

Nous avons fait cela pour résoudre les problèmes. Ne recommande pas pour les cas de production. 

La restauration automatique consistait à placer une copie du fichier de base de données dans le répertoire data avant que la fonction de copie de base de données ne soit appelée dans l'assistant de base de données. Par conséquent, le fichier file.exists () renvoie true. 

La base de données sauvegardée à partir du périphérique de développement ne contenait pas la table. Par conséquent, "aucune table trouvée" était en fait correcte.

1
Berry Wing

Tout d'abord, merci d'avoir posté cette question. J'ai eu la même chose arriver. Tout fonctionnait bien, mais lors de tests avec Android P Preview, je me suis fait avoir. Voici le bug que j'ai trouvé pour ce code:

private void copyDatabase(File dbFile, String db_name) throws IOException{
    InputStream is = null;
    OutputStream os = null;

    SQLiteDatabase db = context.openOrCreateDatabase(db_name, Context.MODE_PRIVATE, null);
    db.close();
    try {
        is = context.getAssets().open(db_name);
        os = new FileOutputStream(dbFile);

        byte[] buffer = new byte[1024];
        while (is.read(buffer) > 0) {
            os.write(buffer);
        }
    } catch (IOException e) {
        e.printStackTrace();
        throw(e);
    } finally {
        try {
            if (os != null) os.close();
            if (is != null) is.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

Le problème que j'ai rencontré est que ce code fonctionne très bien MAIS dans le SDK 28+, openOrCreateDatabase ne crée plus automatiquement la table Android_metadata pour vous. Donc, si vous faites une requête de "select * from TABLE", celle-ci ne sera pas trouvée car la requête commence à rechercher la "première" table qui devrait être la table de métadonnées. J'ai résolu ce problème en ajoutant manuellement la table Android_metadata et tout allait bien. J'espère que quelqu'un trouve cela utile. Cela a pris une éternité à résoudre car des requêtes spécifiques fonctionnaient toujours bien.

1
lil_matthew

Il semble que vous ne fermez pas le flux de sortie. Bien que cela n'explique probablement pas pourquoi la base de données n'est pas réellement créée (sauf si Android P a ajouté un tampon de plusieurs Mo), il est recommandé d'utiliser un try-with-resource, comme par exemple:

// garantees that the data are flushed and the resources freed
try (FileOutputStream output = new FileOutputStream(filePath)) {
    byte data[] = new byte[1024];
    long total = 0;
    int count;
    while ((count = inputStream.read(data)) != -1) {
        total += count;
        if (mFileLength != -1) {
            // Publish the progress
            publishProgress((int) (total * 100 / mFileLength));
        }
        output.write(data, 0, count);
    }

    // maybe a bit overkill
    output.getFD().sync();
}
0
bwt