Копирование базы данных SQLite на Oreo

В одном из своих приложений для Android я использую предварительно заполненную базу данных, которую копирую при первом использовании. Пока это хорошо работает на всех версиях Android, но с API 28 это не работает. Он копирует базу данных и может впоследствии подключиться к базе данных, но по какой-то причине ни к одной из таблиц нет доступа. Я получаю сообщение об ошибке, что таблица не существует.

Я проверил базу данных, загрузив ее из эмулятора, и с базой данных проблем нет. Другое приложение, в котором я создаю базу данных в коде, работает нормально (база данных создается, а таблицы можно найти после).

Код, который я использую для копирования базы данных:

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

Изменилось ли что-нибудь в API 28, из-за чего это по какой-то причине не работает? Что может быть причиной этого?

Да, с Android 9 есть два дополнительных файла, которые являются частью базы данных SQLite, и если вы скопируете базу данных, перекрывающую существующую базу данных, и не удалите сначала эти два других файла, тогда база данных будет считать себя поврежденной. Смотрите этот ответ. stackoverflow.com/questions/56008907/…

Michael Dougan 21.05.2019 17:36

Google В самом деле необходимо добавить поддержку API резервного копирования в свои привязки Android sqlite.

Shawn 21.05.2019 19:15

@MichaelDougan, это было действительно так! С некоторыми настройками для моего конкретного приложения теперь это работает. Спасибо, если вы опубликуете это как ответ, я могу отметить его как принятый ответ

ehusmann 21.05.2019 20:26
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
3
379
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Я не могу быть уверен, пока не увижу logcat. Однако я думаю, что у вас может отсутствовать какое-то разрешение при доступе к вашей базе данных.

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

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

И вам нужен класс DatabaseContentProvider, подобный следующему.

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

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

Ответ принят как подходящий

Проблема с включенным API 28 заключается в том, что WAL (ведение журнала с опережающей записью) включен по умолчанию. Если, как это часто бывает, база данных открывается, что часто делается при проверке существования базы данных, то создаются два файла - wal и -shm.

например в вашем коде это указывает на то, что вы делаете это: -

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

Когда существующая база данных заменяется копией, файлы -wal и -shm остаются.

Когда база данных в конечном итоге пытается быть открыта как SQLiteDatabase, возникает несоответствие между базой данных и файлами -wal и -shm. Затем базовая SQLiteDatabase open решает, что базу данных нельзя открыть, и поэтому, чтобы предоставить пригодную для использования базу данных, удаляет и воссоздает базу данных, эффективно очищая базу данных, и, следовательно, возникает ситуация.

Есть три способа обойти это

  1. Переопределите метод onConfigure и заставьте его использовать старый режим журнала (используя [disableWriteAheadLogging](метод https://developer.android.com/reference/android/arch/persistence/db/SupportSQLiteDatabase#disablewriteaheadlogging)).
  2. Используйте открытый метод SQLiteDatabase, чтобы проверить, существует ли база данных, но чтобы проверить, существует ли файл.
  3. В рамках копирования удалите файлы -wal и -shm.

Я бы предположил, что 2 или 3 являются лучшими способами: -

  • Нет никаких накладных расходов на ненужное открытие (и создание) базы данных, просто для проверки ее существования и создания каталогов.
  • Приложение, вероятно, будет улучшено из-за преимущества WAL по сравнению с ведением журнала.

Все вышеперечисленное описано в Отправка приложения для Android с предварительно заполненной базой данных.

Код решения фактически включает в себя как 2, так и 3 (хотя исправление 3 не требуется (поскольку исправление 2 не открывает базу данных как SQLiteDatabase)).

Говоря, что я считаю, что следующий код для проверки существования базы данных (исправление 2) лучше, чем его эквивалент в ответе выше: -

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

Да, с Android 9 есть два дополнительных файла, которые являются частью базы данных SQLite, и если вы скопируете базу данных, перекрывающую существующую базу данных, и не удалите сначала эти два других файла, тогда база данных будет считать себя поврежденной. См. этот ответ: Отправка приложения для Android с предварительно заполненной базой данных

Но @MikeT должен получить признание, так как связанный ответ был его с самого начала!

Очень верно. Сначала его ответа еще не было, поэтому ваш комментарий помог мне решить проблему. Однако теперь я принял его как правильный ответ. Тем не менее спасибо за помощь!

ehusmann 27.05.2019 14:33

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