Android P-'SQLite: sem tal erro de tabela' depois de copiar banco de dados de ativos




android-9.0-pie (8)

Eu tenho um banco de dados salvo na minha pasta de ativos de aplicativos e copio o banco de dados usando o código abaixo quando o aplicativo é aberto pela primeira vez.

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;
        }

O código acima é executado sem problemas, mas quando você tenta consultar o banco de dados, obtém um SQLite: Nenhuma exceção de tabela.

Esse problema ocorre apenas no Android P, todas as versões anteriores do Android funcionam corretamente.

Este é um problema conhecido com o Android P ou algo mudou?


Aqui está a solução perfeita para este problema:

Apenas sobrescreva este método na sua classe SQLiteOpenHelper :

 @Override public void onOpen(SQLiteDatabase db) { super.onOpen(db); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { db.disableWriteAheadLogging(); } } 

Esse problema parece levar a uma falha muito mais frequentemente no Android P do que nas versões anteriores, mas não é um bug no próprio Android P.

O problema é que sua linha na qual você atribui o valor a seu String filePath abre uma conexão com o banco de dados que permanece aberto quando você copia o arquivo dos ativos.

Para corrigir o problema, substitua a linha

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

com código para obter o valor do caminho do arquivo e, em seguida, feche o banco de dados:

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

E também adicione uma classe auxiliar interna:

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) {
    }
}

Eu me deparei com um problema semelhante. Eu estava copiando um banco de dados, mas não de um ativo. O que eu descobri é que o problema não tinha nada a ver com o código de cópia do meu arquivo de banco de dados. Nem tem a ver com arquivos deixados abertos, não fechados, flushing ou sincronização. Meu código geralmente sobrescreve um banco de dados existente aberto. O que parece ser novo / diferente no Android Pie e diferente dos lançamentos anteriores do Android, é que quando o Android Pie cria um banco de dados SQLite, ele define journal_mode como WAL (registro de write-ahead), por padrão. Eu nunca usei o modo WAL e os documentos do SQLite dizem que o journal_mode deve ser DELETE por padrão. O problema é se eu sobrescrever um arquivo de banco de dados existente, vamos chamá-lo de my.db, o log write-ahead, my.db-wal, ainda existe e efetivamente "sobrescreve" o que está no arquivo my.db recém-copiado. Quando abri meu banco de dados, a tabela sqlite_master normalmente continha apenas uma linha para android_metadata. Todas as mesas que eu estava esperando estavam faltando. Minha solução é simplesmente definir journal_mode de volta para DELETE após abrir o banco de dados, especialmente ao criar um novo banco de dados com o Android Pie.

PRAGMA journal_mode = DELETE;

Talvez o WAL seja melhor e provavelmente haja alguma maneira de fechar o banco de dados para que o log write-ahead não atrapalhe, mas eu realmente não preciso do WAL e não precisei dele para todas as versões anteriores do Android.


Infelizmente, a resposta aceita apenas "acontece de funcionar" em casos muito concretos, mas não fornece um conselho de trabalho consistente para evitar esse erro no Android 9.

Aqui está:

  1. Tenha uma única instância da classe SQLiteOpenHelper em seu aplicativo para acessar seu banco de dados.
  2. Se você precisar reescrever / copiar o banco de dados, feche o banco de dados (e feche todas as conexões desse banco de dados) usando o método SQLiteOpenHelper.close () desta instância E não use mais essa instância SQLiteOpenHelper.

Depois de chamar close (), não apenas todas as conexões com o banco de dados são fechadas, mas os arquivos de log adicionais do banco de dados são liberados para o arquivo .sqlite principal e excluídos. Então você tem apenas um arquivo database.sqlite, pronto para ser reescrito ou copiado.

  1. Depois de copiar / reescrever, etc. crie um novo singleton do SQLiteOpenHelper, que o método getWritableDatabase () retornará uma nova instância do banco de dados SQLite! E use-o até a próxima vez que você precisar que seu banco de dados seja copiado / reescrito ...

Essa resposta me ajudou a descobrir isso: https://.com/a/35648781/297710

Eu tive esse problema no Android 9 no meu aplicativo AndStatus https://github.com/andstatus/andstatus que tem um conjunto bastante grande de testes automatizados que consistentemente reproduziam "SQLiteException: no such table" no emulador Android 9 antes deste commit: https://github.com/andstatus/andstatus/commit/1e3ca0eee8c9fbb8f6326b72dc4c393143a70538 Então, se você está realmente curioso, pode executar todos os testes antes e depois desse commit para ver a diferença.


Não posso comentar a resposta aceita, por isso devo abrir uma nova resposta.

mContext.getDatabasePath () NÃO abre uma conexão com o banco de dados, nem requer um nome de arquivo existente para ter sucesso (veja sources / android-28 / android / app / ContextImpl.java):

 @Override public File getDatabasePath(String name) { File dir; File f; if (name.charAt(0) == File.separatorChar) { // snip } else { dir = getDatabasesDir(); f = makeFilename(dir, name); } return f; } private File makeFilename(File base, String name) { if (name.indexOf(File.separatorChar) < 0) { return new File(base, name); } throw new IllegalArgumentException( "File " + name + " contains a path separator"); } 

Parece que você não fecha o fluxo de saída. Embora isso provavelmente não explique por que o db não é realmente criado (a menos que o Android P tenha adicionado um buffer multi MB), é uma boa prática usar um try-with-resource, algo como:

// 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();
}

Problema semelhante, apenas dispositivo Android P afetado. Todas as versões anteriores sem problemas.

Desativou a restauração automática em dispositivos Android 9.

Nós fizemos isso para solucionar problemas. Não recomendaria para casos de produção.

A restauração automática estava colocando uma cópia do arquivo de banco de dados no diretório de dados antes que a função de banco de dados de cópia fosse chamada no auxiliar de banco de dados. Portanto, o arquivo file.exists () retornou true.

O banco de dados do qual foi feito backup do dispositivo de desenvolvimento estava faltando na tabela. Portanto, "nenhuma tabela encontrada" estava correta.


Resposta mais simples para usar a seguinte linha para o caminho do arquivo de banco de dados no Android PIE e acima:

DB_NAME="xyz.db";
DB_Path = "/data/data/" + BuildConfig.APPLICATION_ID + "/databases/"+DB_NAME;






android-9.0-pie