预填充的数据库在 API 28 上不起作用,会抛出“没有这样的表”异常

2024-01-07

我在我的项目中使用预先填充的数据库。我创建了一个 .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();

}

它在除 API 28 之外的不同 Android 版本上都能正常工作。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)

先感谢您。


使用 SQLite 并复制预先存在的数据库突然不适用于 API 28 的应用程序的典型原因是为了解决以下问题:database文件夹不存在(如果目录不存在,复制将失败)的方法是创建一个空数据库,然后覆盖该数据库。

但是,默认情况下,从 API 28 开始,SDK 使用 WAL(预写日志记录)并且创建要覆盖的空数据库会导致创建 -shm 和 -wal 文件。正是这些文件的存在导致了复制后数据库为空。

  • 我相信这是因为一旦打开复制的数据库,就会检测到不匹配,并且 SDK 的方法会创建一个空的可用数据库(这是推测,实际上尚未证明)。

快速解决

快速但不推荐的修复方法是覆盖配置上子类 SQLiteOpenHelper 的类中的方法以使用禁用WriteAheadLogging https://developer.android.com/reference/android/database/sqlite/SQLiteDatabase#disableWriteAheadLogging()方法使数据库以日志模式打开。

  • 下面的完整代码(第二段代码)包含此内容,但该行已被注释掉。

建议修复

推荐的方法,以便从WAL 的好处 https://www.sqlite.org/wal.html,是检查数据库目录是否存在,如果不存在则创建该目录而不是创建一个要覆盖的数据库(因此复制数据库时 -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/databases/)。

上面的内容是从 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
    }
}
  • 请注意,上述代码用于开发/实验,因此包含可以删除的代码。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

预填充的数据库在 API 28 上不起作用,会抛出“没有这样的表”异常 的相关文章

  • Android 在打开应用程序时会广播吗?

    例如 如果我想知道Youtube何时打开 是否有与之相关的广播 我当然知道我可以轮询 logcat 消息来检查活动 但我可以通过广播来做到这一点吗 因为它会少得多的耗电 此链接似乎表明这是不可能的 如何跟踪 Android 中的应用程序使用
  • Twitter 登录说明

    我想在 Android 中创建一个 Twitter 应用程序 为此 我想创建一个登录页面并登录到 Twitter 为此 我们需要消费者密钥和消费者密钥 这是什么意思 要创建此登录页面 除了 Twitter 帐户之外 我们还需要其他任何东西吗
  • 在android中通过BLE传输图像

    我使用以下代码传输 1 MB 的图像 如果在每个数据包之间实现线程延迟 则图像将成功传输 如果未设置线程延迟 则所有数据包均从BluetoothGattServer 发送 但BluetoothGattCallback 不会接收所有数据包 任
  • 带有一、二和三个按钮的 Android 警报对话框

    我不经常发出警报 但每次发出警报时 我都会花一些时间来阅读文档 https developer android com guide topics ui dialogs html并弄清楚如何去做 由于我现在不得不这样做几次 所以我将在下面写一
  • Android 如何更改 OnTouchListener 上的按钮背景

    你好 我在 xml 中有一个按钮 我正在使用OnTouchListener在我的活动中获得button按下并释放 但问题是 当我按下按钮时背景颜色没有改变 当我延长可能的活动时OnClickListener背景正在改变 任何人都可以告诉我的
  • 带操作按钮的颤动本地通知

    我在我的 flutter 项目中尝试了 flutter 本地通知插件 它在简单通知上工作正常 但我需要带有操作按钮的通知功能 请帮助我或建议我实现此功能 不幸的是 flutter local notifications 插件尚不支持操作按钮
  • Android 自定义布局 - onDraw() 永远不会被调用

    public class MainActivity extends Activity Override public void onCreate Bundle savedInstanceState super onCreate savedI
  • 如何为发布而不是调试创建密钥库?扑

    我按照使用此网站部署 flutter 的步骤进行操作https flutter io android release https flutter io android release 当我运行 flutter build apk 时出现此错
  • Android 应用程序中的 Eszett (ß)

    我的 res layout activity 文件中的德语 字符在我的应用程序中自动转换为 ss 即使我将语言和键盘设置为德语 它仍然不会显示 Android 中可以显示 吗 edit
  • 如何在 SQLite 中将时间戳转换为字符串?

    我有一个表 其中存储了时间戳 以毫秒为单位 我想将这些时间戳转换为人类可读的形式 这是我的表的输出示例 SELECT date raw strftime d m Y date 1000 as string FROM my table raw
  • Java 文件上传速度非常慢

    我构建了一个小型服务 它从 Android 设备接收图像并将其保存到 Amazon S3 存储桶中 代码非常简单 但是速度非常慢 事情是这样的 public synchronized static Response postCommentP
  • 使用 PhoneGap 使 Android 应用程序易于访问(对于残障人士)

    有人有过使用 PhoneGap 使 Android 应用程序可访问的经验吗 至少我们需要使我们的应用程序符合第 508 条规定 我尝试实现一些标准的辅助功能 文本框标签 向 div 添加标题属性等 但是 当在 Android 中使用 Tal
  • 在旋转时从错误的资源文件夹中提取可绘制对象

    在这里拉我的头发 因此 我正在使用一个具有多种类型的可绘制对象的应用程序 并且它们的结构如下 res Portrait resources drawable mdpi drawable hdpi drawable xhdpi Landsca
  • Android FragmentTransaction 自定义动画(未知动画师名称:Translate)

    我正在尝试让自定义动画与我的片段一起使用 我已按照在线教程进行操作 但出现以下错误 java lang RuntimeException 未知的动画师名称 翻译 动画的 XML 如下
  • Android:滚动 Horizo​​ntalScrollView 时如何禁用 ScrollView 的垂直滚动?

    我正在开发一个带有带有 ScrollView 的 Activity 的 Android 应用程序 其中包含 Horizo ntalScrollView 等内容 当我触摸 Horizo ntalScrollView 时 我想禁用外部 Scro
  • 在 React Native 中调试应用程序崩溃

    我是 React Native 新手 我正在尝试安装 React Native Facebook SDK 以便我可以使用我的应用程序进行 Facebook 登录 我按照此处列出的步骤操作 https tylermcginnis com in
  • 在 Android 中调整可绘制对象的大小

    我正在为进度对话框设置一个可绘制对象 pbarDialog 但我的问题是我想每次调整可绘制的大小 但不知道如何调整 这是一些代码 Handler progressHandler new Handler public void handleM
  • Android 中循环事件的星期几和时间选择器

    我想创建一个控件 允许用户在我的 Android 活动中选择一周中的某一天 星期一 和一天中的某个时间 下午 1 00 找不到任何关于此的好帖子 好吧 我想我已经明白了 我只是不喜欢这个解决方案 因为我在一周中的某一天使用的微调器与时间选择
  • 使用单选按钮更改背景颜色 Android

    我试图通过从单选组中选择单选按钮来更改应用程序选项卡的背景 但是我不确定如何执行此操作 到目前为止我已经 收藏夹 java import android app Activity import android os Bundle publi
  • Android应用程序可以像旧的普通java小程序一样嵌入到网页中吗?

    我对 android 平台一无所知 也无法在互联网上找到这个基本问题的答案 更新 好的 我本身无法嵌入 Android 应用程序 但是我可以在 Android Webbrowser 中嵌入 Java 的东西吗 不可以 您无法将 Androi

随机推荐