Running my app on an HTC Desire (android version 2.3.3) I noticed memory usage increasing when calling the following function:
private static final String TABLE_MODULES = "tbl_module1";
private static final String MOD1_INCLUDE = "mod1_include";
private void setItemInclude (int idx, boolean include)
{
String incString = "";
if (include)
incString = "true";
else
incString = "false";
ContentValues args = new ContentValues();
args.put(MOD1_INCLUDE, incString);
database.update(TABLE_MODULES,args,"_id="+idx,null);
}
where database is of type SQLiteDatabase and was obtained from a call to dbHelper.getWritableDatabase() and where dbHelper is derived from SQLiteOpenHelper and instantiated with the application context.
I get the desired update, but when I check the data usage of my application it appears to increase by about 60KB per call and is not released unless dbHelper.close() is called. Multiple calls to this function result in SQLiteDiskIOException.
I have tried database.rawQuery(), which also updates correctly but hangs on to memory similarly.
String updateQuery = "UPDATE " + TABLE_MODULES + " SET MOD1_INCLUDE = " + "'" + incString + "'" + " WHERE _id ='" + idx + "'";
Cursor cursor = database.rawQuery(updateQuery, null);
cursor.moveToFirst();
cursor.close();
Running the same code on an Asus (android 4.0.3) and an Orange San Francisco (android 2.1.update1) does not have this problem.
getWritableDatabase is called from the activity's onCreate() and the resulting database is closed in onDestroy(). I thought that it was ok to keep the database open for the duration of the activity. Any ideas as to what is the cause?
I have found what is causing the problem.
On the HTC, each time database.update() is called a temporary file is updated in the application's databases directory. This file has the extension .sqlite-wal. This file gets bigger (by 60KB) on each call and is only deleted when I close the database.
The ASUS also creates a temporary file but with the extension .sqlite-journal. This file is also updated on each update() call, but once the transaction completes the file's size reduces to zero. So, no memory problems result.
I am not sure what the difference is between Journal and Wal but believe that they are both to do with rolling back a database after a failed transaction. I have solved the memory problem with the following code, which turns off the journal/wal feature.
Cursor cursor = database.rawQuery("PRAGMA journal_mode=OFF", null);
cursor.moveToFirst();
cursor.close();
Calling this after opening the database appears to prevent these temporary files from being created.
However, I am not confident that this is a good idea. I was surprised not to find others reporting this issue and wonder if leaving the database open ready for multiple transactions is considered bad practice? I am new to SQL and have fairly limited experience with databases.
The documentation is quite clear.
The WAL journaling mode uses a write-ahead log instead of a rollback
journal to implement transactions.
In your case it sounds like automatic checkpointing is either not configured or not working. You probably don't want to turn journaling off as it can result in data loss.
Related
As I changed the android version and the path from externalized to the default database-folder, I don't know where the error come from.
I found that the reason might be problems accessing the journal file is locked (it is created by the program).
the "test" is reading data and if it exists it updates and if not it inserts. This is done in a loop while reading lines from a file. For testing purpose i simplified it but the error is the same.
package ...;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
public class test {
public test(SQLiteDatabase MyDB)
{
MyDB.execSQL("CREATE TABLE IF NOT EXISTS testtable (mytext TEXT, number INT PRIMARY KEY)", new String[] {});
for (int i = 1; i < 5; i++)
{
String[] ColArray = { "any text", String.valueOf(i) };
Cursor readCursor = MyDB.rawQuery("SELECT mytext FROM testtable WHERE number=?", new String[] { String.valueOf(i) });
if (!readCursor.moveToNext()) // Error when executing moveToNext
/*
In the 2nd time it runs over this point an Error occurs:
android.database.sqlite.SQLiteCantOpenDatabaseException: unable to open database file (code 14)
I need to remove the journal file to be able to connect to the database it again - the change made by the Insert/Update is successfully saved!
*/ {
readCursor.close();
MyDB.execSQL("INSERT INTO testtable (mytext, number) VALUES(?, ?)", ColArray);
}
else {
readCursor.close();
MyDB.execSQL("UPDATE testtable SET mytext=? WHERE number=?", ColArray);
}
}
}
}
MyDB is a valid DB-Connection.
1) The Table is created
2) The Table is READ but Line 1 is not found
2a) READ again is possible until now if the read is closed.
3) Line 1 is INSERTED
4) when trying to READ again (the line 2 would not be found, too), moving the cursor fails.
The journalfile need to be deleted manually or the app will fail to start again when trying to connect.
Please Help! Thank you.
Maybe you should use the specialized insert and update methods.
In the documentation of the SQLiteDatabase it says for execSQL:
Execute a single SQL statement that is NOT a SELECT/INSERT/UPDATE/DELETE.
There are specialized methods for insert and update with similar names. I guess, that there is some problem generated, when the unspecific "execSQL" statement is used.
i solved it after hours of testing.
possibly the sqlite version is different in the old and new system... i changed the rom again - for some reason, this time i didn't copy the databasefile into the data-folder. Instead i let the program create a empty database and imported the data from text by the program itself from a csv-export.
I currently don't know if the problem was within the rom or (what seem to be the reason in my opinion) i created the problem by using the origin database which might be read and writeable but has problems when the journalfile should get processed. if somebody has an equal problem, he should try to create a new empty database in the new system.
Can anybody explain me why following happens. This is Android app which is using SQLite db.
App adding new data to db, which stored in external memory.
At the same time, this external memory becomes unavailable (for example, I mount it to PC).
When external memory becomes avaliable again, I can't access db in read_only mode. But, if I try to open it in read-write mode, it goes well. Why?
I wrote code (below), which handle this problem and it works well, but I still don't understand why.
This is NOT related to remaining open connections or stuff like this. To insure, I totally removed my app from device and re-install it. But even after that, db was unavailable in read-only mode. After db opened once in read-write mode, it may be open in read-only next time without any problems.
When I say "unavailable", I mean that db opens well, but any query to db failed with IO error.
SQLiteDatabase db = SQLiteDatabase.openDatabase(f.getPath(), null, SQLiteDatabase.OPEN_READONLY | SQLiteDatabase.NO_LOCALIZED_COLLATORS);
if (!testQuery(db))
{
Utils.logD("Failed to open db at path: " + f + ". Trying recovering...");
db.close();
db = SQLiteDatabase.openDatabase(f.getPath(), null, SQLiteDatabase.NO_LOCALIZED_COLLATORS);
if (!testQuery(db))
{
Utils.logD("Failed to open db at path: " + f + " (noway)");
db.close();
return null;
}
}
Utils.logD("DATABASE OPENED! " + db.hashCode());
If the DB was made unavailable while a transaction was still active, this transaction must be rolled back before the DB is again in a consistent state. This rollback requires write access.
This implies that you should open a DB in readonly mode only if it gets never ever changed.
My app inserts some data into its SQLite DB and the app confirms that the insert was successful.
However, if power is lost shortly afterward, the data is not visible after the mobile device fully reboots.
It seems that there is some caching going on - how can I 'flush' the buffer / cache ?
Note: the sqlite db is located on the main flash memory, not on the sdcard.
Hi Someone Somewhere,
If you want to guarantee that a DB write occurs, you should use transactions.
ie: Here is a simple example which you could build off of.
DatabaseHelper readDB = new DatabaseHelper(Context);
SQLiteDatabase db = readDB.getWritableDatabase();
String lastConnID = "1";
String DATABASE_TABLE = "TestDB";
String KEY_ROWID = "pkey";
db.beginTransaction();
db.update(DATABASE_TABLE, values, KEY_ROWID + "="
+ "'" + lastConnID + "'", null);
db.setTransactionSuccessful();
db.endTransaction();
A sample DatabaseHelperClass can be found here.
When you call setTransactionSuccessful, the deed is done and the item should be committed to the DB. You should end the transaction right after this is called because it will still commit to the database even if an error occurs between setting the transaction successful and ending the transaction.
Hopefully this helps you!
Cheers.
I have an app that uses a database with 3 tables in it. Those 3 tables have data read from and written to them by activities and services.
Having gotten a few "android.database.sqlite.SQLiteException: database is locked" crashes, I went in to the database adapter class and wrapped every write, update, or delete function with a synchronized statement, so like:
public int deleteExpiredAlarms() {
String whereClause = FIELD_EXPIRED + " = 1";
int val = 0;
synchronized(dbWriteLock) {
val = db.delete(ALARM_DATABASE_TABLE, whereClause, null);
}
return val;
}
That seemed to make it better. But lately it's gotten bad again as I've added more services that read and write to different tables.
Do I need to synchronize ALL db access statements, including queries?
The exception is occurring on the attempt to open the writable database via the open helper...should I synchronize that act also?
I've heard that I should only be using one db helper so that there won't be issues with multiple threads accessing the db. How do I use only one db helper? Every example I've seen so far has the db helper as an instantiated value inside the db adapter....so wouldn't that be a separate db helper per db adapter instantiated (one in an activity, one in a service running,etc)
I've looked at using a content provider instead, as it's been claimed to solve problems like this, but it's really more work than I want to do if I should be able to have direct db access without locking issues. And I do not plan to make this db accessible to other apps.
Thanks for the help.
This link describes my problem exactly: http://old.nabble.com/Android-database-corruption-td28044218.html#a28044218
There are about 300 people using my Android App right now and every once and while I get a crash report to the server with this stack trace:
android.database.sqlite.SQLiteDatabaseCorruptException: database disk image is malformed
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2596)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2621)
at android.app.ActivityThread.access$2200(ActivityThread.java:126)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1932)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:123)
at android.app.ActivityThread.main(ActivityThread.java:4595)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:521)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:860)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)
at dalvik.system.NativeStart.main(Native Method) Caused by: android.database.sqlite.SQLiteDatabaseCorruptException: database disk image is malformed
at android.database.sqlite.SQLiteQuery.native_fill_window(Native Method)
at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:75)
at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:295)
at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:276)
at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:171)
at android.database.AbstractCursor.moveToFirst(AbstractCursor.java:248)
The result is the app crashing and all the data in the DB being lost.
One thing to note is that every time I read or write to the database I get a new SQLiteDatabase and close it as soon as I'm done. I did this in an attempt to prevent these kind of corruption errors.
I also tried synchronizing all DB reads and writes using a single static object and that didn't seem to help.
Is it possible this is just a SQLite bug?
I found a similar bug with the built-in email app here: http://code.google.com/p/android/issues/detail?id=5610.
Here is my code:
public class KeyValueTableAdapter extends BaseTableAdapter {
private String tableName;
private String keyColumnName;
private String valueColumnName;
public KeyValueTableAdapter(Context context, String tableName, String keyColumnName, String valueColumnName) {
super(context);
this.tableName = tableName;
this.keyColumnName = keyColumnName;
this.valueColumnName = valueColumnName;
}
protected String getStringValue(int key) {
Cursor cursor = null;
SQLiteDatabase db = null;
String value;
try {
db = dbOpenHelper.getReadableDatabase();
cursor = db.query(true, tableName, new String[] { valueColumnName }, keyColumnName + "=" + key, null, null, null, null, null);
if ((cursor.getCount() == 0) || !cursor.moveToFirst()) {
value = null;
} else {
value = cursor.getString(0);
}
} finally {
if (cursor != null) cursor.close();
if (db != null) db.close();
dbOpenHelper.close();
}
return value;
}
}
public abstract class BaseTableAdapter {
protected DbOpenHelper dbOpenHelper;
public BaseTableAdapter(Context context) {
this.dbOpenHelper = new DbOpenHelper(context, DatabaseSettings.DATABASE_NAME, null, DatabaseSettings.DATABASE_VERSION);
}
}
Most likely the database process(es) is getting killed during an I/O. For example by a task killer, or if you're allowing db write operations to continue during a time when the app should shutdown or sleep...
See if you can reproduce the issue by putting your app in a DB write loop and using a task killer on it.
Scenario: 32 bytes being written to the database, the writer task gets killed after only writing 10, result: database left in inconsistent and possibly corrupt state.
Also see:
Android process killer
EDIT: opening and closing the DB for each read/write? stop that! :)
Using multiple instances of SQLiteDatabase could be causing your problem if you have two instances updating the same database file at the same time.
Implement a backup db process each day, and if the DB gets corrupted you just replace the database with a backup. You can use simple file copy paste methods to maintain a backup each day.
Since I can't comment -yet- on Brad's post.
I have to agree with the extra overhead.
I have a HTC Magic as my daily phone, and ram is always an issue.
Android phones are at very different ends of the $$$
Some are super cheap and some are super expensive, this basically comes down to ram, and cpu.
People who run task killers do ruin their android phones.
As a developer you should suggest people not to use them, or just deny support to people who use task killers, since android doesn't need these "improvements" (ask steve (cyanogen))
Also the new statement in android is very expensive.
You want to limit the amount of new calls when programming for android.
Programming for android is all about reuse of the precious memory. (HTC Magics/Dreams only have 96MB available to applications, and most of it is already in use)
As for your SQLiteDB...the API says that your SQLiteDB is private to your application.
I don't see why you need to open and close a NEW connection to it each time you want to read or write to it.
I would rather keep the connection open until the user looses focus from it.
However, if you are writing a content provider, that is a different story.
"the DB holds session information so
it's not very feasible to do a backup.
The data changes by the minute"
You should try using SharedPreferences: it stores key-value pairs (in the background, it uses a file).
Storing values:
SharedPreferences sp=MyActivity.getSharedPreferences("Name", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString("key", value);
editor.putBoolean("another", true);
editor.commit();
Retrieving data:
sp.getString("key", "Not found");
// "Not found" is the default value
// if sp does not contain the specified key
sp.getBoolean("another", false);
// false is the default value
// if sp does not contain the specified key
See getSharedPreferences and SharedPreferences for a more detailed description.