I am using code in this article to copy a pre-populated db I have into a freshly created db thus overwriting it.
The overwrite is successful. I am able to see the correctly copied db in the right path data/data/my.package/databases/myDatabase. But this is ONLY if I look at the database before leaving getWritableDatabse().
If I leave getWritableDatabse() the db is invalidated: it still keeps its right size BUT it has only the android_metadata instead of my tables that are then all gone.
I didn’t manage to get access to the Android source code for Eclipse (followed all the instructions but it still doesn’t work) but using GrepCode I managed to narrow down the problem to lines 173-176 in method getWritableDatabase().
173 db.setVersion(mNewVersion);
174 db.setTransactionSuccessful();
175 } finally {
176 db.endTransaction();
Anyone has a clue why this piece of code might rewrite my db and empty it?
This is the relevant stack:
Thread [<1> main] (Suspended)
StournamentDbAdapter$DatabaseHelper(SQLiteOpenHelper).getWritableDatabase() line: 180
StournamentDbAdapter.open() line: 192
ActivityTournamentsList.onCreate(Bundle) line: 57
ActivityTournamentsList(Activity).performCreate(Bundle) line: 4465
Instrumentation.callActivityOnCreate(Activity, Bundle) line: 1049
ActivityThread.performLaunchActivity(ActivityThread$ActivityClientRecord, Intent) line: 1920
ActivityThread.handleLaunchActivity(ActivityThread$ActivityClientRecord, Intent) line: 1981
ActivityThread.access$600(ActivityThread, ActivityThread$ActivityClientRecord, Intent) line: 123
ActivityThread$H.handleMessage(Message) line: 1147
ActivityThread$H(Handler).dispatchMessage(Message) line: 99
Looper.loop() line: 137
ActivityThread.main(String[]) line: 4424
Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method]
Method.invoke(Object, Object...) line: 511
ZygoteInit$MethodAndArgsCaller.run() line: 784
ZygoteInit.main(String[]) line: 551
NativeStart.main(String[]) line: not available [native method]
I fist use mDb = mDbHelper.getWritableDatabase(); which calls to DatabaseHelper.onCreate()where the creation of the db actually happens. In DatabaseHelper.onCreate() I call only copyDatabase(); which looks like this:
private void copyDatabase() throws IOException
{
//Open your local db as the input stream
InputStream myInput = sContext.getAssets().open(StournamentConstants.Database.DATABASE_NAME);
// Path to the just created empty db
String outFileName = StournamentConstants.Database.DB_PATH + StournamentConstants.Database.DATABASE_NAME;
//Open the empty db as the output stream
OutputStream myOutput = new FileOutputStream(outFileName);
//transfer bytes from the inputfile to the outputfile
byte[] buffer = new byte[1024];
int length;
while ((length = myInput.read(buffer))>0)
{
myOutput.write(buffer, 0, length);
}
//Close the streams
myOutput.flush();
myOutput.close();
myInput.close();
}
All this goes fine and the db is correctly copied. Only when I climb out of the stack and return to getWritableDatabase() line: 180 that something goes wrong: the size of the db stays the same but it loses all tables besides the àndroid_metadata`.
Intermediate conclusion
I dug a bit more into the code (the above mentioned GrepCode site was VERY helpful) because I wanted to get to the bottom of this. After going into getWritableDatabase(), on line 165 it will call onCreate(), where I replace to database by using copyDatabase();. The issue is that before, on getWritableDatabase() line 162 db.beginTransaction(); is called. Why is that a problem? I will explain by showing the sequence of events:
- call
getWritableDatabase() getWritableDatabase()callsdb = SQLiteDatabase.create(null);hence creating a dbgetWritableDatabase()callsdb.beginTransaction();getWritableDatabase()callsonCreate()- inside
onCreate()I replace the database by callingcopyDatabase();which copies the db from asset to the db location. Not sure why the already created db doesn’t crash at that moment. Maybe because there is no lock on it? - on line 176
getWritableDatabase()callsdb.endTransaction();which is the crucial part: it is nice and well that I copied my db into the initially created db, BUT whendb.endTransaction();is called, the system commits all the changes to my newly copied db. But remember: I did no changes, I created no tables – I just copied the db to the correct location. So the commit commits NOTHING thus rewriting the db with NOTHING. For that I get an empty db.
I had the evil thought to:
- Copy the db in
onCreate() - Open the newly copied db and replace the db
getWritableDatabase()uses (onCreate()gets the db as parameter fromgetWritableDatabase()) with it.
Something like that:
@Override
public void onCreate(SQLiteDatabase db)
{
try
{
copyDatabase();
String dbPath = StournamentConstants.Database.DB_PATH + StournamentConstants.Database.DATABASE_NAME;
SQLiteDatabase activeDb = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY);
db = activeDb;
}
catch (IOException e)
{
Log.i(getClass().getName() + ".onCreate ", e.getMessage());
}
}
The problem with that is that I get an android.database.sqlite.SQLiteDatabaseLockedException: database is locked so I can’t get the newly copied db. And besides that – I am not sure what sinister consequences lurk behind such a devious deed.
So I will do what @Barak suggests and move my copying into the constructor – way before the system tries to create the db. So when getWritableDatabase() is finally reached, the db is already there and the system will not attempt to touch it. I learned a lot today – am very pleased.
Conclusion: if you can read (code), you have a definite advantage :^)
Will keep you posted on how this worked for me.
Final conclusion
I solved the issue.
Few differences:
- I DON’T do it inside
DatabaseHelper‘s constructor. That would have taken too many code changes. Instead I am doing it insideopen()of myprivate static class DatabaseHelper extends SQLiteOpenHelperclass - I DON’T use
getReadableDatabase()butgetWritableDatabase(). The reason is simple:getReadableDatabase()will ALWAYS callgetWritableDatabase()so I thought I save that one…
I also tried to be a wiseguy and instead of calling getWritableDatabase() (which has some overhead of code) simply CREATING the db file on my own. I failed. I think it is due to permission issues or something. This is what I tried inside the copyDatabase():
File f = new File(DB_PATH);
if (!f.exists())
{
f.mkdir();
}
But it always fails. I later found out that OutputStream myOutput = new FileOutputStream(outFileName);actually should take care of the file creation.
Hope this helps anyone!
Well, the only difference I see is that I don’t do the database copying/creation in
onCreate, that is empty. I do it all in the constructor, like this: