Предварительно заполненная база данных не работает на API 28, выдает исключение «нет такой таблицы» - PullRequest
1 голос
/ 25 марта 2019

Я использую предварительно заполненную базу данных в своем проекте. Я создал базу .sql и скопировал ее при первом запуске. База большая 33mb.

    private void copyDataBase() throws IOException {

    InputStream externalDbStream = context.getAssets().open(DB_NAME);

    String outFileName = DB_PATH + DB_NAME;

    OutputStream localDbStream = new FileOutputStream(outFileName);

    byte[] buffer = new byte[1024];
    int bytesRead;
    while ((bytesRead = externalDbStream.read(buffer)) > 0) {
        localDbStream.write(buffer, 0, bytesRead);
    }
    localDbStream.close();
    externalDbStream.close();

}

Он отлично работает на разных версиях Android, кроме API 28. API 28 выдает «Причина: android.database.sqlite.SQLiteException: нет такой таблицы: фразы» исключение:

Caused by: android.database.sqlite.SQLiteException: no such table: phrases (code 1 SQLITE_ERROR): , while compiling: select * from phrases where complexity > 1 and known is null  or known == 1   order by complexity limit 10
    at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method)
    at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:903)
    at android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:514)
    at android.database.sqlite.SQLiteSession.prepare(SQLiteSession.java:588)
    at android.database.sqlite.SQLiteProgram.<init>(SQLiteProgram.java:58)
    at android.database.sqlite.SQLiteQuery.<init>(SQLiteQuery.java:37)
    at android.database.sqlite.SQLiteDirectCursorDriver.query(SQLiteDirectCursorDriver.java:46)

Заранее спасибо.

1 Ответ

4 голосов
/ 25 марта 2019

Типичная причина того, что приложение, использующее SQLite и копирующее ранее существующую базу данных, неожиданно не работает для API 28, заключается в том, что для решения проблемы не существующей папки база данных (копия потерпеть неудачу, если каталог не существует) - создать пустую базу данных, а затем перезаписать базу данных.

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

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

Быстрое исправление

Быстрое, но не рекомендуемое исправление - переопределить метод onConfigure в классе, который подклассов SQLiteOpenHelper, чтобы использовать метод disableWriteAheadLogging , чтобы база данных открывается в режиме журнала.

  • полный код ниже (второй фрагмент кода) включает это, но строка закомментирована.

Рекомендуемое исправление

Рекомендованный метод, чтобы воспользоваться преимуществами WAL , состоит в проверке существования каталога базы данных и создании каталога, если он не существует, вместо создания базы данных, которая будет перезаписан ( и, следовательно, файлы -shm и -wal не существуют при копировании базы данных )

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

private boolean checkDataBase() {
    /**
     * Does not open the database instead checks to see if the file exists
     * also creates the databases directory if it does not exists
     * (the real reason why the database is opened, which appears to result in issues)
     */

    File db = new File(myContext.getDatabasePath(DB_NAME).getPath()); //Get the file name of the database
    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;
}
  • Обратите внимание, что это зависит от переменной DB_NAME, являющейся именем базы данных (имя файла базы данных), и что окончательное местоположение базы данных является стандартным местоположением (data / data / the_package / database /).

Вышеприведенное было извлечено из следующего подкласса SQLiteOpenHelper: -

public class DBHelper extends SQLiteOpenHelper {

    private static String DB_NAME = "db";
    private SQLiteDatabase myDataBase;
    private final Context myContext;

    private int bytes_copied = 0;
    private static int buffer_size = 1024;
    private int blocks_copied = 0;

    public DBHelper(Context context) {
        super(context, DB_NAME, null, 1);

        this.myContext = context;
        // Check for and create (copy DB from assets) when constructing the DBHelper
        if (!checkDataBase()) {
            bytes_copied = 0;
            blocks_copied = 0;
            createDataBase();
        }
    }

    /**
     * Creates an empty database on the system and rewrites it with your own database.
     * */
    public void createDataBase() {

        boolean dbExist = checkDataBase(); // Double check
        if(dbExist){
            //do nothing - database already exists
        } else {
            //By calling this method an empty database will be created into the default system path
            //of your application so we are gonna be able to overwrite that database with our database.
            //this.getReadableDatabase();
            //<<<<<<<<<< Dimsiss the above comment
            //By calling this method an empty database IS NOT created nor are the related -shm and -wal files
            //The method that creates the database is flawed and was only used to resolve the issue
            //of the copy failing in the absence of the databases directory.
            //The dbExist method, now utilised, checks for and creates the database directory, so there
            //is then no need to create the database just to create the databases library. As a result
            //the -shm and -wal files will not exist and thus result in the error associated with
            //Android 9+ failing with due to tables not existining after an apparently successful
            //copy.
            try {
                copyDataBase();
            } catch (IOException e) {
                File db = new File(myContext.getDatabasePath(DB_NAME).getPath());
                if (db.exists()) {
                    db.delete();
                }
                e.printStackTrace();
                throw new RuntimeException("Error copying database (see stack-trace above)");
            }
        }
    }

    /**
     * Check if the database already exist to avoid re-copying the file each time you open the application.
     * @return true if it exists, false if it doesn't
     */
    private boolean checkDataBase() {
        /**
         * Does not open the database instead checks to see if the file exists
         * also creates the databases directory if it does not exists
         * (the real reason why the database is opened, which appears to result in issues)
         */

        File db = new File(myContext.getDatabasePath(DB_NAME).getPath()); //Get the file name of the database
        Log.d("DBPATH","DB Path is " + db.getPath()); //TODO remove for Live 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;
    }

    /**
     * Copies your database from your local assets-folder to the just created empty database in the
     * system folder, from where it can be accessed and handled.
     * This is done by transfering bytestream.
     * */
    private void copyDataBase() throws IOException {

        final String TAG = "COPYDATABASE";

        //Open your local db as the input stream
        Log.d(TAG,"Initiated Copy of the database file " + DB_NAME + " from the assets folder."); //TODO remove for Live App
        InputStream myInput = myContext.getAssets().open(DB_NAME); // Open the Asset file
        String dbpath = myContext.getDatabasePath(DB_NAME).getPath();
        Log.d(TAG,"Asset file " + DB_NAME + " found so attmepting to copy to " + dbpath); //TODO remove for Live App

        // Path to the just created empty db
        //String outFileName = DB_PATH + DB_NAME;
        //Open the empty db as the output stream
        File outfile = new File(myContext.getDatabasePath(DB_NAME).toString());
        Log.d("DBPATH","path is " + outfile.getPath()); //TODO remove for Live App
        //outfile.setWritable(true); // NOT NEEDED as permission already applies
        //OutputStream myoutputx2 = new FileOutputStream(outfile);
        /* Note done in checkDatabase method
        if (!outfile.getParentFile().exists()) {
            outfile.getParentFile().mkdirs();
        }
        */

        OutputStream myOutput = new FileOutputStream(outfile);
        //transfer bytes from the inputfile to the outputfile
        byte[] buffer = new byte[buffer_size];
        int length;
        while ((length = myInput.read(buffer))>0) {
            blocks_copied++;
            Log.d(TAG,"Ateempting copy of block " + String.valueOf(blocks_copied) + " which has " + String.valueOf(length) + " bytes."); //TODO remove for Live App
            myOutput.write(buffer, 0, length);
            bytes_copied += length;
        }
        Log.d(TAG,
                "Finished copying Database " + DB_NAME +
                        " from the assets folder, to  " + dbpath +
                        String.valueOf(bytes_copied) + "were copied, in " +
                        String.valueOf(blocks_copied) + " blocks of size " +
                        String.valueOf(buffer_size) + "."
        ); //TODO remove for Live App
        //Close the streams
        myOutput.flush();
        myOutput.close();
        myInput.close();
        Log.d(TAG,"All Streams have been flushed and closed."); //TODO remove for Live App
    }


    @Override
    public synchronized void close() {
        if(myDataBase != null)
            myDataBase.close();
        super.close();
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }

    @Override
    public void onConfigure(SQLiteDatabase db) {
        super.onConfigure(db);
        Log.d("DBCONFIGURE","Database has been configured "); //TODO remove for Live App
        //db.disableWriteAheadLogging(); //<<<<<<<<<< un-comment to force journal mode
    }

    @Override
    public void onOpen(SQLiteDatabase db) {
        super.onOpen(db);
        Log.d("DBOPENED","Database has been opened."); //TODO remove for live App
    }
}
  • Обратите внимание, что приведенный выше код предназначен / предназначен для разработки / экспериментов и, следовательно, включает в себя код, который можно удалить.
...