无法从资产中复制预先创建的数据库

问题描述 投票:1回答:1

android无法从资产复制我的预创建数据库,旧的数据库仍然存在于data / data / packagename / dabases中,我想用新的更改旧数据库

ive tried to change this.getReadableDatabase(); into SQLiteDatabse db = this.getReadabledatabase()
if (db.isOpen()){
    db.close();
}


 public void createDatabase(){
   boolean dbExist = checkDatabase();
   if(!dbExist){
       this.getReadableDatabase();
       SQl
       try {
           copyDatabase();
       }catch (IOException e){
           Log.e(this.getClass().toString(),"eror kopi");
           throw new Error("error kopi");
       }
   }else{
       Log.i(this.getClass().toString(),"databse udah ada");
   }
}                                                                   
    private void copyDatabase() throws IOException {
    InputStream externalDBStream = 
     context.getAssets().open(DATABASE_NAME);
    String outFileName = DB_PATH + DATABASE_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.flush();
    localDBStream.close();
    externalDBStream.close();
}

我希望使用我的预创建数据库

android sqlite android-sqlite sqliteopenhelper android-assets
1个回答
2
投票

假设您要将现有的预先存在的数据库替换为另一个副本,则存在许多问题。

您面临的问题是,当数据库存在时,副本将不会继续,即checkDatabase()将返回true。

如果您只是简单地调用copyDatabase(),那么每次运行App时都会复制数据库,如果用户可以修改数据库,这将是低效且具有破坏性的。

您需要做的是有一个可以测试的指示器,以查看是否已更改预先存在的数据库。有多种方法,但最可能/最常见的方式是利用SQLite user_version。这是一个整数值,经常用于通过onUpgrade方法更新当前数据库。

作为打开数据库的一部分,SQLiteOpenHelper(以及它的子类)将数据库中存储的user_version与提供的版本号(SQLiteOpenHelper超级调用的第4个参数)进行比较,如果后者大于存储在数据库中的值。数据库然后调用onUpgrade方法。 (如果相反,那么将调用onDowngrade方法,如果没有编码,则会发生异常)。

user_version可以在SQLite管理工具用户SQL PRAGMA user_version = n中设置。

另一个问题是,从Android 9开始,数据库默认以WAL(预读日志)模式打开。上面的代码使用this.getReadableDatabase();导致创建-shm和-wal文件。它们的存在会导致陷阱错误(因为它们与复制的数据库不匹配),然后导致SQLiteOpenHelper创建一个空的(理论上可用的数据库)基本上擦除复制的数据库(我相信这就是发生的事情)。

之所以使用this.getReadableDatabase();,是因为它存在这样的问题:当没有App数据时,数据库文件夹/目录不存在,并使用上面创建它。正确的方法是创建数据库目录/文件夹(如果它不存在)。因此,不会创建-wal和-shm文件。

以下是DatabseHelper的示例,它克服了这些问题,并且还允许基于更改user_version来复制预先存在的数据库的修改版本。

public class DBHelperV001 extends SQLiteOpenHelper {

    public static final String DBNAME = "test.db"; //<<<<<<<<<< obviously change accordingly

    //
    private static int db_user_version, asset_user_version, user_version_offset = 60, user_version_length = 4;
    private static String stck_trc_msg = " (see stack-trace above)";
    private static String sqlite_ext_journal = "-journal";
    private static String sqlite_ext_shm = "-shm";
    private static String sqlite_ext_wal = "-wal";
    private static int copy_buffer_size = 1024 * 8; //Copy data in 8k chucks, change if wanted.

    SQLiteDatabase mDB;

    /**
     *  Instantiate the DBHelper, copying the databse from the asset folder if no DB exists
     *  or if the user_version is greater than the user_version of the current database.
     *  NOTE The pre-existing database copied into the assets folder MUST have the user version set
     *  to 1 or greater. If the user_version in the assets folder is increased above the
     *
     * @param context
     */
    public DBHelperV001(Context context) {

        // Note get the version according to the asset file
        // avoid having to maintain the version number passed
        super(context, DBNAME, null, setUserVersionFromAsset(context,DBNAME));
        if (!ifDbExists(context,DBNAME)) {
            copyDBFromAssets(context, DBNAME,DBNAME);
        } else {
            setUserVersionFromAsset(context,DBNAME);
            setUserVersionFromDB(context,DBNAME);
            if (asset_user_version > db_user_version) {
                copyDBFromAssets(context,DBNAME,DBNAME);
            }
        }
        // Force open (and hence copy attempt) when constructing helper
        mDB = this.getWritableDatabase();
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
    }

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

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

    /**
     * Check to see if the databse file exists
     * @param context   The Context
     * @param dbname    The databse name
     * @return          true id database file exists, else false
     */
    private static boolean ifDbExists(Context context, String dbname) {
        File db = context.getDatabasePath(dbname);
        if (db.exists()) return true;
        if (!db.getParentFile().exists()) {
            db.getParentFile().mkdirs();
        }
        return false;
    }

    /**
     * set the db_user_version according to the user_version obtained from the current database file
     * @param context   The Context
     * @param dbname    The database (file) name
     * @return          The user_version
     */
    private static int setUserVersionFromDB(Context context, String dbname) {
        File db = context.getDatabasePath(dbname);
        InputStream is;
        try {
            is = new FileInputStream(db);
        } catch (IOException e) {
            throw new RuntimeException("IOError Opening " + db.getPath() + " as an InputStream" + stck_trc_msg);
        }
        db_user_version = getUserVersion(is);
        Log.d("DATABASEUSERVERSION","Obtained user_version from current DB, it is " + String.valueOf(db_user_version)); //TODO remove for live App
        return db_user_version;
    }

    /**
     * set the asset_user_version according to the user_version from the asset file
     * @param context
     * @param assetname
     * @return
     */
    private static int setUserVersionFromAsset(Context context, String assetname) {
        InputStream is;
        try {
            is = context.getAssets().open(assetname);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("IOError Getting asset " + assetname + " as an InputStream" + stck_trc_msg);
        }
        asset_user_version = getUserVersion(is);
        Log.d("ASSETUSERVERSION","Obtained user_version from asset, it is " + String.valueOf(asset_user_version)); //TODO remove for Live App
        return asset_user_version;
    }

    /**
     * Retrieve SQLite user_version from the provied InputStream
     * @param is    The InputStream
     * @return      the user_version
     */
    private static int getUserVersion(InputStream is) {
        String ioerrmsg = "Reading DB header bytes(60-63) ";
        int rv;
        byte[] buffer = new byte[user_version_length];
        byte[] header = new byte[64];
        try {
            is.skip(user_version_offset);
            is.read(buffer,0,user_version_length);
            ByteBuffer bb = ByteBuffer.wrap(buffer);
            rv = ByteBuffer.wrap(buffer).getInt();
            ioerrmsg = "Closing DB ";
            is.close();
            return rv;
        } catch (IOException e) {
            e.printStackTrace();
            throw  new RuntimeException("IOError " + ioerrmsg + stck_trc_msg);
        }
    }

    /**
     * Copy the database file from the assets 
     * Note backup of existing files may not be required
     * @param context   The Context
     * @param dbname    The database (file)name
     * @param assetname The asset name (may therefore be different but )
     */
    private static void copyDBFromAssets(Context context, String dbname, String assetname) {
        String tag = "COPYDBFROMASSETS";
        Log.d(tag,"Copying Database from assets folder");
        String backup_base = "bkp_" + String.valueOf(System.currentTimeMillis());
        String ioerrmsg = "Opening Asset " + assetname;

        // Prepare Files that could be used
        File db = context.getDatabasePath(dbname);
        File dbjrn = new File(db.getPath() + sqlite_ext_journal);
        File dbwal = new File(db.getPath() + sqlite_ext_wal);
        File dbshm = new File(db.getPath() + sqlite_ext_shm);
        File dbbkp = new File(db.getPath() + backup_base);
        File dbjrnbkp = new File(db.getPath() + backup_base);
        File dbwalbkp = new File(db.getPath() + backup_base);
        File dbshmbkp = new File(db.getPath() + backup_base);
        byte[] buffer = new byte[copy_buffer_size];
        int bytes_read = 0;
        int total_bytes_read = 0;
        int total_bytes_written = 0;

        // Backup existing sqlite files
        if (db.exists()) {
            db.renameTo(dbbkp);
            dbjrn.renameTo(dbjrnbkp);
            dbwal.renameTo(dbwalbkp);
            dbshm.renameTo(dbshmbkp);
        }
        // ALWAYS delete the additional sqlite log files
        dbjrn.delete();
        dbwal.delete();
        dbshm.delete();

        //Attempt the copy
        try {
            ioerrmsg = "Open InputStream for Asset " + assetname;
            InputStream is = context.getAssets().open(assetname);
            ioerrmsg = "Open OutputStream for Databse " + db.getPath();
            OutputStream os = new FileOutputStream(db);
            ioerrmsg = "Read/Write Data";
             while((bytes_read = is.read(buffer)) > 0) {
                 total_bytes_read = total_bytes_read + bytes_read;
                 os.write(buffer,0,bytes_read);
                 total_bytes_written = total_bytes_written + bytes_read;
             }
             ioerrmsg = "Flush Written data";
             os.flush();
             ioerrmsg = "Close DB OutputStream";
             os.close();
             ioerrmsg = "Close Asset InputStream";
             is.close();
             Log.d(tag,"Databsse copied from the assets folder. " + String.valueOf(total_bytes_written) + " bytes were copied.");
             // Delete the backups
             dbbkp.delete();
             dbjrnbkp.delete();
             dbwalbkp.delete();
             dbshmbkp.delete();
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("IOError attempting to " + ioerrmsg + stck_trc_msg);
        }
    }
}

Example usage

考虑以下资产文件(sqlite数据库)(警告,因为它们是应用程序将失败): -

enter image description here

所以有两个数据库(相同的条形码分别使用PRAGMA user_version = 1PRAGMA user_version = 2 /根据文件名设置)对于全新的,第一次运行App(即卸载)然后文件test.dbV1重命名为test.db和使用以下活动: -

public class MainActivity extends AppCompatActivity {

    DBHelperV001 mDbhlpr;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mDbhlpr = new DBHelperV001(this);
        DatabaseUtils.dumpCursor(
                mDbhlpr.getWritableDatabase().query(
                        "sqlite_master",
                        null,null,null,null,null,null
                )
        );
    }
}
  • 这只是实例化Database Helper(将复制或使用数据库),然后转储sqlite_master表。

该日志包含: -

04-02 12:55:36.258 644-644/aaa.so55441840 D/ASSETUSERVERSION: Obtained user_version from asset, it is 1
04-02 12:55:36.258 644-644/aaa.so55441840 D/COPYDBFROMASSETS: Copying Database from assets folder
04-02 12:55:36.262 644-644/aaa.so55441840 D/COPYDBFROMASSETS: Databsse copied from the assets folder. 69632 bytes were copied.
04-02 12:55:36.265 644-644/aaa.so55441840 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@d121f9c
04-02 12:55:36.265 644-644/aaa.so55441840 I/System.out: 0 {
04-02 12:55:36.265 644-644/aaa.so55441840 I/System.out:    type=table
04-02 12:55:36.265 644-644/aaa.so55441840 I/System.out:    name=android_metadata
04-02 12:55:36.265 644-644/aaa.so55441840 I/System.out:    tbl_name=android_metadata
04-02 12:55:36.265 644-644/aaa.so55441840 I/System.out:    rootpage=3
04-02 12:55:36.265 644-644/aaa.so55441840 I/System.out:    sql=CREATE TABLE android_metadata (locale TEXT)
04-02 12:55:36.266 644-644/aaa.so55441840 I/System.out: }
04-02 12:55:36.266 644-644/aaa.so55441840 I/System.out: 1 {
04-02 12:55:36.266 644-644/aaa.so55441840 I/System.out:    type=table
04-02 12:55:36.266 644-644/aaa.so55441840 I/System.out:    name=shops
..........

引入新版本的DB时,其user_version为2

  • 即test.db,test.dbV1被重命名为test.dbV1然后, (有效地删除它)
  • 然后将test.dbV2重命名为test.db (有效地引入新的资产文件)然后: -

然后重新运行应用程序,然后日志包含: -

04-02 13:04:25.044 758-758/? D/ASSETUSERVERSION: Obtained user_version from asset, it is 2
04-02 13:04:25.046 758-758/? D/ASSETUSERVERSION: Obtained user_version from asset, it is 2
04-02 13:04:25.046 758-758/? D/DATABASEUSERVERSION: Obtained user_version from current DB, it is 1
04-02 13:04:25.047 758-758/? D/COPYDBFROMASSETS: Copying Database from assets folder
04-02 13:04:25.048 758-758/? D/COPYDBFROMASSETS: Databsse copied from the assets folder. 69632 bytes were copied.
04-02 13:04:25.051 758-758/? I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@25012da5
04-02 13:04:25.052 758-758/? I/System.out: 0 {
04-02 13:04:25.052 758-758/? I/System.out:    type=table
04-02 13:04:25.052 758-758/? I/System.out:    name=android_metadata
04-02 13:04:25.052 758-758/? I/System.out:    tbl_name=android_metadata
04-02 13:04:25.052 758-758/? I/System.out:    rootpage=3
04-02 13:04:25.052 758-758/? I/System.out:    sql=CREATE TABLE android_metadata (locale TEXT)
04-02 13:04:25.052 758-758/? I/System.out: }
04-02 13:04:25.052 758-758/? I/System.out: 1 {
04-02 13:04:25.052 758-758/? I/System.out:    type=table
04-02 13:04:25.052 758-758/? I/System.out:    name=shops

最后,随后的运行,即没有更新的资产,日志显示: -

04-02 13:05:50.197 840-840/aaa.so55441840 D/ASSETUSERVERSION: Obtained user_version from asset, it is 2
04-02 13:05:50.198 840-840/aaa.so55441840 D/ASSETUSERVERSION: Obtained user_version from asset, it is 2
04-02 13:05:50.198 840-840/aaa.so55441840 D/DATABASEUSERVERSION: Obtained user_version from current DB, it is 2
04-02 13:05:50.201 840-840/aaa.so55441840 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@d121f9c
04-02 13:05:50.202 840-840/aaa.so55441840 I/System.out: 0 {
04-02 13:05:50.202 840-840/aaa.so55441840 I/System.out:    type=table
04-02 13:05:50.202 840-840/aaa.so55441840 I/System.out:    name=android_metadata
04-02 13:05:50.202 840-840/aaa.so55441840 I/System.out:    tbl_name=android_metadata
04-02 13:05:50.202 840-840/aaa.so55441840 I/System.out:    rootpage=3
04-02 13:05:50.202 840-840/aaa.so55441840 I/System.out:    sql=CREATE TABLE android_metadata (locale TEXT)
04-02 13:05:50.202 840-840/aaa.so55441840 I/System.out: }
04-02 13:05:50.202 840-840/aaa.so55441840 I/System.out: 1 {
04-02 13:05:50.202 840-840/aaa.so55441840 I/System.out:    type=table
04-02 13:05:50.202 840-840/aaa.so55441840 I/System.out:    name=shops

即没有复制,因为资产实际上是相同的

© www.soinside.com 2019 - 2024. All rights reserved.