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();
}
我希望使用我的预创建数据库
假设您要将现有的预先存在的数据库替换为另一个副本,则存在许多问题。
您面临的问题是,当数据库存在时,副本将不会继续,即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);
}
}
}
考虑以下资产文件(sqlite数据库)(警告,因为它们是应用程序将失败): -
所以有两个数据库(相同的条形码分别使用PRAGMA user_version = 1
和PRAGMA 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
)
);
}
}
该日志包含: -
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
然后重新运行应用程序,然后日志包含: -
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
即没有复制,因为资产实际上是相同的