Как исправить исключение SQLiteException при попытке сделать запрос? - PullRequest
1 голос
/ 03 мая 2019

У меня есть приложение-словарь, которое работает полностью в автономном режиме с использованием базы данных, которую я предоставил. Чтобы иметь возможность использовать базу данных, я применил решение, которое я видел в блоге разработчика. Вы можете сослаться на него по этой ссылке. Решение в основном копирует базу данных из папки активов и использует ее. Он работает без проблем на большинстве устройств, но некоторые пользователи испытывают сбои, когда они пытаются выполнить запрос к базе данных. Я прикрепил полученную трассировку стека к отчету о сбое, отправленному от Asus ZenFone 5 (ZE620KL) (ASUS_X00QD). Мой вопрос: есть ли проблема в практике, которую я применяю (используя существующую базу данных, расположенную в папке ресурсов)? Что я могу сделать, чтобы избежать этого исключения?

  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)
  at android.database.sqlite.SQLiteDatabase.rawQueryWithFactory (SQLiteDatabase.java:1408)
  at android.database.sqlite.SQLiteDatabase.queryWithFactory (SQLiteDatabase.java:1255)
  at android.database.sqlite.SQLiteDatabase.query (SQLiteDatabase.java:1126)
  at android.database.sqlite.SQLiteDatabase.query (SQLiteDatabase.java:1294)
  at com.tur_cirdictionary.turkish_circassiandictionary.data.WordProvider.query (WordProvider.java:52)
  at android.content.ContentProvider.query (ContentProvider.java:1064)
  at android.content.ContentProvider.query (ContentProvider.java:1156)
  at android.content.ContentProvider$Transport.query (ContentProvider.java:241)
  at android.content.ContentResolver.query (ContentResolver.java:809)
  at android.content.ContentResolver.query (ContentResolver.java:758)
  at com.tur_cirdictionary.turkish_circassiandictionary.MainActivity.showSuggestionsForQuery (MainActivity.java:245)
  at com.tur_cirdictionary.turkish_circassiandictionary.MainActivity.access$000 (MainActivity.java:37)
  at com.tur_cirdictionary.turkish_circassiandictionary.MainActivity$2.onQueryTextChange (MainActivity.java:178)
  at android.widget.SearchView.onTextChanged (SearchView.java:1250)
  at android.widget.SearchView.access$2100 (SearchView.java:98)
  at android.widget.SearchView$10.onTextChanged (SearchView.java:1776)
  at android.widget.TextView.sendOnTextChanged (TextView.java:9784)
  at android.widget.TextView.handleTextChanged (TextView.java:9881)
  at android.widget.TextView$ChangeWatcher.onTextChanged (TextView.java:12539)
  at android.text.SpannableStringBuilder.sendTextChanged (SpannableStringBuilder.java:1263)
  at android.text.SpannableStringBuilder.replace (SpannableStringBuilder.java:575)
  at android.text.SpannableStringBuilder.replace (SpannableStringBuilder.java:506)
  at android.text.SpannableStringBuilder.replace (SpannableStringBuilder.java:36)
  at android.view.inputmethod.BaseInputConnection.replaceText (BaseInputConnection.java:843)
  at android.view.inputmethod.BaseInputConnection.commitText (BaseInputConnection.java:197)
  at com.android.internal.widget.EditableInputConnection.commitText (EditableInputConnection.java:183)
  at com.android.internal.view.IInputConnectionWrapper.executeMessage (IInputConnectionWrapper.java:341)
  at com.android.internal.view.IInputConnectionWrapper$MyHandler.handleMessage (IInputConnectionWrapper.java:85)
  at android.os.Handler.dispatchMessage (Handler.java:106)
  at android.os.Looper.loop (Looper.java:198)
  at android.app.ActivityThread.main (ActivityThread.java:6732)
  at java.lang.reflect.Method.invoke (Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:493)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:858)

WordContract.java

package com.tur_cirdictionary.turkish_circassiandictionary.data;

import android.content.ContentResolver;
import android.net.Uri;
import android.provider.BaseColumns;

public final class WordContract {

    public static final String CONTENT_AUTHORITY =
            "com.tur_cirdictionary.turkish_circassiandictionary";

    public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);

    public static final String PATH_WORDS = "words";

    private WordContract() {}

    public static class WordEntry implements BaseColumns {

        public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, PATH_WORDS);

        public static final String CONTENT_LIST_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/" +
                CONTENT_AUTHORITY + PATH_WORDS;

        public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" +
                CONTENT_AUTHORITY + "/" + PATH_WORDS;

        public static final String TABLE_NAME = "words";
        public static final String _ID = BaseColumns._ID;
        public static final String COLUMN_NAME_CIRCASSIAN = "circassian";
        public static final String COLUMN_NAME_TURKISH = "turkish";
    }
}

WordDbHelper.java

package com.tur_cirdictionary.turkish_circassiandictionary.data;

import android.content.Context;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.tur_cirdictionary.turkish_circassiandictionary.data.WordContract.WordEntry;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class WordDbHelper extends SQLiteOpenHelper {

    private static final int DATABASE_VERSION = 1;
    private static String DATABASE_PATH
            = "/data/data/com.tur_cirdictionary.turkish_circassiandictionary/databases/";
    private static final String DATABASE_NAME = "Cir_Tur.sqlite";
    private SQLiteDatabase database;
    private final Context context;

    public WordDbHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        this.context = context;
    }

    public void createDatabase() {
        boolean dbExist = checkDataBase();
        if (dbExist) {
            //Cool. Don't do anything.
        } else {
            try {
                this.getReadableDatabase();
                copyDataBase();
            } catch (IOException e) {
                throw new Error("Error copying database");
            }
        }
    }

    private boolean checkDataBase() {
        SQLiteDatabase checkDB = null;
        try {
            String path = DATABASE_PATH + DATABASE_NAME;
            checkDB = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY);
        } catch (SQLException e) {
            //Database does not exist
        }
        if (checkDB != null) {
            checkDB.close();
        }
        return checkDB != null;
    }

    private void copyDataBase() throws IOException {
        InputStream inputStream = context.getAssets().open(DATABASE_NAME);
        String outFileName = DATABASE_PATH + DATABASE_NAME;
        OutputStream outputStream = new FileOutputStream(outFileName);
        byte[] buffer = new byte[1024];
        int length;
        while ((length = inputStream.read(buffer)) > 0) {
            outputStream.write(buffer, 0, length);
        }
        outputStream.flush();
        outputStream.close();
        inputStream.close();
    }

    public SQLiteDatabase openDatabase() {
        String path = DATABASE_PATH + DATABASE_NAME;
        database = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY);
        return database;
    }

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

    @Override
    public void onCreate(SQLiteDatabase db) {

    }

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

    }

    private static final String SQL_CREATE_WORDS =
            "CREATE TABLE  " + WordEntry.TABLE_NAME + " ("
                    + WordEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
                    + WordEntry.COLUMN_NAME_CIRCASSIAN + " TEXT, "
                    + WordEntry.COLUMN_NAME_TURKISH + " TEXT)";

    private static final String SQL_DELETE_WORDS =
            "DROP TABLE IF EXISTS " + WordContract.WordEntry.TABLE_NAME;
}

WordProvider.java

package com.tur_cirdictionary.turkish_circassiandictionary.data;

import android.content.Context;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.tur_cirdictionary.turkish_circassiandictionary.data.WordContract.WordEntry;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class WordDbHelper extends SQLiteOpenHelper {

    private static final int DATABASE_VERSION = 1;
    private static String DATABASE_PATH
            = "/data/data/com.tur_cirdictionary.turkish_circassiandictionary/databases/";
    private static final String DATABASE_NAME = "Cir_Tur.sqlite";
    private SQLiteDatabase database;
    private final Context context;

    public WordDbHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        this.context = context;
    }

    public void createDatabase() {
        boolean dbExist = checkDataBase();
        if (dbExist) {
            //Cool. Don't do anything.
        } else {
            try {
                this.getReadableDatabase();
                copyDataBase();
            } catch (IOException e) {
                throw new Error("Error copying database");
            }
        }
    }

    private boolean checkDataBase() {
        SQLiteDatabase checkDB = null;
        try {
            String path = DATABASE_PATH + DATABASE_NAME;
            checkDB = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY);
        } catch (SQLException e) {
            //Database does not exist
        }
        if (checkDB != null) {
            checkDB.close();
        }
        return checkDB != null;
    }

    private void copyDataBase() throws IOException {
        InputStream inputStream = context.getAssets().open(DATABASE_NAME);
        String outFileName = DATABASE_PATH + DATABASE_NAME;
        OutputStream outputStream = new FileOutputStream(outFileName);
        byte[] buffer = new byte[1024];
        int length;
        while ((length = inputStream.read(buffer)) > 0) {
            outputStream.write(buffer, 0, length);
        }
        outputStream.flush();
        outputStream.close();
        inputStream.close();
    }

    public SQLiteDatabase openDatabase() {
        String path = DATABASE_PATH + DATABASE_NAME;
        database = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY);
        return database;
    }

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

    @Override
    public void onCreate(SQLiteDatabase db) {

    }

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

    }

    private static final String SQL_CREATE_WORDS =
            "CREATE TABLE  " + WordEntry.TABLE_NAME + " ("
                    + WordEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
                    + WordEntry.COLUMN_NAME_CIRCASSIAN + " TEXT, "
                    + WordEntry.COLUMN_NAME_TURKISH + " TEXT)";

    private static final String SQL_DELETE_WORDS =
            "DROP TABLE IF EXISTS " + WordContract.WordEntry.TABLE_NAME;

}

MainActivity.java

package com.tur_cirdictionary.turkish_circassiandictionary.data;

import android.content.Context;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.tur_cirdictionary.turkish_circassiandictionary.data.WordContract.WordEntry;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class WordDbHelper extends SQLiteOpenHelper {

    private static final int DATABASE_VERSION = 1;
    private static String DATABASE_PATH
            = "/data/data/com.tur_cirdictionary.turkish_circassiandictionary/databases/";
    private static final String DATABASE_NAME = "Cir_Tur.sqlite";
    private SQLiteDatabase database;
    private final Context context;

    public WordDbHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        this.context = context;
    }

    public void createDatabase() {
        boolean dbExist = checkDataBase();
        if (dbExist) {
            //Cool. Don't do anything.
        } else {
            try {
                this.getReadableDatabase();
                copyDataBase();
            } catch (IOException e) {
                throw new Error("Error copying database");
            }
        }
    }

    private boolean checkDataBase() {
        SQLiteDatabase checkDB = null;
        try {
            String path = DATABASE_PATH + DATABASE_NAME;
            checkDB = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY);
        } catch (SQLException e) {
            //Database does not exist
        }
        if (checkDB != null) {
            checkDB.close();
        }
        return checkDB != null;
    }

    private void copyDataBase() throws IOException {
        InputStream inputStream = context.getAssets().open(DATABASE_NAME);
        String outFileName = DATABASE_PATH + DATABASE_NAME;
        OutputStream outputStream = new FileOutputStream(outFileName);
        byte[] buffer = new byte[1024];
        int length;
        while ((length = inputStream.read(buffer)) > 0) {
            outputStream.write(buffer, 0, length);
        }
        outputStream.flush();
        outputStream.close();
        inputStream.close();
    }

    public SQLiteDatabase openDatabase() {
        String path = DATABASE_PATH + DATABASE_NAME;
        database = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY);
        return database;
    }

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

    @Override
    public void onCreate(SQLiteDatabase db) {

    }

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

    }

    private static final String SQL_CREATE_WORDS =
            "CREATE TABLE  " + WordEntry.TABLE_NAME + " ("
                    + WordEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
                    + WordEntry.COLUMN_NAME_CIRCASSIAN + " TEXT, "
                    + WordEntry.COLUMN_NAME_TURKISH + " TEXT)";

    private static final String SQL_DELETE_WORDS =
            "DROP TABLE IF EXISTS " + WordContract.WordEntry.TABLE_NAME;
}

Она является ссылкой на весь проект, если необходимо.

Ответы [ 2 ]

3 голосов
/ 04 мая 2019

Есть ли проблема в практике, которую я применяю (используя существующую базу данных, расположенную в папке ресурсов)?

Не совсем так, как эторекомендуемая и часто используемая техника.Однако при неправильной реализации могут возникнуть проблемы, особенно сейчас.

В соответствии с быстрым поиском операционная система устройства Android 8.0 (Oreo), с возможностью обновления до Android 9.0 (Pie) ;ZenUI 5

Конечно, при использовании Android Pie выявляется 1 проблема, с которой вы могли столкнуться.

Причиной является использование this.getReadableDatabase(); до вызова метода copyDatabase .

Поскольку Android Pie включает WAL (запись с опережением записи), по умолчанию создаются два файла: -wal и -shm .Эти оставшиеся вызывают конфликт, поскольку они не соответствуют базе данных, которая была только что скопирована.Я считаю, что конечным результатом является то, что скопированная база данных удаляется и создается совершенно новая, чтобы обеспечить пригодную для использования базу данных.Как таковых таблиц и базовых данных не существует.Это часто приводит к тому, что в таблице не найдена ошибка / исключение, как правило, когда делается попытка получить доступ к данным, которых таблица не существует.

Что я могу сделать, чтобы избежать этого исключения?

Исправление 1

Простое исправление, но не рекомендуемое исправление, заключается в переопределении SQLiteOpenhelper onConfigure .в WordDbHelper.java для вызова метода disableWriteAheadLogging .

Однако применение этого исправления означает, что вы упускаете преимущества Запись в журнал с записью впереди .

Исправление 2

Правильное исправление - не использовать getReadableDatabase перед копированием.Похоже, это было историческим решением простой проблемы.Это каталог data / data / the_package / для пакета, для новой установки приложения, который не имеет каталога database .Таким образом, getWritableDatabase (или getReabableDatabase, которая получает доступную для записи базу данных, если она может) создает каталог, а также базу данных, которая затем перезаписывается.

Что приложение должно сделать, это проверить, существует ли каталог, и затем создать его,все это можно сделать в методе checkDataBase, используя что-то вроде: -

private boolean checkDataBase() {

    File db = new File(DATABASE_PATH + DATABASE_NAME);
    if(db.exists()) return true;
    File dir = new File(db.getParent());
    if (!dir.exists()) {
        dir.mkdirs();
    }
    return false;
}
  • , отметьте, что вам следует дополнительно удалить строку this.getReadableDatabase(); из метода createDatabase .

Хотя, вероятно, это не проблема, гораздо мудрее НЕ когда-либо жестко кодировать путь к базе данных, а вместо этого использовать метод класса Context ' getDatabasePath (если используется стандартное / рекомендуемое расположение длябаза данных).например,

private boolean checkDataBase() {

    File db = new File(context.getDatabasePath(DBNAME)); //<<<<<<<<<< CHANGED
    if(db.exists()) return true;
    File dir = new File(db.getParent());
    if (!dir.exists()) {
        dir.mkdirs();
    }
    return false;
}

Примечание

Из предоставленной трассировки стека, а также MainActivity.java , WordProvidr.java и WordDBHelper имеют идентичный код, точную причину определить невозможно.Таким образом, вышеизложенное является вероятной причиной или причиной, которая может произойти.

0 голосов
/ 04 мая 2019

Я не знаю, является ли это причиной вашей проблемы или нет, но когда у меня были подобные неустойчивые проблемы с SQLite, оказалось, что Android (или Eclipse) сжимал файл базы данных при компиляции с активами папка. Решением было дать файлу расширение типа файла, который Android не будет сжимать, например файл изображения с расширением .jpg. Звучит странно, но я помещаю файл моей базы данных в Assets как mydb.jpg, а затем в метод CopyDatabase изменяет расширение на mydb.db и сохраняет его в папке приложения / data / data / database.

...