Android P - 'SQLite: нет такой ошибки таблицы' после копирования базы данных из активов

У меня есть база данных, сохраненная в папке с ресурсами моего приложения, и я копирую базу данных, используя приведенный ниже код, когда приложение впервые открывается.

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

Приведенный выше код работает без проблем, но когда вы пытаетесь запросить базу данных, вы получаете SQLite: нет такого исключения таблицы.

Эта проблема возникает только в Android P, все более ранние версии Android работают правильно.

Это известная проблема с Android P или что-то изменилось?

вы можете проверить, не является ли ваш inputStream нулевым? с помощью отладчика Android?

Deepak kaku 23.05.2018 00:30

Я могу подтвердить, что InputStream не равен нулю.

Michael J 23.05.2018 01:34

Непосредственно перед return true добавить Log.d("COPYINFO","Bytes Copied = " + String.valueOf(totalcount) + " to " + filepath);, каков будет результат в Журнал?

MikeT 23.05.2018 01:43

Вывод на моем работающем устройстве под управлением Android 8.1: (D / COPYINFO: Bytes Copied = 1687552 to /data/user/0/am.radiogr/databases/s.db) Вывод на Android P: (D / COPYINFO : Bytes Copied = 1687552 в /data/user/0/am.radiogr/databases/s.db) Они точно такие же.

Michael J 23.05.2018 12:38

Мой следующий шаг, поскольку очевидно, что БД копируется, должен был проверить таблицы в базе данных. Либо запрашиваю таблицу sqlite_master, либо лично я использую здесь метод logDatabaseInfo [Существуют ли какие-либо методы, которые помогают в решении общих проблем SQLite? ] (stackoverflow.com/questions/46642269/…).

MikeT 23.05.2018 13:09

Используя logDatabaseInfo, я получаю следующее: D/SQLITE_CSU: DatabaseList Row 1 Name=main File=/data/user/0/my.packagename/databases/databasename.db Database Version = 1 D/SQLITE_CSU: Table Name = android_metadata Created Using = CREATE TABLE android_metadata (locale TEXT) D/SQLITE_CSU: Table = android_metadata ColumnName = locale ColumnType = TEXT Default Value = null PRIMARY KEY SEQUENCE = 0 Это, я полагаю, показывает пустую базу данных на Android P. При тестировании на Android 8.1 он регистрирует все таблицы, которые я ожидал увидеть.

Michael J 23.05.2018 17:35

@MichaelJ да, это пустая / необработанная база данных. К сожалению, у меня нет устройства / эмулятора 8.1 для тестирования. Есть еще один вопрос, очень похожий на ваш, и мне интересно, есть ли проблема. Я немного осмотрелся, но не нашел никаких проблем с 8.1.

MikeT 24.05.2018 02:08

вы копируете свою базу данных в папку "/ data / data / <packagename> / database"?

Ashish John 31.05.2018 14:26

Отключение ведения журнала с упреждающей записью, как указал Рамон ниже, сработало для меня. stackoverflow.com/a/51953955/1172181

Luis 05.10.2018 14:36
53
9
20 022
15
Перейти к ответу Данный вопрос помечен как решенный

Ответы 15

Похоже, вы не закрываете выходной поток. Хотя это, вероятно, не объясняет, почему db на самом деле не создается (если Android P не добавил буфер с несколькими МБ), рекомендуется использовать try-with-resource, например:

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

К сожалению, закрытие FileOutputStream не повлияло. Также переключение на метод «попробовать с ресурсами» привело к такой же пустой базе данных.

Michael J 31.05.2018 15:53
Ответ принят как подходящий

Эта проблема, кажется, приводит к сбою гораздо чаще на Android P, чем на предыдущих версиях, но это не ошибка самого Android P.

Проблема в том, что ваша строка, в которой вы присваиваете значение своему String filePath, открывает соединение с базой данных, которое остается открытым, когда вы копируете файл из ресурсов.

Чтобы устранить проблему, замените строку

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

с кодом, чтобы получить значение пути к файлу, а затем закрыть базу данных:

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

А также добавьте внутренний вспомогательный класс:

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

Спасибо за постоянную помощь. Я заменил код на ваш, чтобы получить filePath, но результат сохраняется. Пустая база данных создается и не заполняется базой данных в папке ресурсов. Я должен добавить, что тестирую с помощью эмулятора под управлением Android P, а не реального устройства, это не должно мешать работе кода, как задумано, не так ли?

Michael J 01.06.2018 12:54

Я могу воспроизвести проблему на эмуляторе, это не проблема. Возможно, вам стоит убедиться, что мгновенный запуск отключен, на всякий случай. В любом случае проблема решается для меня, когда я проверяю, что нет открытых подключений к базе данных перед копированием файла из ресурсов. Это ключ.

rmtheis 01.06.2018 18:01

Извините, вы были правы. Я упустил из виду, что раньше называл этот (довольно бессмысленный) код: openOrCreateDatabase(Utils.getDatabaseName(), MODE_PRIVATE, null); Это поддерживало открытое соединение с базой данных и вызывало проблему. С тех пор я удалил этот код, и все работает нормально. Спасибо за помощь!

Michael J 02.06.2018 12:21

Хороший! Рад, что ты понял это.

rmtheis 02.06.2018 16:21

Отключение ведения журнала с упреждающей записью, как указал Рамон ниже, сработало для меня. stackoverflow.com/a/51953955/1172181

Luis 05.10.2018 14:35

@rmtheis все еще есть проблема, как узнать, создана ли база данных? каждый раз, когда вы вызываете helper.getReadableDatabase (); чтобы получить путь, по которому вы создаете базу данных, поэтому проверка того, существует ли она, всегда верна, даже до ее создания. Итак ... как мы можем узнать, создан он или нет, чтобы заполнить его содержимым базы данных ресурсов только в первый раз?

NullPointerException 08.10.2018 19:38

Этот ответ у меня не сработал, поэтому я, наконец, решил его, используя другой ответ: stackoverflow.com/questions/52706557/…

NullPointerException 08.10.2018 20:58

Оно работает. Я могу скопировать файл db в базу данных после использования SQLiteOpenHelper, снова подключить старую базу данных и закрыть ее.

JackWu 18.07.2019 08:39

Во-первых, спасибо за вопрос. Со мной случилось то же самое. Все работало хорошо, но потом при тестировании с Android P Preview у меня возникали сбои. Вот ошибка, которую я нашел для этого кода:

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

    }
}

Проблема, с которой я столкнулся, заключалась в том, что этот код работает нормально, НО в SDK 28+ openOrCreateDatabase больше не создает для вас автоматически таблицу android_metadata. Таким образом, если вы выполните запрос «выберите * из ТАБЛИЦЫ», он не найдет эту ТАБЛИЦУ, потому что запрос начинает искать «первую» таблицу, которая должна быть таблицей метаданных. Я исправил это, вручную добавив таблицу android_metadata, и все было хорошо. Надеюсь, кто-то еще сочтет это полезным. На это потребовалась целая вечность, потому что конкретные запросы по-прежнему работали нормально.

как добавить таблицу android_metadata? Вы не сказали этого в своем ответе

NullPointerException 08.10.2018 20:39

Я добавил его с помощью браузера БД для SQLite. С помощью этой программы вы можете добавлять таблицы вручную.

lil_matthew 09.10.2018 16:30

Я столкнулся с подобной проблемой. Я копировал базу данных, но не из актива. Я обнаружил, что проблема вообще не связана с кодом копирования файла базы данных. И это не имело отношения к файлам, оставленным открытыми, незакрытыми, сброшенными или синхронизируемыми. Мой код обычно перезаписывает существующую неоткрытую базу данных. Что кажется новым / отличным от Android Pie и отличается от предыдущих выпусков Android, так это то, что когда Android Pie создает базу данных SQLite, он по умолчанию устанавливает для journal_mode значение WAL (ведение журнала с упреждающей записью). Я никогда не использовал режим WAL, и в документации SQLite сказано, что journal_mode по умолчанию должен быть DELETE. Проблема в том, что если я перезаписываю существующий файл базы данных, назовем его my.db, журнал упреждающей записи my.db-wal все еще существует и эффективно «переопределяет» то, что находится во вновь скопированном файле my.db. Когда я открывал свою базу данных, таблица sqlite_master обычно содержала только строку для android_metadata. Все столы, которые я ожидал, отсутствовали. Мое решение - просто вернуть journal_mode значение DELETE после открытия базы данных, особенно при создании новой базы данных с Android Pie.

PRAGMA journal_mode = УДАЛИТЬ;

Возможно, WAL лучше, и, вероятно, есть способ закрыть базу данных, чтобы журнал упреждающей записи не мешал, но мне действительно не нужен WAL, и он не нужен для всех предыдущих версий Android.

Это отличное объяснение и более подробное, чем другие. Спасибо

CaptainCrunch 09.12.2020 16:45

Была аналогичная проблема, и я решил ее добавить в мой SQLiteOpenHelper

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

Судя по всему, Android P настраивает PRAGMA Log по-другому. Все еще не знаю, будут ли побочные эффекты, но, похоже, работает!

Хороший ответ, но для придирки onConfigure - лучшее место для этого. В javadoc для onConfigure специально упоминается, что это место для таких вещей, как enableWriteAheadLogging. В моем тестировании оба места работают для решения проблемы на Android 9.

TalkLittle 11.11.2018 03:14

Работай со мной! Если "проблема" только на 9 версии Android, в моем случае: if (Build.VERSION.SDK_INT> = 28) {database.disableWriteAheadLogging ();}

Bronz 01.09.2019 06:23

я тоже застрял с той же проблемой, что он отлично работал на устройствах ниже версии 9, и теперь с вашим решением он отлично работает и на Pie, спасибо

Annie 11.06.2020 12:26

Меня устраивает. У меня была ошибка только на Android9. Благодарность

Ehsan 02.07.2020 14:54

Это работает для меня, но если мы отключим WAL, он уменьшит количество записей на 10% до 15% в соответствии с этим source.android.com/devices/tech/perf/compatibility-wal

Sagar Vasoya 08.08.2020 13:56

Подобная проблема затронула только устройство Android P. Все предыдущие версии без проблем.

Отключено автоматическое восстановление на устройствах Android 9.

Мы сделали это для устранения неполадок. Не рекомендую для производственных корпусов.

При автоматическом восстановлении копия файла базы данных помещалась в каталог данных до вызова функции копирования базы данных в помощнике базы данных. Следовательно, a file.exists () вернул true.

В базе данных, для которой была создана резервная копия с устройства разработки, отсутствует таблица. Следовательно, «таблица не найдена» на самом деле было правильным.

Эй, я сейчас столкнулся с той же проблемой. Я не могу понять, как он может восстановить копию файла, но без таблицы, как это вообще возможно? Есть какие-нибудь подсказки, как обойти это в продакшене?

Shyri 23.10.2018 19:59

Мои проблемы с Android P были решены добавлением 'this.close ()' после this.getReadableDatabase () в методе createDataBase (), как показано ниже.

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

Я столкнулся с точной проблемой, решение тоже сработало, но мне интересно узнать о проблеме, почему и как эта проблема решена в Android P?

buzzingsilently 02.01.2019 08:25

Спасибо! я потратил 3 дня на поиски решения, но почему?

Ayoub Anbara 02.11.2019 22:34

Решение без отключения WAL

Android 9 представляет специальный режим SQLiteDatabase, называемый Совместимость WAL (вход в систему с упреждающей записью), который позволяет базе данных использовать «journal_mode = WAL», сохраняя при этом поведение, заключающееся в сохранении максимум одного соединения для каждой базы данных.

Подробно здесь:
https://source.android.com/devices/tech/perf/compatibility-wal

Режим SQLite WAL подробно описан здесь:
https://www.sqlite.org/wal.html

Согласно официальной документации, режим WAL добавляет второй файл базы данных, называемый имя базы данных и "-wal". Итак, если ваша база данных называется «data.db», она называется «data-wal.db» в том же каталоге.

Теперь решение состоит в том, чтобы сохранять и восстанавливать файлы ОБА (data.db и data-wal.db) на Android 9.

В дальнейшем он работает как в более ранних версиях.

как я могу сгенерировать файл data-wal.db? Может ли эмулятор его сгенерировать? Потому что проблема возникает на реальных устройствах, а не на эмуляторах.

Muhammad Saqib 17.01.2020 12:21

К сожалению, принятый ответ просто «работает» в очень конкретных случаях, но он не дает постоянно работающего совета, как избежать такой ошибки в Android 9.

Вот:

  1. Имейте в своем приложении единственный экземпляр класса SQLiteOpenHelper для доступа к базе данных.
  2. Если вам нужно переписать / скопировать базу данных, закройте базу данных (и закройте все соединения с этой базой данных) с помощью метода SQLiteOpenHelper.close () этого экземпляра И больше не используйте этот экземпляр SQLiteOpenHelper.

После вызова close () не только все подключения к базе данных закрываются, но и дополнительные файлы журнала базы данных сбрасываются в основной файл .sqlite и удаляются. Итак, у вас есть только один файл database.sqlite, готовый к перезаписи или копированию.

  1. После копирования / перезаписи и т. д. Создайте новый синглтон SQLiteOpenHelper, метод getWritableDatabase () которого вернет новый экземпляр базы данных SQLite! И используйте его до тех пор, пока в следующий раз вам не понадобится скопировать / переписать вашу базу данных ...

Этот ответ помог мне понять это: https://stackoverflow.com/a/35648781/297710

У меня была эта проблема в Android 9 в моем приложении AndStatus https://github.com/andstatus/andstatus, которое имеет довольно большой набор автоматических тестов, которые последовательно воспроизводили «SQLiteException: no such table» в эмуляторе Android 9 до этого коммита: https://github.com/andstatus/andstatus/commit/1e3ca0eee8c9fbb8f6326b72dc4c393143a70538 Итак, если вам действительно интересно, вы можете запустить все тесты до и после этого коммита, чтобы увидеть разницу.

Самый простой ответ - использовать следующую строку для пути к файлу базы данных в Android PIE и выше:

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

Вот идеальное решение этой проблемы:

Просто переопределите этот метод в своем классе SQLiteOpenHelper:

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

Я не могу комментировать принятый ответ, поэтому должен открыть новый ответ.

mContext.getDatabasePath () НЕ открывает соединение с базой данных, ему даже не требуется существующее имя файла для успешного выполнения (см. 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");
}

В версии P основным изменением является WAL (Write Ahead Log). Требуются следующие два шага.

  1. Отключите то же самое с помощью следующей строки в config.xml в папке значений в разделе ресурсов.

ложный

  1. Внесите следующие изменения в класс DBAdapter в методе createDatabase. В противном случае произойдет сбой телефонов с более ранними версиями Android.

    private void createDataBase () выбрасывает IOException {

    if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.P) {
                    this.getWritableDatabase();
        try {           
            copyDataBase();            
        } catch (IOException e) {           
            throw new RuntimeException(e);
        }
    }
    

    }

У меня было то же самое, что у меня было приложение в версии 4 для Android, и при обновлении моего мобильного телефона с Android 9 я 2 дня пытался найти ошибку, спасибо за комментарии, в моем случае мне просто пришлось добавить это. Закрыть ();

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

готово работает для всех версий !!

Проблема, возникающая в Android Pie, Решение:

 SQLiteDatabase db = this.getReadableDatabase();
        if (db != null && db.isOpen())
            db.close();
   copyDataBase();

Другие вопросы по теме