SQLiteOpenHelper synchronization - android

So I've come up with some idea and I'm wondering if it is realizable.
Let's say I've multiple tables(database models) each of them is represented by some class.I don't wont to use singleton pattern with the open helper so I've created some simple class to provide single instance of the database.My idea is that as long as all tables hold reference to SQLiteDatabase(returned by the open helper) they will all work with the same DB instance and probably won't be needed to synchronized the work with the database since the open helper do this.When the last table finish it's work the GC will collect the open helper (since the last reference will be weak reference) -> finalize() is called and I close the db during this method to prevent any warning from OS. My question is: Is this could work?Will it close automatically the DB and will it leak or throw some exception?
Here is my class:
public class DatabaseHelper {
private static WeakReference<SomeCustomOpenHelper> sDBOpenHelper;
private void notifyDBCreate(SQLiteDatabase db) {
for (DBTable table : mTables) {
table.onDBCreate(db);
}
}
private void notifyDBUpgrade(SQLiteDatabase db) {
for (DBTable table : mTables) {
table.onDBUpgrade(db);
}
}
public SQLiteDatabase getDatabase(boolean readOnly) {
SomeCustomOpenHelper dbHelper = sDBOpenHelper.get();
if (dbHelper == null) {
dbHelper = new SomeCustomOpenHelper(context, name, factory, version, new DatabaseEventsCallback());
sDBOpenHelper = new WeakReference<SomeCustomOpenHelper>(dbHelper);
}
if (readOnly) {
return dbHelper.getReadableDatabase();
} else {
return dbHelper.getWritableDatabase();
}
}
private class DatabaseEventsCallback implements IDatabaseEventsCallback {
#Override
public void onCreate(SQLiteDatabase db) {
notifyDBCreate(db);
}
#Override
public void onUpgrade(SQLiteDatabase db) {
notifyDBUpgrade(db);
}
}
interface IDatabaseEventsCallback {
void onCreate(SQLiteDatabase db);
void onUpgrade(SQLiteDatabase db);
}
private static class SomeCustomOpenHelper extends SQLiteOpenHelper {
private IDatabaseEventsCallback mCB;
public SomeCustomOpenHelper(Context context, String name, CursorFactory factory, int version, IDatabaseEventsCallback cb) {
super(context, name, factory, version);
mCB = cb;
}
#Override
public void onCreate(SQLiteDatabase db) {
mCB.onCreate(db);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
mCB.onUpgrade(db);
}
#Override
protected void finalize() throws Throwable {
this.close();
super.finalize();
}
}
}

Did not really know the answer neither, but got interested and looked it up.
The answer is written out properly here;
http://blog.foxxtrot.net/2009/01/a-sqliteopenhelper-is-not-a-sqlitetablehelper.html
But basically the core of the info is;
I created three SQLiteOpenHelper classes, one for each table, even though they all referenced only a single database file.
Here is where everything fell apart. Android maintains Versions for databases based on the package it’s associated with, the name of the database, and the version number you provide. The package and name go into decided what the path on the device will be, while the version is stored (somewhere) on the device so that it knows when it needs to call an OpenHelper’s onUpgrade event handler. It turns out that if, in the SQLiteOpenHelper Constructor, it determines that the database already exists, it won’t call your onCreate or onUpgrade methods at all, even if the particular class which is making the call has never been called before.

I've been through the same issue when I was working on a project. I also went crazy on the doubt if the static instance was using enough memory and causing a considerable memory leak.
I'm not sure if creating a weak reference would guarantee that database instance would be collected. However a possible workaround could be : Assigning a null value to static database instance once your all database transaction is done and you've close the database. This might ensure that the database instance no more allocates any memory.
Let me know if this works or if there is a better work-around.

You can do so. As you say the locking should be happening on the SQLite and I've never heard issues around that so you should be fine with this.
The only restriction you have is that all the tables will have to go into the same database since Android for now just allows you to have one file.
Closing the database is a different thing, that's why it is actually interesting to use the singleton pattern (you avoid closing + opening all the time).
Nonetheless with your approach you just need to make sure to close the db whenever you are done with it. As far as I'm concerned this is not automatically done.
Additionally Lars Vogel has written extremely useful and detailed articles around DB access in Android. You might want to have a look there. http://www.vogella.com/articles/AndroidSQLite/article.html

you can use one open helper for all the table .i am using the single instance in my app also like this .
public static synchronized DatabaseHelper getInstance(Context ctx)
{
if (dbhelper == null) {
dbhelper = new DatabaseHelper(ctx);
}
return dbhelper ;
}

My question is: Is this could work?Will it close automatically the DB
and will it leak or throw some exception?
NO it will not close automatically database , when your application will demand for DATABASE object and OS found some of your database instant are alive then Android framework try to connect that object reference (which is probably weak reference )
and i have to say , I don't recommend opening and closing a DATABASE on-demand or temporarily . It is always nice to open the DB up early and keep it open for the duration of your whole activity and close it when the activity gets finished or suspended .

Related

Android ORMLite DbHelper onCreate() not called even after uninstall

I'm getting this error when I run my app for the first time after re-install:
android.database.sqlite.SQLiteException: no such table
(This error happens when my app tries to read from the database)
For some reason the onCreate() method in DBHelper is not getting called and therefore the tables are not getting created. I followed the advice from other question and tried calling getWritableDatabase(), also tried a create() call to insert data in some table, but still no luck: onCreate is never called.
I got it to work however by changing the DATABASE_VERSION value to 2. But that doesn't make sense since this is a brand new installation after uninstall.
Also I found that before the SQL read error the database got created but it has only 1 table "android_metadata" (not created by me).
I'm posting some code here for reference
public class DatabaseHelper extends OrmLiteSqliteOpenHelper{
private static final String DATABASE_NAME = "RoutePlanner.db";
private static final int DATABASE_VERSION = 1;
private Dao<Trip, Integer> tripDAO = null;
private RuntimeExceptionDao<Trip, Integer> tripRunTimeDAO = null;
...
}
#Override
public SQLiteDatabase getWritableDatabase() {
return super.getWritableDatabase();
}
public DatabaseHelper(Context context){
super(context, DATABASE_NAME,null, DATABASE_VERSION, R.raw.ormlite_config);
}
#Override
public void onCreate(SQLiteDatabase db, ConnectionSource source) {
try {
Log.i(DatabaseHelper.class.getSimpleName(), "onCreate");
TableUtils.createTable(source, Trip.class);
...
} catch (SQLException ex) {
Log.e(DatabaseHelper.class.getSimpleName(), "Error creating db", ex);
throw new RuntimeException(ex);
}
}
OK, I found the problem, hope this explanations helps others on what NOT to do. The issue was that I had a separate calendar module which I wanted to access my Database. To make things 'simpler' I created a separate DatabaseHelper on that module to access the same SQLite databse as my main module. The existance of the 2nd DatabaseHelper was causing all my issues. Solutions are either join the 2 modules into one, or use a Database Service Provider

Correctly open/close a database with Singleton design pattern

I am creating an application which makes a lot of interactions with a database (both read and write operations).
To avoid open/close operations at each request, I created a class extending SQLiteOpenHelper with a Singleton design pattern. This way, I am sure only one instance of the SQLiteOpenHelper and only one connection to the database is made during all the application lifecycle (and not only activity lifecycle).
I also read some articles about ContentProvider, but I am not sure it's a better way.
So, this is the main logic of my Singleton class (onCreate and onUpgrade removed) :
public final class BaseSQLite extends SQLiteOpenHelper {
private static BaseSQLite mInstance = null;
private SQLiteDatabase db = null;
public static BaseSQLite getInstance(Context context) {
if (mInstance == null) {
mInstance = new BaseSQLite(context.getApplicationContext(),
DBNAME, DBVERSION);
}
return mInstance;
}
private BaseSQLite(final Context context, final String name,
final int version) {
super(context, name, null, version);
db = getWritableDatabase();
}
#Override
public synchronized void close() {
if (mInstance != null)
db.close();
}
public Cursor getAllData() {
String buildSQL = "SELECT * FROM myTable";
return db.rawQuery(buildSQL, null);
}
}
So, to access my database, I made this :
BaseSQLite baseSQLite = BaseSQLite.getInstance(context);
baseSQLite.getAllData();
It works perfectly for now. But my question is about the close() method. I really don't know when to call it. Actually, my database instance is the same for every Activies of my application, so I think it's a bad idea to call close() in an onPause() method, because the instance will be potentially (and it will often happens) recreated in the onStart() method of the next Activity. Also, I can't detect the end of my application, i.e. when no activity is visible on the screen anymore.
Can somebody give me some help about this issue ? I found some answer when the database is linked to ONE activity, but no really hint is given for my case.
You should call close anytime you are done writing to your database. For example when you insert data, you will have an open connection to the database that should be closed when it is done.
Reading is different. When you create a SQLite database on your phone, the data is persistent. The database exists and the handler you create provides a convenient way to access that information. Reading the database usually takes place by getting a readable instance of the database and using a Cursor to extract values. In that case you close the cursor when you're done, not the database itself.
You're right that you should not be closing the database connection during separate activities' lifecycle methods. Instead, as suggested above, close the database connection in your handler's methods that write to the database when you are done performing that transaction.

SQLite database in separate class vs. in same class, which is better? Android

I have an SQLite database that is in a separate class from the main class that extends Activity.
I noticed that there are two ways of setting up the database. one way is to put it inside the main Activity class, either in the class or as a nested sub class. the second way is to put it in the separate class.
the separate class looks better, however there is one disadvantage. You have to create an instance of it in the main activity class every time you want to do something. I read that instantiating objects in Android is expensive and should be avoided.
despite this, I would rather make the database as a separate class. Is the cost of instantiating objects enough that it makes putting the database in the same class a better deal?
example of separate class for SQLite database: incomplete psudo-code
public class SQLiteDB {
private static class DbHelper extends SQLiteOpenHelper{
// db helper methods
}
// methods for DB, like get, set, and others
public void openDatabase(){ }
public void closeDatabse(){ }
public void insertRecord(String record){ }
}
example use in main Activity: incompete psudo-code
public class Main extends Activity{
// every time I want to use it I must instantiate an object for the database class
// many instances of SQLiteDB object created, garbage collector works hard
SQLiteDB mDatabase = new SQLiteDB();
openDatabase();
insertRecord("insert this");
closeDatabase();
}
SQLite database in separate class vs. in same class, which is better?
This is very comprehensive question and it depends on more factors(type of application, personal requirements, how you'll deal with db etc.). Somebody can prefer to place database as inner class and someone as separated class. Problem is that many developers are trying to "stick" as much code as possible into one class and maybe they "fear" to create a little more classes. I don't know that exactly. I mentioned that only as my personal note.
But let's back to your question. What is better?
I think that approach with separeted class. You should let your Activity classes only "Activity classes" > only for creating and dealing with UI. Application appearance should be separated from application logic. If you'll follow this "rule" your code will become more clean and human-readable(if someone else will look at your code he shouldn't be completely lost). It's not a shame to have 20 purely written classes as to have all stuff sticked in one class(like a pig).
however there is one disadvantage. You have to create an instance of
it in the main activity class every time you want to do something. I
read that instantiating objects in Android is expensive and should be
avoided.
Did you think about an usage of Singleton? This design pattern is worth to think about it. You will always have only one instance that have many benefits e.q. no waste of memory. I have only good experiences with Singleton. Therefore i recommend you to try and use it.
Example:
private static SQLiteOpenHelper instance;
public static SQLiteOpenHelper getInstance(Context mContext) {
if (instance == null) {
instance = new SQLiteOpenHelperImplementation(mContext);
}
return instance;
}
And at the end i give you a few suggestions:
Everytime you'll work with cursors, databases etc. release / close
them immediately after work is done. This can solve many exceptions
related to SQLiteDatabase and Cursor
An usage of synchronized blocks and methods is pretty good practise
in the case of concurrent programming to avoid many problems
If you have more than one table in database i suggest you create
"serving" class for each table that will wrap CRUD operations and specific
methods of the table
Before Activity is destroyed, check and release all sources which are not
already released.
I prefer the solution you gave here. The primary advantage is that you can easily access the database from any Activity (or other class) in your app. To solve the problem of creating a new instance every time you use the database, you can instead create a single instance in onCreate(), use the database all you want while the Activity is active, and finally close the database in onDestroy().
This would be a matter of personal taste.
However, what I've found to be efficient and clean has been to create a class that extends SQLiteOpenHelper. In this class you will end up writing the SQL code to create your tables and writing methods as your stored procedures.
The class would look something like this:
public class DatabaseInterface extends SQLiteOpenHelper {
// Database version
private static final int DATABASE_VERSION = 1;
public DatabaseInterface(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
//in your oncreate you will write the queries to create your tables
#Override
public void onCreate(SQLiteDatabase db) {
String CREATE_NEWS = "CREATE TABLE News(id INTEGER)";
db.execSQL(CREATE_NEWS);
}
// upgrading tables
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// drop tables if exist
db.execSQL("DROP TABLE IF EXSIST " + NEWS);
// recreate tables
onCreate(db);
}
Consider we have a News obj that takes 1 param as it's constructor, your stored procedures can look something like this:
public ArrayList<News> getNews() {
ArrayList<News> mNewes = new ArrayList<News>();
SQLiteDatabase db = null;
Cursor cursor = null;
try {
String sQry = "SELECT * FROM " + NEWS;
db = this.getWritableDatabase();
cursor = db.rawQuery(sQry, null);
if (cursor.moveToFirst()) {
do {
mNewes.add(new News(cursor.getInt(0)));
} while (cursor.moveToNext());
}
} catch (SQLiteException e) {
Log.e("SQLite - getNewes", e.getMessage());
return null;
} finally {
cursor.close();
db.close();
}
return mNewes;
}
In the above method you get and open the application database preform the query on it, anticipating any sql errors and then close the database. Doing it this way assures that you never have any resources open that you don't need/aren't using.
I've used this method in two apps that are currently out in the market and it runs rather quickly with making several hundred calls to methods I created for my stored procedures

Closing the database in a ContentProvider

This week I've been learning all about ContentProvider and using the SQLiteOpenHelper class to manage the creation and upgrading of the database inside of a provider. Specifically, I've been reading through the NotePad example from the sdk's samples directory.
Now, I can see that SQLiteOpenHelper has a close() method. I'm aware that leaving idle databases open is bad practice and can cause memory leaks and whatnot (unless this discussion is headed in the right direction). If I were using the class in an Activity, then I would simply call close() in the onDestroy() method, but as far as I know, ContentProvider does not have the same life cycle that activities do. The code for NotePad never seems to call close(), so I would like to assume that it is handled by SQLiteOpenHelper or some other piece of the puzzle, but I'd really like to know for sure. I don't really trust the sample code that much, either...
Question summary: When should we close the database in a provider, if at all?
According to Dianne Hackborn (Android framework engineer) there is no need to close the database in a content provider.
A content provider is created when its hosting process is created, and
remains around for as long as the process does, so there is no need to
close the database -- it will get closed as part of the kernel
cleaning up the process's resources when the process is killed.
Thanks #bigstones for pointing this out.
This question is a bit old but is still quite relevant. Note that if you're doing things the 'modern' way (e.g. using LoaderManager and creating CursorLoaders to query a ContentProvider in a background thread), make sure that you do NOT call db.close() in your ContentProvider implementation. I was getting all sorts of crashes relating to CursorLoader/AsyncTaskLoader when it tried to access the ContentProvider in a background thread, which were resolved by removing the db.close() calls.
So if you're running into crashes that look like this (Jelly Bean 4.1.1):
Caused by: java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.
at android.database.sqlite.SQLiteConnectionPool.throwIfClosedLocked(SQLiteConnectionPool.java:962)
at android.database.sqlite.SQLiteConnectionPool.waitForConnection(SQLiteConnectionPool.java:677)
at android.database.sqlite.SQLiteConnectionPool.acquireConnection(SQLiteConnectionPool.java:348)
at android.database.sqlite.SQLiteSession.acquireConnection(SQLiteSession.java:894)
at android.database.sqlite.SQLiteSession.executeForCursorWindow(SQLiteSession.java:834)
at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:62)
at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:143)
at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:133)
at android.content.ContentResolver.query(ContentResolver.java:388)
at android.content.ContentResolver.query(ContentResolver.java:313)
at com.hindsightlabs.paprika.loaders.GroceryListLoader.loadInBackground(GroceryListLoader.java:147)
at com.hindsightlabs.paprika.loaders.GroceryListLoader.loadInBackground(GroceryListLoader.java:1)
at android.support.v4.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:240)
at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:51)
at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:40)
at android.support.v4.content.ModernAsyncTask$2.call(ModernAsyncTask.java:123)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
... 4 more
Or this (ICS 4.0.4):
Caused by: java.lang.IllegalStateException: database /data/data/com.hindsightlabs.paprika/databases/Paprika.db (conn# 0) already closed
at android.database.sqlite.SQLiteDatabase.verifyDbIsOpen(SQLiteDatabase.java:2215)
at android.database.sqlite.SQLiteDatabase.lock(SQLiteDatabase.java:436)
at android.database.sqlite.SQLiteDatabase.lock(SQLiteDatabase.java:422)
at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:79)
at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:164)
at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:156)
at android.content.ContentResolver.query(ContentResolver.java:318)
at android.support.v4.content.CursorLoader.loadInBackground(CursorLoader.java:49)
at android.support.v4.content.CursorLoader.loadInBackground(CursorLoader.java:35)
at android.support.v4.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:240)
at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:51)
at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:40)
at android.support.v4.content.ModernAsyncTask$2.call(ModernAsyncTask.java:123)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
... 4 more
Or if you're seeing error messages in LogCat that look like this:
Cursor: invalid statement in fillWindow()
Then check your ContentProvider implementation and make sure you're not closing the database prematurely. According to this, the ContentProvider will get cleaned up automatically when the process is killed anyway, so you don't need to close its database ahead of time.
That said, make sure you are still correctly:
Closing your Cursors that are returned from ContentProvider.query(). (CursorLoader/LoaderManager does this automatically for you, but if you're doing direct queries outside of the LoaderManager framework, or you've implemented a custom CursorLoader/AsyncTaskLoader subclass, you'll have to make sure you're cleaning up your cursors properly.)
Implementing your ContentProvider in a thread-safe way. (The easiest way to do this is to make sure your database access methods are wrapped in a synchronized block.)
Ive follow Mannaz's answer and saw that SQLiteCursor(database, driver, table, query); constructor is deprecated. Then I found getDatabase() method and used it instead of mDatabase pointer; and kept constructor for backward capability
public class MyOpenHelper extends SQLiteOpenHelper {
public static final String TAG = "MyOpenHelper";
public static final String DB_NAME = "myopenhelper.db";
public static final int DB_VESRION = 1;
public MyOpenHelper(Context context) {
super(context, DB_NAME, new LeaklessCursorFactory(), DB_VESRION);
}
//...
}
public class LeaklessCursor extends SQLiteCursor {
static final String TAG = "LeaklessCursor";
public LeaklessCursor(SQLiteDatabase db, SQLiteCursorDriver driver,
String editTable, SQLiteQuery query) {
super(db, driver, editTable, query);
}
#Override
public void close() {
final SQLiteDatabase db = getDatabase();
super.close();
if (db != null) {
Log.d(TAG, "Closing LeaklessCursor: " + db.getPath());
db.close();
}
}
}
public class LeaklessCursorFactory implements CursorFactory {
#Override
public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
String editTable, SQLiteQuery query) {
return new LeaklessCursor(db,masterQuery,editTable,query);
}
}
If you want your Database to close automatically you can provide a CursorFactory when opening it:
mContext.openOrCreateDatabase(DB_NAME, SQLiteDatabase.OPEN_READWRITE, new LeaklessCursorFactory());
Here are the classes:
public class LeaklessCursorFactory implements CursorFactory {
#Override
public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
String editTable, SQLiteQuery query) {
return new LeaklessCursor(db,masterQuery,editTable,query);
}
}
public class LeaklessCursor extends SQLiteCursor {
static final String TAG = "LeaklessCursor";
final SQLiteDatabase mDatabase;
public LeaklessCursor(SQLiteDatabase database, SQLiteCursorDriver driver, String table, SQLiteQuery query) {
super(database, driver, table, query);
mDatabase = database;
}
#Override
public void close() {
Log.d(TAG, "Closing LeaklessCursor: " + mDatabase.getPath());
super.close();
if (mDatabase != null) {
mDatabase.close();
}
}
}
Close it when you are done with it, preferably in a finally block so you can ensure that it happens. I know that sounds a little trite and off-the-cuff, but it's really the only answer that I know of. If you open the database and perform an action, close it when you're done with that action unless you know for a fact it will be needed again (in which case be sure to close it once its no longer needed).
If you are using your content provider within a activity, then I do not believe that you have to maintain the connection of the content provider. You could just manage the cursor object returned using startManagingCursor. In the onPause method of activity, you can release the content provider. ( you can reload it in onResume). Assuming that the activity life cycle will usually be limited, this would suffice. (Atleast according to me ;))

How can I avoid concurrency problems when using SQLite on Android?

What would be considered the best practices when executing queries on an SQLite database within an Android app?
Is it safe to run inserts, deletes and select queries from an AsyncTask's doInBackground? Or should I use the UI Thread? I suppose that database queries can be "heavy" and should not use the UI thread as it can lock up the app - resulting in an Application Not Responding (ANR).
If I have several AsyncTasks, should they share a connection or should they open a connection each?
Are there any best practices for these scenarios?
Inserts, updates, deletes and reads are generally OK from multiple threads, but Brad's answer is not correct. You have to be careful with how you create your connections and use them. There are situations where your update calls will fail, even if your database doesn't get corrupted.
The basic answer.
The SqliteOpenHelper object holds on to one database connection. It appears to offer you a read and write connection, but it really doesn't. Call the read-only, and you'll get the write database connection regardless.
So, one helper instance, one db connection. Even if you use it from multiple threads, one connection at a time. The SqliteDatabase object uses java locks to keep access serialized. So, if 100 threads have one db instance, calls to the actual on-disk database are serialized.
So, one helper, one db connection, which is serialized in java code. One thread, 1000 threads, if you use one helper instance shared between them, all of your db access code is serial. And life is good (ish).
If you try to write to the database from actual distinct connections at the same time, one will fail. It will not wait till the first is done and then write. It will simply not write your change. Worse, if you don’t call the right version of insert/update on the SQLiteDatabase, you won’t get an exception. You’ll just get a message in your LogCat, and that will be it.
So, multiple threads? Use one helper. Period. If you KNOW only one thread will be writing, you MAY be able to use multiple connections, and your reads will be faster, but buyer beware. I haven't tested that much.
Here's a blog post with far more detail and an example app.
Android Sqlite Locking (Updated link 6/18/2012)
Android-Database-Locking-Collisions-Example by touchlab on GitHub
Gray and I are actually wrapping up an ORM tool, based off of his Ormlite, that works natively with Android database implementations, and follows the safe creation/calling structure I describe in the blog post. That should be out very soon. Take a look.
In the meantime, there is a follow up blog post:
Single SQLite connection
Also checkout the fork by 2point0 of the previously mentioned locking example:
Android-Database-Locking-Collisions-Example by 2point0 on GitHub
Concurrent Database Access
Same article on my blog(I like formatting more)
I wrote small article which describe how to make access to your android database thread safe.
Assuming you have your own SQLiteOpenHelper.
public class DatabaseHelper extends SQLiteOpenHelper { ... }
Now you want to write data to database in separate threads.
// Thread 1
Context context = getApplicationContext();
DatabaseHelper helper = new DatabaseHelper(context);
SQLiteDatabase database = helper.getWritableDatabase();
database.insert(…);
database.close();
// Thread 2
Context context = getApplicationContext();
DatabaseHelper helper = new DatabaseHelper(context);
SQLiteDatabase database = helper.getWritableDatabase();
database.insert(…);
database.close();
You will get following message in your logcat and one of your changes will not be written.
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
This is happening because every time you create new SQLiteOpenHelper object you are actually making new database connection. If you try to write to the database from actual distinct connections at the same time, one will fail. (from answer above)
To use database with multiple threads we need to make sure we are using one database connection.
Let’s make singleton class Database Manager which will hold and return single SQLiteOpenHelper object.
public class DatabaseManager {
private static DatabaseManager instance;
private static SQLiteOpenHelper mDatabaseHelper;
public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
if (instance == null) {
instance = new DatabaseManager();
mDatabaseHelper = helper;
}
}
public static synchronized DatabaseManager getInstance() {
if (instance == null) {
throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
" is not initialized, call initialize(..) method first.");
}
return instance;
}
public SQLiteDatabase getDatabase() {
return new mDatabaseHelper.getWritableDatabase();
}
}
Updated code which write data to database in separate threads will look like this.
// In your application class
DatabaseManager.initializeInstance(new MySQLiteOpenHelper());
// Thread 1
DatabaseManager manager = DatabaseManager.getInstance();
SQLiteDatabase database = manager.getDatabase()
database.insert(…);
database.close();
// Thread 2
DatabaseManager manager = DatabaseManager.getInstance();
SQLiteDatabase database = manager.getDatabase()
database.insert(…);
database.close();
This will bring you another crash.
java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase
Since we are using only one database connection, method getDatabase() return same instance of SQLiteDatabase object for Thread1 and Thread2. What is happening, Thread1 may close database, while Thread2 is still using it. That’s why we have IllegalStateException crash.
We need to make sure no-one is using database and only then close it. Some folks on stackoveflow recommended to never close your SQLiteDatabase. This will result in following logcat message.
Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed
Working sample
public class DatabaseManager {
private int mOpenCounter;
private static DatabaseManager instance;
private static SQLiteOpenHelper mDatabaseHelper;
private SQLiteDatabase mDatabase;
public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
if (instance == null) {
instance = new DatabaseManager();
mDatabaseHelper = helper;
}
}
public static synchronized DatabaseManager getInstance() {
if (instance == null) {
throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
" is not initialized, call initializeInstance(..) method first.");
}
return instance;
}
public synchronized SQLiteDatabase openDatabase() {
mOpenCounter++;
if(mOpenCounter == 1) {
// Opening new database
mDatabase = mDatabaseHelper.getWritableDatabase();
}
return mDatabase;
}
public synchronized void closeDatabase() {
mOpenCounter--;
if(mOpenCounter == 0) {
// Closing database
mDatabase.close();
}
}
}
Use it as follows.
SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way
Every time you need database you should call openDatabase() method of DatabaseManager class. Inside this method, we have a counter, which indicate how many times database is opened. If it equals to one, it means we need to create new database connection, if not, database connection is already created.
The same happens in closeDatabase() method. Every time we call this method, counter is decreased, whenever it goes to zero, we are closing database connection.
Now you should be able to use your database and be sure it's thread safe.
Use a Thread or AsyncTask for long-running operations (50ms+). Test your app to see where that is. Most operations (probably) don't require a thread, because most operations (probably) only involve a few rows. Use a thread for bulk operations.
Share one SQLiteDatabase instance for each DB on disk between threads and implement a counting system to keep track of open connections.
Are there any best practices for these scenarios?
Share a static field between all your classes. I used to keep a singleton around for that and other things that need to be shared. A counting scheme (generally using AtomicInteger) also should be used to make sure you never close the database early or leave it open.
My solution:
The old version I wrote is available at https://github.com/Taeluf/dev/tree/main/archived/databasemanager and is not maintained. If you want to understand my solution, look at the code and read my notes. My notes are usually pretty helpful.
copy/paste the code into a new file named DatabaseManager. (or download it from github)
extend DatabaseManager and implement onCreate and onUpgrade like you normally would. You can create multiple subclasses of the one DatabaseManager class in order to have different databases on disk.
Instantiate your subclass and call getDb() to use the SQLiteDatabase class.
Call close() for each subclass you instantiated
The code to copy/paste:
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import java.util.concurrent.ConcurrentHashMap;
/** Extend this class and use it as an SQLiteOpenHelper class
*
* DO NOT distribute, sell, or present this code as your own.
* for any distributing/selling, or whatever, see the info at the link below
*
* Distribution, attribution, legal stuff,
* See https://github.com/JakarCo/databasemanager
*
* If you ever need help with this code, contact me at support#androidsqlitelibrary.com (or support#jakar.co )
*
* Do not sell this. but use it as much as you want. There are no implied or express warranties with this code.
*
* This is a simple database manager class which makes threading/synchronization super easy.
*
* Extend this class and use it like an SQLiteOpenHelper, but use it as follows:
* Instantiate this class once in each thread that uses the database.
* Make sure to call {#link #close()} on every opened instance of this class
* If it is closed, then call {#link #open()} before using again.
*
* Call {#link #getDb()} to get an instance of the underlying SQLiteDatabse class (which is synchronized)
*
* I also implement this system (well, it's very similar) in my Android SQLite Libray at http://androidslitelibrary.com
*
*
*/
abstract public class DatabaseManager {
/**See SQLiteOpenHelper documentation
*/
abstract public void onCreate(SQLiteDatabase db);
/**See SQLiteOpenHelper documentation
*/
abstract public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
/**Optional.
* *
*/
public void onOpen(SQLiteDatabase db){}
/**Optional.
*
*/
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
/**Optional
*
*/
public void onConfigure(SQLiteDatabase db){}
/** The SQLiteOpenHelper class is not actually used by your application.
*
*/
static private class DBSQLiteOpenHelper extends SQLiteOpenHelper {
DatabaseManager databaseManager;
private AtomicInteger counter = new AtomicInteger(0);
public DBSQLiteOpenHelper(Context context, String name, int version, DatabaseManager databaseManager) {
super(context, name, null, version);
this.databaseManager = databaseManager;
}
public void addConnection(){
counter.incrementAndGet();
}
public void removeConnection(){
counter.decrementAndGet();
}
public int getCounter() {
return counter.get();
}
#Override
public void onCreate(SQLiteDatabase db) {
databaseManager.onCreate(db);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
databaseManager.onUpgrade(db, oldVersion, newVersion);
}
#Override
public void onOpen(SQLiteDatabase db) {
databaseManager.onOpen(db);
}
#Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
databaseManager.onDowngrade(db, oldVersion, newVersion);
}
#Override
public void onConfigure(SQLiteDatabase db) {
databaseManager.onConfigure(db);
}
}
private static final ConcurrentHashMap<String,DBSQLiteOpenHelper> dbMap = new ConcurrentHashMap<String, DBSQLiteOpenHelper>();
private static final Object lockObject = new Object();
private DBSQLiteOpenHelper sqLiteOpenHelper;
private SQLiteDatabase db;
private Context context;
/** Instantiate a new DB Helper.
* <br> SQLiteOpenHelpers are statically cached so they (and their internally cached SQLiteDatabases) will be reused for concurrency
*
* #param context Any {#link android.content.Context} belonging to your package.
* #param name The database name. This may be anything you like. Adding a file extension is not required and any file extension you would like to use is fine.
* #param version the database version.
*/
public DatabaseManager(Context context, String name, int version) {
String dbPath = context.getApplicationContext().getDatabasePath(name).getAbsolutePath();
synchronized (lockObject) {
sqLiteOpenHelper = dbMap.get(dbPath);
if (sqLiteOpenHelper==null) {
sqLiteOpenHelper = new DBSQLiteOpenHelper(context, name, version, this);
dbMap.put(dbPath,sqLiteOpenHelper);
}
//SQLiteOpenHelper class caches the SQLiteDatabase, so this will be the same SQLiteDatabase object every time
db = sqLiteOpenHelper.getWritableDatabase();
}
this.context = context.getApplicationContext();
}
/**Get the writable SQLiteDatabase
*/
public SQLiteDatabase getDb(){
return db;
}
/** Check if the underlying SQLiteDatabase is open
*
* #return whether the DB is open or not
*/
public boolean isOpen(){
return (db!=null&&db.isOpen());
}
/** Lowers the DB counter by 1 for any {#link DatabaseManager}s referencing the same DB on disk
* <br />If the new counter is 0, then the database will be closed.
* <br /><br />This needs to be called before application exit.
* <br />If the counter is 0, then the underlying SQLiteDatabase is <b>null</b> until another DatabaseManager is instantiated or you call {#link #open()}
*
* #return true if the underlying {#link android.database.sqlite.SQLiteDatabase} is closed (counter is 0), and false otherwise (counter > 0)
*/
public boolean close(){
sqLiteOpenHelper.removeConnection();
if (sqLiteOpenHelper.getCounter()==0){
synchronized (lockObject){
if (db.inTransaction())db.endTransaction();
if (db.isOpen())db.close();
db = null;
}
return true;
}
return false;
}
/** Increments the internal db counter by one and opens the db if needed
*
*/
public void open(){
sqLiteOpenHelper.addConnection();
if (db==null||!db.isOpen()){
synchronized (lockObject){
db = sqLiteOpenHelper.getWritableDatabase();
}
}
}
}
The Database is very flexible with multi-threading. My apps hit their DBs from many different threads simultaneously and it does just fine. In some cases I have multiple processes hitting the DB simultaneously and that works fine too.
Your async tasks - use the same connection when you can, but if you have to, its OK to access the DB from different tasks.
after struggling with this for a couple of hours, I've found that you can only use one db helper object per db execution. For example,
for(int x = 0; x < someMaxValue; x++)
{
db = new DBAdapter(this);
try
{
db.addRow
(
NamesStringArray[i].toString(),
StartTimeStringArray[i].toString(),
EndTimeStringArray[i].toString()
);
}
catch (Exception e)
{
Log.e("Add Error", e.toString());
e.printStackTrace();
}
db.close();
}
as apposed to:
db = new DBAdapter(this);
for(int x = 0; x < someMaxValue; x++)
{
try
{
// ask the database manager to add a row given the two strings
db.addRow
(
NamesStringArray[i].toString(),
StartTimeStringArray[i].toString(),
EndTimeStringArray[i].toString()
);
}
catch (Exception e)
{
Log.e("Add Error", e.toString());
e.printStackTrace();
}
}
db.close();
creating a new DBAdapter each time the loop iterates was the only way I could get my strings into a database through my helper class.
Dmytro's answer works fine for my case.
I think it's better to declare the function as synchronized. at least for my case, it would invoke null pointer exception otherwise, e.g. getWritableDatabase not yet returned in one thread and openDatabse called in another thread meantime.
public synchronized SQLiteDatabase openDatabase() {
if(mOpenCounter.incrementAndGet() == 1) {
// Opening new database
mDatabase = mDatabaseHelper.getWritableDatabase();
}
return mDatabase;
}
You can try to apply new architecture approach anounced at Google I/O 2017.
It also includes new ORM library called Room
It contains three main components: #Entity, #Dao and #Database
User.java
#Entity
public class User {
#PrimaryKey
private int uid;
#ColumnInfo(name = "first_name")
private String firstName;
#ColumnInfo(name = "last_name")
private String lastName;
// Getters and setters are ignored for brevity,
// but they're required for Room to work.
}
UserDao.java
#Dao
public interface UserDao {
#Query("SELECT * FROM user")
List<User> getAll();
#Query("SELECT * FROM user WHERE uid IN (:userIds)")
List<User> loadAllByIds(int[] userIds);
#Query("SELECT * FROM user WHERE first_name LIKE :first AND "
+ "last_name LIKE :last LIMIT 1")
User findByName(String first, String last);
#Insert
void insertAll(User... users);
#Delete
void delete(User user);
}
AppDatabase.java
#Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
My understanding of SQLiteDatabase APIs is that in case you have a multi threaded application, you cannot afford to have more than a 1 SQLiteDatabase object pointing to a single database.
The object definitely can be created but the inserts/updates fail if different threads/processes (too) start using different SQLiteDatabase objects (like how we use in JDBC Connection).
The only solution here is to stick with 1 SQLiteDatabase objects and whenever a startTransaction() is used in more than 1 thread, Android manages the locking across different threads and allows only 1 thread at a time to have exclusive update access.
Also you can do "Reads" from the database and use the same SQLiteDatabase object in a different thread (while another thread writes) and there would never be database corruption i.e "read thread" wouldn't read the data from the database till the "write thread" commits the data although both use the same SQLiteDatabase object.
This is different from how connection object is in JDBC where if you pass around (use the same) the connection object between read and write threads then we would likely be printing uncommitted data too.
In my enterprise application, I try to use conditional checks so that the UI Thread never have to wait, while the BG thread holds the SQLiteDatabase object (exclusively). I try to predict UI Actions and defer BG thread from running for 'x' seconds. Also one can maintain PriorityQueue to manage handing out SQLiteDatabase Connection objects so that the UI Thread gets it first.
Having had some issues, I think I have understood why I have been going wrong.
I had written a database wrapper class which included a close() which called the helper close as a mirror of open() which called getWriteableDatabase and then have migrated to a ContentProvider. The model for ContentProvider does not use SQLiteDatabase.close() which I think is a big clue as the code does use getWriteableDatabase In some instances I was still doing direct access (screen validation queries in the main so I migrated to a getWriteableDatabase/rawQuery model.
I use a singleton and there is the slightly ominous comment in the close documentation
Close any open database object
(my bolding).
So I have had intermittent crashes where I use background threads to access the database and they run at the same time as foreground.
So I think close() forces the database to close regardless of any other threads holding references - so close() itself is not simply undoing the matching getWriteableDatabase but force closing any open requests. Most of the time this is not a problem as the code is single threading, but in multi-threaded cases there is always the chance of opening and closing out of sync.
Having read comments elsewhere that explains that the SqLiteDatabaseHelper code instance counts, then the only time you want a close is where you want the situation where you want to do a backup copy, and you want to force all connections to be closed and force SqLite to write away any cached stuff that might be loitering about - in other words stop all application database activity, close just in case the Helper has lost track, do any file level activity (backup/restore) then start all over again.
Although it sounds like a good idea to try and close in a controlled fashion, the reality is that Android reserves the right to trash your VM so any closing is reducing the risk of cached updates not being written, but it cannot be guaranteed if the device is stressed, and if you have correctly freed your cursors and references to databases (which should not be static members) then the helper will have closed the database anyway.
So my take is that the approach is:
Use getWriteableDatabase to open from a singleton wrapper. (I used a derived application class to provide the application context from a static to resolve the need for a context).
Never directly call close.
Never store the resultant database in any object that does not have an obvious scope and rely on reference counting to trigger an implicit close().
If doing file level handling, bring all database activity to a halt and then call close just in case there is a runaway thread on the assumption that you write proper transactions so the runaway thread will fail and the closed database will at least have proper transactions rather than potentially a file level copy of a partial transaction.
I know that the response is late, but the best way to execute sqlite queries in android is through a custom content provider. In that way the UI is decoupled with the database class(the class that extends the SQLiteOpenHelper class). Also the queries are executed in a background thread(Cursor Loader).

Categories

Resources