Question:
I have a version of the Application in production, where it is allowed to bookmark a message, but this totally random error happens causing database corruption and resulting in application failure.
I know that I perform operations a little risky according to the people at SQLite.
The procedure is as follows:
- starts the upgrade procedure
- copies the user database to a temporary file
- copy the new database to the device
- attach the old base to the new
- copy necessary data (such as favorite messages)
- detach the temporary basis
- excludes the temporary basis
This procedure is done on thousands of devices, most everything goes well, but it started to become frequent cases like this.
Does anyone know how to minimize this?
MISTAKE:
java.lang.RuntimeException: Unable to start activity ComponentInfo{br.com.redrails.torpedos/br.com.redrails.torpedos.MainActivity}: android.database.sqlite.SQLiteDatabaseCorruptException: error code 11: database disk image is malformed
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1970)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1995)
at android.app.ActivityThread.access$600(ActivityThread.java:128)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1161)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4514)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:790)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:557)
at dalvik.system.NativeStart.main(Native Method)
Caused by: android.database.sqlite.SQLiteDatabaseCorruptException: error code 11: database disk image is malformed
at android.database.sqlite.SQLiteStatement.native_1x1_long(Native Method)
at android.database.sqlite.SQLiteStatement.simpleQueryForLong(SQLiteStatement.java:138)
at android.database.DatabaseUtils.longForQuery(DatabaseUtils.java:791)
at android.database.DatabaseUtils.longForQuery(DatabaseUtils.java:779)
at br.com.redrails.torpedos.MensagemDAO.reloadQuantidadeTotal(MensagemDAO.java:144)
at br.com.redrails.torpedos.MensagemDAO.getQuantidadeTotal(MensagemDAO.java:149)
at br.com.redrails.torpedos.MainActivity.onCreate(MainActivity.java:85)
at android.app.Activity.performCreate(Activity.java:4465)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1053)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1934)
... 11 more
Answer:
A few months ago, we started getting a lot of reports from users complaining about crashes in one of our apps. During investigation of the issue, it became clear that the error was due to some corruption in the SQLite
file. I noticed that this problem also existed from a certain period until then. I also saw that this error occurred especially with newer devices that have more processing cores and higher clocks, and that in older devices that are less powerful and especially with only one core, this error occurred very little or simply did not occur. This made me conclude that the problem was related to competition. That here was linked to SQLite
multi-thread access.
After some research and testing I saw the following points:
For each database there must be only one SqliteOpenHelper
. As much as it sounds, there are no multiple database connections. A SqliteOpenHelper
has only one connection. Ever. Even though the getWritableDatabase
and getReadableDatabase
methods make it look like there are two different connections, it just doesn't happen: it's the same connection.
This ensures that there are no collisions, concurrent accesses, to the database file.
In the case of the application in question, there were multiple SqliteOpenHelper
and multiple Threads. The solution was a general persistence refactoring with the goal of having only one SqliteOpenHelper
for each database (in this case there were three). There was thus a strong reduction in the occurrences of the problem, however there were still occurrences.
Finally, the database access methods were synchronized in order to avoid concurrent access to the SqliteOpenHelper
. This is a measure that did not affect the application's performance, but which must be analyzed for each case.
Summing up:
- Keep only one
SqliteOpenHelper
per database - If necessary and feasible, synchronize access