0

我有一个字典应用程序使用我提供的数据库完全脱机工作。为了能够使用数据库,我应用了我在开发人员博客上看到的解决方案。您可以从此链接参考它。该解决方案基本上是从资产文件夹中复制数据库并使用它。它在大多数设备上都可以正常工作,但有些用户在尝试查询数据库时遇到了崩溃。我已附上我收到的堆栈跟踪以及从华硕 ZenFone 5 (ZE620KL) (ASUS_X00QD) 发送的崩溃报告。我的问题是:我正在应用的做法是否有问题(使用位于 assets 文件夹中的现有数据库)?我能做些什么来避免这个异常?

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

如有必要,她是整个项目的链接。

4

2 回答 2

2

我正在应用的做法是否有问题(使用位于 assets 文件夹中的现有数据库)?

并非如此,因为这是一种推荐且经常使用的技术。但是,如果实施不当,可能会出现问题,尤其是现在。

根据快速搜索该设备的操作系统是Android 8.0 (Oreo),可升级到 Android 9.0 (Pie);ZenUI 5

使用 Android Pie 时肯定会出现 1 个问题,这很可能是您遇到的问题。

原因是使用this.getReadableDatabase();之前调用了copyDatabase方法。

由于 Android Pie 默认打开 WAL(预写日志记录),因此会创建两个文件-wal-shm文件。这些剩余会导致冲突,因为它们与刚刚复制的数据库不匹配。我相信最终的结果是删除复制的数据库并创建一个全新的数据库以提供可用的数据库。因此,不存在表或基础数据。这通常会导致表未找到错误/异常,因为通常在尝试访问该表不存在的数据时。

我能做些什么来避免这个异常?

修复 1

简单但不推荐的修复方法是覆盖WordDbHelper.java中 SQLiteOpenhelper的onConfigure方法以调用disableWriteAheadLogging方法。

但是,应用此修复意味着您放弃了Write-Ahead Logging的优势。

修复 2

正确的解决方法是在复制之前不使用getReadableDatabase。这似乎是一个简单问题的历史修复。也就是包的data/data/ the_package /目录,对于新安装的App,没有databases目录。因此 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.javaWordProvidr.javaWordDBHelper的代码似乎相同,无法确定确切的原因。因此,上述是可能的原因或可能发生的原因。

于 2019-05-04T01:34:20.247 回答
0

我不知道这是否是您的问题的原因,但是当我遇到类似的 SQLite 间歇性问题时,原来是 Android(或 Eclipse)在使用 Assets 文件夹编译时正在压缩数据库文件。解决方案是为文件提供 Android 不会压缩的文件类型的扩展名,例如扩展名为 .jpg 的图像文件。这听起来很奇怪,但我将我的数据库文件作为 mydb.jpg 放在 Assets 中,然后在 CopyDatabase 方法中,它将扩展名更改为 mydb.db 并将其存储在 /data/data/databases 应用程序文件夹中。

于 2019-05-03T21:41:02.090 回答