U hebt een aantal problemen, ervan uitgaande dat u een bestaande reeds bestaande database wilt vervangen door een ander exemplaar.
Het probleem waarmee u wordt geconfronteerd, is dat aangezien er een database bestaat, de kopie niet verder zal gaan, d.w.z. de checkDatabase() zal true retourneren.
Als u gewoon de copyDatabase() . zou aanroepen dan zou de database elke keer dat de app wordt uitgevoerd worden gekopieerd, wat inefficiënt en destructief zou zijn als de database door de gebruiker kan worden gewijzigd.
Wat u moet doen, is een indicator hebben, die kan worden getest, om te zien of de reeds bestaande database is gewijzigd. Er zijn verschillende manieren, maar de meest waarschijnlijke/gebruikelijke manier is om de SQLite user_version te gebruiken . Dit is een geheel getal en wordt vaak gebruikt om de huidige database bij te werken via de onUpgrade methode.
Als onderdeel van het openen van de database, de SQLiteOpenHelper (en dus een subklasse daarvan), vergelijkt het de user_version die is opgeslagen in de database met het opgegeven versienummer (4e parameter naar de SQLiteOpenHelper-superaanroep) en als deze laatste groter is dan de waarde die is opgeslagen in de database dan wordt de methode onUpgrade aangeroepen. (indien het omgekeerde dan de onDowngrade methode wordt aangeroepen en zonder dat deze wordt gecodeerd, treedt er een uitzondering op).
De user_version kan worden ingesteld in de SQLite-beheertool gebruiker de SQL PRAGMA user_version = n
.
Een ander probleem is dat vanaf Android 9 de database standaard in WAL-modus (Write-Ahead Logging) wordt geopend. De bovenstaande code met behulp van this.getReadableDatabase();
resulteert in het maken van de -shm en -wal bestanden. Hun bestaan resulteert in een opgesloten fout (omdat ze dan niet overeenkomen met de gekopieerde database) die er vervolgens toe leidt dat de SQLiteOpenHelper een lege (theoretisch bruikbare database) creëert die in feite de gekopieerde database wist (Ik geloof dat dit is wat er gebeurt ).
De reden waarom this.getReadableDatabase();
is gebruikt, is dat het het probleem omzeilt dat wanneer er geen app-gegevens zijn, de databases map/directory bestaat niet en met behulp van het bovenstaande wordt het gemaakt. De juiste manier is om de databasedirectory/map aan te maken als deze niet bestaat. Als zodanig worden de -wal- en -shm-bestanden niet gemaakt.
Het volgende is een voorbeeld van een DatabseHelper die de problemen oplost en bovendien toestaat dat gewijzigde versies van de reeds bestaande database worden gekopieerd op basis van het wijzigen van de 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);
}
}
}
Voorbeeld van gebruik
Overweeg de volgende activabestanden (sqlite-databases) (waarschuwing aangezien de app zou mislukken ) :-
Er zijn dus twee databases (identieke balk de user_version zoals ingesteld met PRAGMA user_version = 1
en PRAGMA user_version = 2
respectievelijk/volgens de bestandsnamen)Voor de gloednieuwe, eerste keer de app uitvoeren (d.w.z. niet-geïnstalleerd) en vervolgens test.dbV1 bestand wordt hernoemd naar test.db en de volgende activiteit wordt gebruikt:-
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
)
);
}
}
- Hiermee wordt eenvoudig de Database Helper geïnstantieerd (die de database zal kopiëren of gebruiken) en vervolgens de tabel sqlite_master dumpt.
Het logboek bevat:-
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 [email protected]
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
..........
Wanneer de nieuwe versie van de DB wordt geïntroduceerd, die een user_version van 2
. heeft- d.w.z. test.db dat was test.dbV1 wordt hernoemd naar test.dbV1 EN dan,
- (effectief verwijderen)
- test.dbV2 wordt dan hernoemd tot test.db
- (effectieve introductie van het nieuwe activabestand) dan:-
En de app wordt dan opnieuw uitgevoerd, dan bevat het logboek:-
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 [email protected]
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
Ten slotte, met een volgende run, d.w.z. geen bijgewerkte activa, toont het logboek:-
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 [email protected]
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
d.w.z. er is geen kopie gemaakt omdat het item in feite hetzelfde is