从资产文件夹将现有数据库复制到设备内存

时间:2019-07-13 03:55:51

标签: java android sqlite android-studio

我正在尝试将数据库文件从资产文件夹复制到设备/ data / data文件夹中,以供应用程序参考。此复制过程不断失败。

我已经使用了copyDatabase()方法,该方法在关于stackoverflow的类似问题的答案中找到了。这些方法使用由context()。getAssets()。open(databaseName);设置的InputStream变量。但是,就我而言,此分配无法填充数据库。查询数据库的后续方法将引发异常,因为数据库没有按预期的方式存在“主”表。

这是我的copyDatabase()方法:

 String DATABASE_NAME = "birdsDBase3.db"

       private void copyDatabase(String DATABASE_PATH){
            Log.i(TAG, "now in copyDatabase()");
            try {
                Log.i(TAG, "trying to copy database.");
                InputStream assetDB = context.getAssets().open(DATABASE_NAME);
                Log.i(TAG, assetDB.toString());
                OutputStream appDataBase = new FileOutputStream(DATABASE_PATH, false);

                byte[] buffer = new byte[1024];
                int length;
                while ((length = assetDB.read(buffer)) > 0){
                    appDataBase.write(buffer, 0, length);
                }
                appDataBase.flush();
                appDataBase.close();
                assetDB.close();
            } catch(IOException e){
                e.printStackTrace();
            }
        }
    }

日志错误:“ SQLiteLog:(1)没有这样的表:类别”

我希望数据库复制到设备上,以便另一种方法可以在一个列中发送针对不同元组的查询。相反,由于数据库中没有“主”表,因此该方法失败。我知道查询的格式正确,因为我已经在SQLite浏览器中运行了该查询并获得了预期的结果,并且该方法已在stackoverflow的另一个线程中进行了检查。

2 个答案:

答案 0 :(得分:2)

假设问题很简单,即尚未正确保存数据库(不是罕见的情况,因此请检查并再次检查复制到assets文件夹中的文件是否已相应填充),则可能的问题如下:-< / p>

自从Android 9出现以来,许多从资产文件夹复制数据库的较旧方法现在都失败了。这是由于使用 getWritableDatabase 方法创建了将数据库复制到的数据库文件夹。

即安装了应用程序后,将为应用程序的数据创建文件夹 data / data / package 。默认情况下,SQlite数据库存储在 / data / data / package / database 中,如果数据库文件夹不存在,则复制将失败,并显示 NOENT

在创建数据库时,使用SQLiteOpenHelper的 getWritableDatabase 将创建 databases 文件夹,通常与要从资产文件夹复制的数据库的名称/文件相同以及最终的数据库名称。

在Android 9之前,使用的默认日志记录为日记模式。此模式使用与数据库名称相同但后缀-journal的文件。该文件包含交易,可用于从实际数据库回滚交易。

在Android 9中,默认使用预写日志记录(WAL)。这会将实际事务写入文件 -wal (实际上是数据库的一部分,首先查看),并且在发生检查点时更新数据库文件并清除-wal文件。

因此-wal文件是内部文件,如果存在,则它必须属于为其创建数据库的数据库。

所以发生的事情是getWritableDatabase创建了一个数据库,并且某些事务(例如创建android_metadata表)被写入了-wal文件。从资产文件复制数据库,覆盖新创建的数据库,但-wal和-shm文件(WAL使用的共享内存文件)保留。当打开数据库时,由于-wal和-shm文件不属于复制的数据库,该错误导致数据库无法使用,并且SDK / API创建了可用的新数据库,因此资产文件中不存在任何表已被复制。

解决方案可以是:-

  1. 通过在使用数据库之前调用SQLiteDatabase的disableWriteAheadLogging方法来禁用WAL。

    • 如果使用SQLiteOpenHelper的子类,通常通过覆盖SQLiteOpenHelper的 onConfigure 方法。
    • 不建议这样做,因为会失去WAL的好处。
  2. 使用不同的数据库名称来创建数据库文件夹(尚不知道该怎么做)。

    • 尴尬,从未尝试过,因此未考虑。
  3. 在打开复制的数据库之前删除或重命名-wal和-shm文件。

    • 到目前为止最好的选择,但是...
  4. 使用标准/基本文件处理(即使用the_databases_parent_file.mkdirs();

    )创建数据库文件夹
    • 最佳和推荐选项
    • 它没有打开数据库文件的开销
    • 进行检查
    • 创建android_metadata表
    • 创建和编写-wal和-shm文件
    • 覆盖中间数据库文件
    • 删除-wal和-shm文件
    • 负担得起WAL的优势

在检查数据库文件是否存在时使用此技术的示例是:-

private boolean checkDataBase(Context context, String databaseName) {
    /**
     * 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(context.getDatabasePath(databaseName).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;
}

要阻止上述情况,您将使用上述方法检查数据库并取消在检查数据库和调用 copyDatbase 方法之间调用的 getWritableDatabase 。 / p>

答案 1 :(得分:0)

您需要实施

  

SqliteAssetHelper

在网络上搜索并尝试。