Импорт и экспорт базы данных не работает в Android Pie

Ниже приведен рабочий метод импорта и экспорта базы данных SQLite. Он отлично работает во всех версиях Android, кроме Android Pie. Когда я пытаюсь импортировать в Android Pie, он показывает успешный тост, но база данных не восстанавливается. Может ли кто-нибудь помочь мне с обходным решением в Android Pie (API 28).

private void importDB() {

    try {
        File sd = Environment.getExternalStorageDirectory();
        File cur_db_pat = new File(this.getDatabasePath(DATABASE_NAME).getAbsolutePath());

        if (sd.canWrite()) {
            String backupDBPath = bac_dir_nam +"/" + DATABASE_NAME;
            File currentDB = new File(sd, backupDBPath);

            FileChannel src = new FileInputStream(currentDB).getChannel();
            FileChannel dst = new FileOutputStream(cur_db_pat).getChannel();
            dst.transferFrom(src, 0, src.size());
            src.close();
            dst.close();
            Toast.makeText(getBaseContext(), cur_db_pat.toString(),
                    Toast.LENGTH_LONG).show();
        }
    } catch (Exception e) {

        Toast.makeText(getBaseContext(), e.toString(), Toast.LENGTH_LONG)
                .show();

    }
}

private void exportDB() {

    try {
        File sd = Environment.getExternalStorageDirectory();
        File cur_db_pat = new File(this.getDatabasePath(DATABASE_NAME).getAbsolutePath());

        if (sd.canWrite()) {
            String backupDBPath = bac_dir_nam+"/" + DATABASE_NAME;
            File backupDB = new File(sd, backupDBPath);

            FileChannel src = new FileInputStream(cur_db_pat).getChannel();
            FileChannel dst = new FileOutputStream(backupDB).getChannel();
            dst.transferFrom(src, 0, src.size());
            src.close();
            dst.close();
            Toast.makeText(getBaseContext(), backupDB.toString(),
                    Toast.LENGTH_LONG).show();

        }
    } catch (Exception e) {

        Toast.makeText(getBaseContext(), e.toString(), Toast.LENGTH_LONG)
                .show();

    }
}

У меня нет большого опыта работы с файловой системой. Так что пример очень поможет.


person ItsRedwan    schedule 05.01.2019    source источник
comment
Вы запросили разрешение на доступ к SD-карте у пользователя? См., например. stackoverflow .com/questions/33139754/   -  person Robert    schedule 05.01.2019


Ответы (3)


В Android Pie+ SQLite был изменен на использование по умолчанию более эффективного ведения журнала упреждающей записи (WAL) вместо режима журнала.

Таким образом, будет два файла с тем же именем, что и у базы данных, но с суффиксом -shm (файл в общей памяти) и -wal (журнал с упреждающей записью), и их присутствие то, что я считаю, вызывает проблему (ы). Временные файлы, используемые SQLite (см. 2.2 и 2.3)

Одним из решений может быть отключение ведения журнала опережающей записи с использованием метода SQliteDatabase disableWriteAheadLogging, и предыдущий метод будет работать, как и раньше, но с менее эффективным режимом журнала.

  • (при использовании подкласса SQliteOpenHelper переопределите метод onConfigure для вызова этого метода.) disableWriteAheadLogging.

Еще одно исправление — удалить эти два файла при восстановлении. Чтобы избежать возможности повреждения, вы должны убедиться, что база данных была правильно проверена перед созданием резервной копии. см. контрольную точку PRAGMA;

Ниже приведен фрагмент, который удаляет эти два файла при восстановлении (отмечая, что резервная копия, как предполагается, была сделана с соответствующей контрольной точкой): -

                    // Added for Android 9+ to delete shm and wal file if they exist
                    File dbshm = new File(dbfile.getPath() + "-shm");
                    File dbwal = new File(dbfile.getPath()+ "-wal");
                    if (dbshm.exists()) {
                        dbshm.delete();
                    }
                    if (dbwal.exists()) {
                        dbwal.delete();
                    }

Еще одно исправление заключается в дополнительном резервном копировании и последующем восстановлении файлов -shm и -wal.

Вы также можете рассмотреть потенциальные преимущества переименования исходных файлов при импорте/восстановлении, проверки новых файлов после их копирования (например, с помощью PRAGMA Integrity_check;), если результаты указывают на отсутствие проблем, удалите переименованные исходные файлы, в противном случае удалите импортированные файлы и переименуйте исходные файлы в исходное имя, указывая, что импорт не удалось.

person MikeT    schedule 05.01.2019
comment
Если восстанавливается старый файл базы данных, можно ли просто восстановить файл .db без файла журнала? Или мне также нужно выполнить миграцию файла .db? Если да, то знаете ли вы, как правильно перенести старый файл .db в режим журнала? - person prom85; 26.06.2019
comment
@prom85 Режим журнала — это журнал примененных обновлений, позволяющий откатить/отменить их, поэтому журнал не требуется. Однако WAL представляет собой журнал обновлений, которые еще предстоит применить, и поэтому обновления в WAL необходимы и, следовательно, необходимо их зафиксировать/выполнить/применить, если только обновления не нужны. Таким образом, файлы -shm и -wal, если они не пусты, должны быть либо зафиксированы (что очистит их), либо файлы должны быть скопированы в дополнение к основному файлу базы данных. - person MikeT; 29.06.2019
comment
большое спасибо, я знаю, что такое файлы режима WAL, но еще не знал, для чего нужен файл журнала. Таким образом, переход из журнала в режим WAL является сохранением, а не наоборот. Перед переходом из режима WAL в режим журнала мне нужно будет передать все ожидающие изменения из вспомогательных файлов WAL в файл базы данных. - person prom85; 01.07.2019
comment
а если закрыть БД? похоже, файлы wal и shm удалены. Так не лучше ли закрыть базу данных, чем скопировать только один файл, содержащий базу данных, и то же самое для восстановления? - person Zhar; 04.01.2020
comment
@Zhar да, это покрывается адекватно контрольной точкой. Однако, как правило, эта проблема связана с тем, как база данных проверяется на наличие, и, к сожалению, многие люди копируют/адаптируют код, который делает это, открывая базу данных через SQLite (getWritable/readable), который затем создает файл -wal, так что это - wal, который обычно вызывает проблему и удаляется. Конечно, лучше всего проверить наличие файла базы данных. Этот вопрос не показывает проверку, но симптомы указывают на это. - person MikeT; 04.01.2020
comment
@MikeT Что вы думаете об ответе Бронза, который ниже вашего? - person Михаил; 27.06.2020

В вашем классе для метода Db WorkHelper ovverride onOpen() и установите disableWriteAheadLogging, затем вызовите стандарт onOpen(), если версия android sdk 28, обязательно, что старая версия останется старой модальностью.

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

В моем случае РАБОТАЕТ идеально.

person Bronz    schedule 01.09.2019
comment
Без необходимости что-либо менять, я напрямую скопировал фрагмент, который вы предоставили. В моем случае тоже сработало, спасибо большое - person mears; 20.10.2019
comment
Еще раз этот ответ оказался полезным. Так сложно найти решение этой проблемы! - person androidneil; 19.08.2020

В отличие от того, что предлагали другие комментаторы, вы не можете полагаться на базу данных, состоящую из одного файла после отключения ведения журнала упреждающей записи, и вы не можете предполагать, что имена файлов -shl и -wal остаются правильными. Это все детали реализации sqlite3/Android, поэтому они могут быть изменены в любое время (так же, как старый код сломался).

Один из способов сделать это, который, как я ожидаю, будет продолжать работать, — это использовать команду sqlite3 .dump для преобразования базы данных в SQL, который впоследствии можно будет выполнить для воссоздания базы данных.

Я не тестировал следующее, но ожидаю, что что-то похожее на него должно работать:

// Copyright 2021 Google LLC.
// SPDX-License-Identifier: Apache-2.0

// Untested:
    private byte[] exportDatabase(SQLiteDatabase database) throws IOException {
        Process process = new ProcessBuilder()
                .command("/system/bin/sqlite3", database.getPath(), ".dump")
                .redirectOutput(ProcessBuilder.Redirect.PIPE)
                .start();
        try (InputStream inputStream = process.getInputStream()) {
            // [read the full contents of inputStream and save them somewhere]
            return ByteStreams.toByteArray(inputStream);
        } finally {
            waitForProcess(process);
        }
    }

    private void importDatabase(String databasePath, InputStream backedUpData) throws IOException {
        // restore the database:
        Process process = new ProcessBuilder()
                .command("/system/bin/sqlite3", databasePath)
                .redirectInput(ProcessBuilder.Redirect.PIPE)
                .start();
        try (OutputStream outputStream = process.getOutputStream()) {
            // now write the backed-up contents back to outputStream
            ByteStreams.copy(backedUpData, outputStream);
        } 
        waitForProcess(process);
    }

    private static void waitForProcess(Process process) {
        try {
            process.waitFor();
        } catch (InterruptedException e) {
            // ignore interruption, restore interrupt flag
            Thread.currentThread().interrupt();
        }
    }

Очевидно, вам нужно будет убедиться, что:

  • База данных, резервную копию которой вы создаете, в настоящее время не открыта.
  • База данных, которую вы восстанавливаете, еще не существует.
person Tobias    schedule 12.07.2021