2
votes

Copie de la base de données SQLite sur Oreo

Dans l'une de mes applications Android, j'utilise une base de données pré-remplie que je copie lors de la première utilisation. Cela fonctionne bien sur toutes les versions d'Android jusqu'à présent, mais avec l'API 28, cela échoue. Il copie la base de données et peut se connecter à la base de données par la suite, mais pour une raison quelconque, aucune des tables n'est accessible. J'obtiens l'erreur que la table n'existe pas.

J'ai vérifié la base de données en la téléchargeant depuis l'émulateur et il n'y a pas de problème avec la base de données. Une autre application où je crée la base de données en code fonctionne très bien (la base de données est créée et les tables peuvent être trouvées après).

Le code que j'utilise pour copier la base de données:

public void copyDatabase() throws IOException{
    InputStream myInput = mContext.getAssets().open(mDbSource);

    // Path to the just created empty db
    String outFileName = getDbPath() + mDbName;

    //Open the empty db as the output stream
    OutputStream myOutput = new FileOutputStream(outFileName);

    //transfer bytes from the inputfile to the outputfile
    byte[] buffer = new byte[1024];
    int length;
    while ((length = myInput.read(buffer))>0){
        myOutput.write(buffer, 0, length);
    }

    //Close the streams
    myOutput.flush();
    myOutput.close();
    myInput.close();
}

private String getDbPath(){
    return "/data/data/"+mContext.getResources().getString(R.string.package_name)+"/databases/";
}


3 commentaires

Oui, avec Android 9, il y a deux fichiers supplémentaires qui font partie de la base de données SQLite, et si vous copiez une base de données superposant une base de données existante et ne supprimez pas ces deux autres fichiers en premier, la base de données se considérera comme corrompue. Voyez cette réponse. stackoverflow.com / questions / 56008907 /…


Google a vraiment besoin d'ajouter la prise en charge de l'API de sauvegarde à ses liaisons sqlite android.


@MichaelDougan c'était bien ça! Avec quelques ajustements pour mon application spécifique, cela fonctionne maintenant. Merci, si vous le postez comme réponse, je peux le marquer comme réponse acceptée


3 Réponses :


0
votes

Je ne peux pas être sûr tant que je n'ai pas vu le logcat. Cependant, je pense que vous pourriez avoir une autorisation manquante lors de l'accès à votre base de données.

Je voudrais demander à vérifier si vous avez le fournisseur suivant dans votre fichier AndroidManifest.xml pour donner l'autorisation à votre application d'accéder à la base de données. Voici un exemple de fichier manifeste ayant le fournisseur .

import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

public class DatabaseContentProvider extends ContentProvider {

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        return null;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri,
                        @Nullable String[] projection,
                        @Nullable String selection,
                        @Nullable String[] selectionArgs,
                        @Nullable String sortOrder) {
        return null;
    }

    @Override
    public boolean onCreate() {
        return true;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values,
                      @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }
}

Et vous avez besoin d'une classe DatabaseContentProvider comme celle-ci.

<provider
    android:name=".DatabaseContentProvider"
    android:authorities="your.application.package.name"
    android:exported="false" />

J'ai un exemple de code dans Github pour les opérations de base de données sous Android où vous pouvez également jeter un coup d'œil.


0 commentaires

1
votes

Le problème avec l'API 28 est que WAL (Write-Ahead Logging) est activé par défaut. Si, comme c'est souvent le cas, la base de données est ouverte, souvent lors de la vérification pour voir si la base de données existe, alors deux fichiers les fichiers -wal et -shm sont créés.

par exemple dans votre code, cela semble indiquer que c'est ce que vous faites: -

/**
 * Check if the database already exists. NOTE will create the databases folder is it doesn't exist
 * @return true if it exists, false if it doesn't
 */
public static boolean checkDataBase(Context context, String dbname) {

    File db = new File(context.getDatabasePath(dbname).getPath()); //Get the file name of the database
    Log.d("DBPATH","DB Path is " + db.getPath()); //TODO remove if publish App
    if (db.exists()) return true; // If it exists then return doing nothing

    // Get the parent (directory in which the database file would be)
    File dbdir = db.getParentFile();
    // If the directory does not exits then make the directory (and higher level directories)
    if (!dbdir.exists()) {
        db.getParentFile().mkdirs();
        dbdir.mkdirs();
    }
    return false;
}

Lorsque la base de données existante est remplacée par la copie, les fichiers -wal et -shm restent .

Lorsque la base de données est finalement tentée d'être ouverte en tant que SQLiteDatabase, alors une discordance entre la base de données et les fichiers -wal et -shm est soulevée. La base de données SQLite sous-jacente ouverte décide alors que la base de données ne peut pas être ouverte et ainsi, afin de fournir une base de données utilisable, supprime et recrée la base de données en vidant efficacement la base de données et donc la situation se présente.

Il y a trois façons de contourner cela

  1. Remplacez la méthode onConfigure et forcez-la à utiliser l'ancien mode journal (à l'aide de [disableWriteAheadLogging] ( https://developer.android.com/reference/android/arch/persistence/db/SupportSQLiteDatabase#disablewriteaheadlogging méthode)).
  2. Utilisez une méthode ouverte SQLiteDatabase pour vérifier si la base de données existe mais pour vérifier si le fichier existe.
  3. Dans le cadre de la copie, supprimez les fichiers -wal et -shm.

Je suggérerais que 2 ou 3 sont les meilleurs moyens comme: -

  • Il n'y a pas de surcharge liée à l'ouverture (et à la création) de la base de données inutilement, juste pour vérifier son existence et pour créer les répertoires.
  • L'application sera probablement améliorée grâce aux avantages d'un WAL par rapport à la journalisation du journal. li>

Tout ce qui précède est couvert dans Ship application Android avec base de données pré-remplie .

Le code de la solution incorpore en fait à la fois 2 et 3 (bien que le correctif 3 ne devrait pas être requis (car le correctif 2 n'ouvre pas la base de données en tant que SQLiteDatabase)).

Dire que je crois que le code suivant pour vérifier si la base de données existe (correction 2) est meilleur que son équivalent dans la réponse ci-dessus: -

// Path to the just created empty db
String outFileName = getDbPath() + mDbName;


0 commentaires

0
votes

Oui, avec Android 9, il y a deux fichiers supplémentaires qui font partie de la base de données SQLite, et si vous copiez une base de données superposant une base de données existante et ne supprimez pas ces deux autres fichiers en premier, la base de données se considérera comme corrompue . Consultez cette réponse: Expédier une application Android avec une base de données pré-remplie

Mais @MikeT devrait avoir le mérite, car la réponse liée était la sienne au départ !!


1 commentaires

Très vrai. Au début, sa réponse n'était pas encore là, c'est donc votre commentaire qui m'a aidé à résoudre le problème. Cependant, j'ai maintenant accepté la sienne comme la bonne réponse. Néanmoins, merci pour l'aide!