Here is my problem. My app starts several threads, each for a particular object to be updated. The update of the object happens with a query to a single database. There is a single database and a single OpenHelper. The behavior of my app suggests me that the calls to the database are non simultaneous as well as I would like. How can I access the same database from different threads simultaneously? If the data for each object are in different tables is more efficient to split the database in several databases, one for each object?
public class SomethingToBeUpdated implements Runnable {
private SQLiteDatabase db;
#Override
public void run() {
db.rawQuery( ... bla bla
}
}
public class MainActivity extends Activity {
private SomethingToBeUpdated[] list = bla bla...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
for( SomethingToBeUpdated x : list ) {
new Thread(x).start();
}
}
}
For the sake of accessing the database in various threads you need to have a Database manager which keeps an object of your database class and pass it to any thread that needs it. In android you cannot access database simultaneously in several threads with different objects. It may just block your UI (the problem i was facing a few days ago).
So to overcome this problem you can use the the database manager i used which is defined as follows:
public class DatabaseManager {
private AtomicInteger mOpenCounter = new AtomicInteger();
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() {
if(mOpenCounter.incrementAndGet() == 1) {
mDatabase = mDatabaseHelper.getWritableDatabase();
}
return mDatabase;
}
public synchronized void closeDatabase() {
if(mOpenCounter.decrementAndGet() == 0) {
mDatabase.close();
}
}
}
Then you initialize it once like this:
DatabaseManager.initializeInstance(new ChatSQL(c));
And then you can get the database object wherever you want with this syntax:
SQLiteDatabase db = DatabaseManager.getInstance().openDatabase(); //in your methods which are querying the database
With this method your database is now thread safe. Hope this helps.
If the data for each object are in different tables is more efficient to split the database in several databases, one for each object?
No it is not efficient. It has a lot of overheads to define, access, make object and query different databases. And what if you want to join tables? you just cannot.
Related
I read that SQLiteDatabase uses reference counting to manage two threads querying the same database at the same time, but it isn't working for me.
Both my threads get the SQLiteDatabase object by calling getWriteableDatabase() on the same instance of SQLiteOpenHelper.
Both threads run their queries simultaneously with SQLiteDatabase.query().
I'm not using transactions.
At the end of each query, the respective thread calls SQLiteOpenHelper.close()
After the first thread closes the DB, the second throws this exception when it calls SQLiteDatabase.query():
java.lang.IllegalStateException: attempt to re-open an already-closed
object: SQLiteDatabase:
/data/user/0/com.comet.android.TypeSmart/databases/en.dic
I also tried calling SQLiteDatabase.close() instead of SQLiteOpenHelper.close() but it didn't help. :(
Isn't reference counting supposed to prevent this?
As to what nfrolov said:
Android already implements the reference counting internally. Basically, whenever you do anything with SQLiteDatabase, it increments a counter, and when you’re done, it decrements it. When all operations are done, DB just closes itself. I saw absolutely no reason to second-guess this system. Just don’t close the DB. You have to close cursors though.
reference: https://nfrolov.wordpress.com/2014/08/16/android-sqlitedatabase-locking-and-multi-threading/
But not closing the database might result into:
Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed
There is a solution where you will implement an AtomicInteger in which you will count how many times you've open the database and then decrement it when you are about to close, when it reaches 0, that's the time you're going to close it.
public class DatabaseManager {
private AtomicInteger mOpenCounter = new AtomicInteger();
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() {
if(mOpenCounter.incrementAndGet() == 1) {
// Opening new database
mDatabase = mDatabaseHelper.getWritableDatabase();
}
return mDatabase;
}
public synchronized void closeDatabase() {
if(mOpenCounter.decrementAndGet() == 0) {
// Closing database
mDatabase.close();
}
}
}
According to Dmytro Danylyk:
Every time you need database you should call openDatabase() method of DatabaseManager class. Inside this method we have a counter, which indicates how many times database is opened. If it equals to one, it means we need to create new database. If not, database 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.
Full example reference: https://blog.lemberg.co.uk/concurrent-database-access
I created a SQlite DB. It is no problem to write or read from my DB. In my DBHandler.class I added some methods to read one column for example. Every method has dbHandler.close() line. If I want to call a method from the DBHandler.class, so I have to make something like:
... DBHandler db = new DBHandler(this); ... list = db.getAllBlock()
Shoud I put the line
db.close()
after
list...
?????
That is my problem to understand.... Thank you!
It's inefficient to constantly have to reconnect to the database everytime you want to do something, so remove the close() statements from each line and create a single instance of the DB Handler class:
It's much better to have a single DBHandler
DBHandler db;
public Activity/Class Constructor()
{
DBHandler db = new DBHandler(this);
}
private void DoSomethingWithDatabase()
{
db.doSomethingHere();
}
private void DoSomethingWithDatabaseAgain()
{
db.doSomethingThere();
}
public void Activity/ClassClose()
{
db.CloseDatabase();
}
instance inside which ever Activity/Class is using it, then have a close method in DBHandler, which closes the connection to the database after the activity/class is finished using it.
public class DBHandler
{
private static SQLiteDatabase m_DB;
public DBHandler open() throws android.database.SQLException
{
m_DB = m_DBHelper.getWritableDatabase();
}
public void doSomethingHere()
{
m_DB.getAllBlock()
}
public void doSomethingThere()
{
m_DB.getAllSomethingElse()
}
public void CloseDatabase()
{
m_DB.close();
}
}
In fact, it's even better to have the DBHandler as a Singleton then you can have app wide database access without the inefficient overheads of re-establishing connections every time you want it.
I just need some validation that this is a workable way to share a SQLlite database connection across my Activities and Services of my app, by using a helper class:
public class DatabaseHelper
{
private static DBItems dbItems;
private static SQLiteDatabase sdbItemsRead, sbItemsWrite;
public static synchronized DBItems getHelper(Context context)
{
if (dbItems == null)
dbItems = new DBItems(context);
return dbItems;
}
public static synchronized SQLiteDatabase select(Context context)
{
if (sdbItemsRead == null)
sdbItemsRead = getHelper(context).getReadableDatabase();
return sdbItemsRead;
}
public static synchronized SQLiteDatabase write(Context context)
{
if (sbItemsWrite == null)
sbItemsWrite = getHelper(context).getWritableDatabase();
return sbItemsWrite;
}
}
In my code I'm calling this to select data:
DatabaseHelper.select(this).rawQuery("SELECT * FROM TABLE");
and this to write data:
DatabaseHelper.write(this).update("tbl_items", cv, "[id] = ?", new String[] { itemId.toString() });
I need to do this because I'm calling the database from different threads (UI and background) and I was getting "database is locked" exceptions thrown.
If you are creating database and calling in some activities then it will work fine, but if you want that your database should run along with service then it is always better to write the database in custom Content Provider.
Custom Content Provider will help you to run your database in background along with service.
you can refer the custom content provider from this link.
I am using a DBManager layer, it holds as private member all all the SQLiteOpenHelpers for the tables. The class is as followed
public class DBManager
{
private static final String mTAG = "DBManager";
Context mContext = null;
DB1 mDB1 = null;
DB2 mDB2 = null;
public DBManager( Context context )
{
mContext = context;
mDB1 = new DB1( mContext );
mDB2 = new DB2( mContext );
}
#Override
protected void finalize() throws Throwable
{
Close();
super.finalize();
}
public void Close()
{
if( mDB1 != null ) mDB1.close();
if( mDB2 != null ) mDB2.close();
}
.... Public API towards the DB1/DB2....
}
The question is like this:
Currently I am using it in each activity I need the DB as a private member.
Maybe better to use it as singleton? Can I? If do - which context to pass?
Or any other way to use?
Thanks
Here's what I do. Keep one instance of SqliteOpenHelper per-database. Do not keep more than one Helper for a single database. This can cause issues with writes if more than one thread are trying to write at the same time.
Keep the Helper instance as a singleton across your application process. Multiple Activity and Service clients can access it easily and you won't have write lock issues.
See here for details:
http://www.touchlab.co/blog/single-sqlite-connection/
It links to a few blog posts of mine about Sqlite and multiple connections.
I wanna make SQLiteOpenHelper with some additional methods (like getStreetsCursor) which returns data from my db. So i wrote something like this:
public class DBHelper extends SQLiteOpenHelper {
public static final String DB_NAME="some.db";
public static final String T1_NAME="streets";
public static final String T1_FNAME1="name";
public static final String T2_NAME="addresses";
public static final String T2_FNAME1="name";
public static final String T2_FNAME2="address";
private Context appContext;
public DBHelper(Context context) {
super(context, DB_NAME, null, 1);
appContext=context;
}
public Cursor getStreetsCursor(String chars) {
SQLiteDatabase dbReadable=this.getReadableDatabase();
Cursor curStreets = dbReadable.query(DBHelper.T1_NAME,
new String[] {"_id",DBHelper.T1_FNAME1},
DBHelper.T1_FNAME1+" LIKE(\""+chars.toUpperCase()+"%\")",
null, null, null, DBHelper.T1_FNAME1);
return curStreets;
}
There are several methods like getStreetsCursor (getAddresses, getAddress4 etc) defined in DBHelper.
I guess if it is a DB Helper it definitely should have such methods i mean the DBHelper is a logical placeholder for them.
What i do in the activity is create a new DBHelper instance and store it in private field (called mDBHelper) of activity. Additionally in onDestroy method of activity i have mDBHelper.close().
private DBHelper mDBHelper;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDBHelper = new DBHelper(this);
...
#Override
protected void onDestroy() {
if (mDBHelper!=null){
mDBHelper.close();
Log.i(APP_TAG,"mDBHelper.close() in "+this.getClass());
}
super.onDestroy();
}
Those activities uses the mDBHelper only in one way - by calling it's custom methods like mDBHelper.getStreetsCursor().
Eventually I found an exception message in logcat about my app got leaks in such activities which uses DBHelper. It says something like "database was never closed". So i decided to add a call to close() method in each of my custom methods just before return. So it looks like:
public Cursor getStreetsCursor(String chars) {
SQLiteDatabase dbReadable=this.getReadableDatabase();
Cursor curStreets = dbReadable.query(DBHelper.T1_NAME,
new String[] {"_id",DBHelper.T1_FNAME1},
DBHelper.T1_FNAME1+" LIKE(\""+chars.toUpperCase()+"%\")",
null, null, null, DBHelper.T1_FNAME1);
dbReadable.close();
return curStreets;
}
Now i got no leaks but got the next problem - only first call to mDBHelper.getStreetsCursor() really executes. All of the next calls returns null. Thats due to dbReadable.close(); line. If i remove it everythig works fine but i got leaks again.
So i cant figure out whats going wrong. In every custom method i got SQLiteDatabase dbReadable=this.getReadableDatabase(); line which should return a readable instance but after executing the close() method it doesn't.
I guess its about my custom methods because they calls .getReadableDatabase() inside an instance of DBHelper. If i place those methods directly in activity everything works fine - no leak exceptions and every time methods returns a proper data. But i want to place those methods in my DBHelper class.
So the main question is - what's wrong and how to do that properly?
You shouldn't have a SQLiteDatabase dbReadable=this.getReadableDatabase();
in every method getSomethingCursor as requesting a database object is expensive ( I think I read it in SO).
So you can create the object from your constructor
SQLiteDatabase dbReadable;
public DBHelper(Context context) {
super(context, DB_NAME, null, 1);
appContext=context;
dbReadable=this.getReadableDatabase()
}
public Cursor getStreetsCursor(String chars) {
Cursor curStreets = dbReadable.query(DBHelper.T1_NAME,
new String[] {"_id",DBHelper.T1_FNAME1},
DBHelper.T1_FNAME1+" LIKE(\""+chars.toUpperCase()+"%\")",
null, null, null, DBHelper.T1_FNAME1);
return curStreets;
}
Create a method to close the database handle:
public closeDb() {
if (dbReadable != null) { dbReadable.close();}
}
And in you activity:
#Override
protected void onDestroy() {
if (mDBHelper!=null){
mDBHelper.closeDb();
Log.i(APP_TAG,"mDBHelper.close() in "+this.getClass());
}
super.onDestroy();
}
And use startManagingCursor (if SDK < HoneyComb) to let your activity manage your cursor (closing it on onDestroy for example
Make SQLiteDatabase dbReadable a class member, and implement
public void close()
{
dbReadable.close();
}
Then, when you call mDBHelper.close(); in your activity's onDestroy() function, it should be ok.
Make a new function
public void DBclose()
{
curstreets.close();
dbReadable.close();
}
Then you can call this in your activity in onDestroy() fucntion.
Years after... I just want to state that now I'll better use Realm.io DB engine and I do it now in my projects. As for my old question I also want to notice that using getReadableDatabase() is a bad idea since across devices fragmentation I met some curios sqlite bug - readable instance will be closed automatically right after a query execution which is unexpected behavior. So I recommend always use getWritableDatabase() method even for reading purpose. And finally I end up with Dmytro Danylyk solution - give it a try, Dmytro is Google Developer Expert:
public class DatabaseManager {
private AtomicInteger mOpenCounter = new AtomicInteger();
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() {
if(mOpenCounter.incrementAndGet() == 1) {
// Opening new database
mDatabase = mDatabaseHelper.getWritableDatabase();
}
return mDatabase;
}
public synchronized void closeDatabase() {
if(mOpenCounter.decrementAndGet() == 0) {
// Closing database
mDatabase.close();
}
}
}
And use it as follows.
SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way