40

Would it be OK to have a single instance of SQLiteOpenHelper as a member of a subclassed Application, and have all Activities that need an instance of SQLiteDatabase get it from the one helper?

Alex Lockwood
  • 81,274
  • 37
  • 197
  • 245
Julian A.
  • 9,850
  • 12
  • 57
  • 98

5 Answers5

48

Click here to see my blog post on this subject.


CommonsWare is right on (as usual). Expanding on his post, here is some sample code that illustrates three possible approaches. These will allow access to the database throughout the application.

Approach #1: subclassing `Application`

If you know your application won't be very complicated (i.e. if you know you'll only end up having one subclass of Application), then you can create a subclass of Application and have your main Activity extend it. This ensures that one instance of the database is running throughout the Application's entire life cycle.

public class MainApplication extends Application {

    /**
     * see NotePad tutorial for an example implementation of DataDbAdapter
     */
    private static DataDbAdapter mDbHelper;

    /**
     * Called when the application is starting, before any other 
     * application objects have been created. Implementations 
     * should be as quick as possible...
     */
    @Override
    public void onCreate() {
        super.onCreate();
        mDbHelper = new DataDbAdapter(this);
        mDbHelper.open();
    }

    public static DataDbAdapter getDatabaseHelper() {
        return mDbHelper;
    }
}

Approach #2: have `SQLiteOpenHelper` be a static data member

This isn't the complete implementation, but it should give you a good idea on how to go about designing the DatabaseHelper class correctly. The static factory method ensures that there exists only one DatabaseHelper instance at any time.

/**
 * create custom DatabaseHelper class that extends SQLiteOpenHelper
 */
public class DatabaseHelper extends SQLiteOpenHelper { 
    private static DatabaseHelper mInstance = null;

    private static final String DATABASE_NAME = "databaseName";
    private static final String DATABASE_TABLE = "tableName";
    private static final int DATABASE_VERSION = 1;

    private Context mCxt;

    public static DatabaseHelper getInstance(Context ctx) {
        /** 
         * use the application context as suggested by CommonsWare.
         * this will ensure that you dont accidentally leak an Activitys
         * context (see this article for more information: 
         * http://developer.android.com/resources/articles/avoiding-memory-leaks.html)
         */
        if (mInstance == null) {
            mInstance = new DatabaseHelper(ctx.getApplicationContext());
        }
        return mInstance;
    }

    /**
     * constructor should be private to prevent direct instantiation.
     * make call to static factory method "getInstance()" instead.
     */
    private DatabaseHelper(Context ctx) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        this.mCtx = ctx;
    }
}

Approach #3: abstract the SQLite database with a `ContentProvider`

This is the approach I would suggest. For one, the new LoaderManager class relies heavily on ContentProviders, so if you want an Activity or Fragment to implement LoaderManager.LoaderCallbacks<Cursor> (which I suggest you take advantage of, it is magical!), you'll need to implement a ContentProvider for your application. Further, you don't need to worry about making a Singleton database helper with ContentProviders. Simply call getContentResolver() from the Activity and the system will take care of everything for you (in other words, there is no need for designing a Singleton pattern to prevent multiple instances from being created).

Hope that helps!

Alex Lockwood
  • 81,274
  • 37
  • 197
  • 245
  • 2
    Just thought I'd point out that CommonsWare's LoaderEx library shows how to use the `LoaderManager.LoaderCallbacks` interface and `Loader`s when working directly with a SQLite database instead of a ContentProvider. http://github.com/commonsguy/cwac-loaderex – Julian A. Jan 18 '12 at 07:09
  • You need to call super.onCreate() from your override. Possibly the same for onTerminate() - not sure. – Tommy Herbert Feb 15 '12 at 15:47
  • oops, forgot about that... you need it for both methods. fixed! – Alex Lockwood Feb 15 '12 at 16:39
  • on second thought, there might not be a definite answer for `onTerminate()`, since the method might not even be called. including it seems safer though :) – Alex Lockwood Feb 15 '12 at 16:44
  • Why this assignment ` this.mCtx = ctx;`? – Mirko Oct 03 '12 at 12:47
  • @Mirko IDK... maybe I edited this at some point and forgot about that. Doesn't look like it is at all necessary. An updated [**blog post**](http://www.androiddesignpatterns.com/2012/05/correctly-managing-your-sqlite-database.html) on the topic can be found here :) – Alex Lockwood Oct 03 '12 at 13:36
  • Thanks Alex! I keep seeing that assignment in all the tutorial/articles about Android database adapters and I was wondering why...probably an OOP best practice?? ;) – Mirko Oct 04 '12 at 15:00
  • It's more because the adapters are subclasses of `SQLiteOpenHelper`, which is a helper class to manage database creation and version management. Most tutorials/articles/apps use it because it's easier than opening/closing/managing a raw `SQLiteDatabase` on your own. – Alex Lockwood Oct 04 '12 at 15:11
  • @AlexLockwood: For Approach #2. Do we have to close DatabaseHelper when application onTerminate? – Loc Jul 14 '14 at 19:28
  • @LocHa According to the [documentation](https://developer.android.com/reference/android/app/Application.html#onTerminate()), `onTerminate()` will never be called in production environments, so overriding `onTerminate()` will have no effect. – Alex Lockwood Jul 14 '14 at 23:08
  • @AlexLockwood In that case, approach #1 is not reliable since you have overridden `onTerminate()` there. – faizal Oct 09 '14 at 12:23
  • With the singleton, in Android Studio I get this: "*Do not place Android context classes in static fields; this is a memory leak (and also breaks Instant Run)*" for `private static DatabaseHelper mInstance`. Is there a way to avoid this issue? – goetzc Sep 28 '16 at 00:41
  • When implementing the second approach which is singleton pattern, the main activity being the first class to instantiate db helper, should the same context be retained for all activities? If not, how to pass each context without reinitializing everytime – Hari Kiran Oct 29 '19 at 17:37
41

Having a single SQLiteOpenHelper instance can help in threading cases. Since all threads would share the common SQLiteDatabase, synchronization of operations is provided.

However, I wouldn't make a subclass of Application. Just have a static data member that is your SQLiteOpenHelper. Both approaches give you something accessible from anywhere. However, there is only one subclass of Application, making it more difficult for you to use other subclasses of Application (e.g., GreenDroid requires one IIRC). Using a static data member avoids that. However, do use the Application Context when instantiating this static SQLiteOpenHelper (constructor parameter), so you do not leak some other Context.

And, in cases where you aren't dealing with multiple threads, you can avoid any possible memory leak issues by just using one SQLiteOpenHelper instance per component. However, in practice, you should be dealing with multiple threads (e.g., a Loader), so this recommendation is only relevant for trivial applications, such as those found in some books... :-)

CommonsWare
  • 910,778
  • 176
  • 2,215
  • 2,253
  • :) I actually used your LoaderEx classes and Advanced Android book to learn how to move database access off the UI thread. Thank you so much for both. They were a huge help. – Julian A. Jan 17 '12 at 01:37
  • 1
    So when should you even call "close", if all classes and threads will reach the same exact instance? – android developer Jan 17 '15 at 22:53
  • 1
    @androiddeveloper: Generally, you don't call `close()`, unless you happen to be in a circumstance where you're 100% sure it is safe to call `close()`. – CommonsWare Jan 17 '15 at 23:03
  • @CommonsWare So it's better to leave it open through the rest of the time? wouldn't it make the OS "want" to close the process more when the app is on the background this way? Especially because this class' real engine is written in C/C++ which puts the memory usage outside of the heap? – android developer Jan 18 '15 at 06:18
  • 1
    @androiddeveloper: "wouldn't it make the OS "want" to close the process more when the app is on the background this way?" -- not that I am aware of. "Especially because this class' real engine is written in C/C++ which puts the memory usage outside of the heap?" -- every process has SQLite in it, as it is in the zygote AFAIK. The RAM for an open database is not especially large AFAIK. – CommonsWare Jan 18 '15 at 12:27
  • @CommonsWare I see. Thank you. – android developer Jan 18 '15 at 13:44
  • @CommonsWare I have a question about it: what about "getWritableDatabase" or "getReadableDatabase" ? would it be a good thing to never close them, and use a single instance for each of them, to be used by various threads? Is it also thread-safe to do it? – android developer Jan 23 '15 at 09:39
  • @androiddeveloper: I do not hold onto the results of those methods, just calling them on a singleton `SQLiteOpenHelper` as needed. If you happen to be in a position to close the `SQLiteOpenHelper`, that will close the underlying `SQLiteDatabase`. `SQLiteOpenHelper` manages a single instance of `SQLiteDatabase`, and the thread safety logic is in `SQLiteDatabase`. – CommonsWare Jan 23 '15 at 12:30
  • @CommonsWare So you say it should be ok to have a single call to them. I think it's best to close them when not needed though. maybe it depends on how much you use the DB. Thank you again. – android developer Jan 23 '15 at 12:43
  • @androiddeveloper: "I think it's best to close them when not needed though" -- we discussed this previously. If you *know* a safe time to close them, great, and do so. Frequently, we do not know when a safe time is to close the database. – CommonsWare Jan 23 '15 at 13:01
  • @CommonsWare Before, I asked about closing "SQLiteOpenHelper" . Now I've asked about "SQLiteDatabase". Do you mean that this is relevant for both? – android developer Jan 23 '15 at 13:48
  • @androiddeveloper: Yes. If you are using a `SQLiteOpenHelper`, you will be using it to get at your `SQLiteDatabase`. Close the helper, and it closes the database. – CommonsWare Jan 23 '15 at 14:11
  • @CommonsWare I meant if the tip is relevant for both questions, but ok. Thank you. – android developer Jan 23 '15 at 21:29
  • @CommonsWare My application is using multiple threads to read database is there a performance hit if i use singleton instance to read database from two different threads than opening a new instance in different threads – user1530779 Jul 03 '15 at 13:59
  • @user1530779: You will run into many problems if you try "opening a new instance in different threads", unless you are doing your own thread synchronization, in which case I would not expect much performance difference. – CommonsWare Jul 03 '15 at 14:04
  • @CommonsWare Also if you could explain the difference between in terms of performance of using http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html#enableWriteAheadLogging%28%29 – user1530779 Jul 03 '15 at 14:06
  • @CommonsWare I understand the problems if im writing and reading at the same time but if im ONLY READING database from multiple threads with multiple instances there should not be a problem. Can you elaborate the problems please – user1530779 Jul 03 '15 at 14:11
  • @user1530779: "if im ONLY READING database from multiple threads with multiple instances there should not be a problem" -- unless the database is immutable (e.g., you are using `SQLiteAssetHelper` and have no code to update it), *something* will be writing to the database. After all, you don't need write-ahead logging if you are not writing. Beyond that, I have no idea what to expect with multiple threads reading from separate `SQLiteDatabase` instances backed by the same database file, as that is a niche scenario. You may wish to ask a separate Stack Overflow question on this. – CommonsWare Jul 03 '15 at 14:17
  • @CommonsWare Raising a separate question for that but does write-ahead logging have any cons other than extensive memory usage in terms of performance ? – user1530779 Jul 03 '15 at 14:20
  • @CommonsWare http://stackoverflow.com/questions/31209495/sqllite-performance-dip-android – user1530779 Jul 03 '15 at 14:48
  • @CommonsWare I certainly think using multiple instances in scenarios if you are reading database from Multiple threads would be faster because all the calls would not be serial and non synchronized while in single instance all calls to database would be synchronized and serial ie no parallel processing even in case of read So CLEARLY ITS A PERFORMANCE HIT IN SCENARIOS WHERE ONLY READING OF DATABASE IS HAPPENING IN MULTIPLE THREADS – user1530779 Jul 03 '15 at 15:06
  • @CommonsWare check out my answer below – user1530779 Jul 03 '15 at 16:34
  • @CommonsWare I'm late to this thread, but can you confirm it is safe to NEVER call SQLiteOpenHelper.close()? I would like a single instance for the application (created and opened for writing at Application.onCreate), but there is no Application.onDestroy I can deterministically call close in. Will the resource be properly cleaned up when/if application is killed by OS at some point? – Sean Sep 03 '15 at 21:40
  • @Sean: "can you confirm it is safe to NEVER call SQLiteOpenHelper.close()?" -- I have been suitably convinced at this point that it is safe to never call `close()`. SQLite is transactional; all buffers are flushed and stuff written to disk by the time a transaction ends. There are no unflushed buffers or something that would cause data loss. "Will the resource be properly cleaned up when/if application is killed by OS at some point?" -- well, the process goes away, if that is what you mean by "cleaned up". – CommonsWare Sep 03 '15 at 21:42
  • @CommonsWare Thanks. By "resource be properly cleaned up" I meant system (or db engine) resources (if any) allocated to support the db connection when I call getWritableDatabase(). – Sean Sep 03 '15 at 21:49
  • 1
    @Sean: No, the process just gets terminated. All your memory, threads, and so on go *poof* when that happens. – CommonsWare Sep 03 '15 at 21:50
  • @CommonsWare, I have a situation where I need to keep multiple databases (Different SQLiteOpenHelper instances) in open state. **In android's sqlite3, is there any limit that maximum number of different database SQLiteOpenHelpers in open state?** _Note: There is only one SQLiteOpenHelper instance per database._ – Uma Sankar Jul 06 '18 at 15:40
  • @UmaSankar: I do not know the answer, sorry. – CommonsWare Jul 06 '18 at 21:43
7

I have written MultiThreadSQLiteOpenHelper which is an enhanced SQLiteOpenHelper for Android applications where several threads might open and close the same sqlite database.

Instead of calling close method, threads ask for closing the database, preventing a thread from performing a query on a closed database.

If each thread asked for closing, then a close is actually performed. Each activity or thread (ui-thread and user-threads) performs an open call on database when resuming, and asks for closing the database when pausing or finishing.

Source code and samples available here: https://github.com/d4rxh4wx/MultiThreadSQLiteOpenHelper

d4rxh4wx
  • 81
  • 3
4

I did a lot of research on this topic and I agree with all the points mentioned by commonware . But i think there is an important point everyone is missing here , Answer to this question is entirely dependent on your Use Case so if your application is reading databases via multiple threads and only reading using Singleton has a huge performance hit as all the functions are synchronized and are executed serially as there is a single connection to database Open source is great, by the way. You can dig right into the code and see what’s going on. From that and some testing, I’ve learned the following are true:

Sqlite takes care of the file level locking.  Many threads can read, one can write.  The locks prevent more than one writing.
Android implements some java locking in SQLiteDatabase to help keep things straight.
If you go crazy and hammer the database from many threads, your database will (or should) not be corrupted.

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.

The first problem, real, distinct connections. The great thing about open source code is you can dig right in and see what’s going on. The SQLiteOpenHelper class does some funny things. Although there is a method to get a read-only database connection as well as a read-write connection, under the hood, its always the same connection. Assuming there are no file write errors, even the read-only connection is really the single, read-write connection. Pretty funny. So, if you use one helper instance in your app, even from multiple threads, you never really using multiple connections.

Also, the SQLiteDatabase class, of which each helper has only one instance, implements java level locking on itself. So, when you’re actually executing database operations, all other db operations will be locked out. So, even if you have multiple threads doing stuff, if you’re doing it to maximize database performance, I have some bad news for you. No benefit.

Interesting Observations

If you turn off one writing thread, so only one thread is writing to the db, but another reading, and both have their own connections, the read performance shoots WAY up and I don’t see any lock issues. That’s something to pursue. I have not tried that with write batching yet.

If you are going to perform more than one update of any kind, wrap it in a transaction. It seems like the 50 updates I do in the transaction take the same amount of time as the 1 update outside of the transaction. My guess is that outside of the transaction calls, each update attempts to write the db changes to disk. Inside the transaction, the writes are done in one block, and the overhead of writing dwarfs the update logic itself.

user1530779
  • 379
  • 3
  • 8
  • Great research. Thanks for sharing. – Julian A. Jul 03 '15 at 17:04
  • Thanks @Julian so the inference here is that if your database is being only Read Don't use SINGLETON but create different instances as that would allow concurrent access as a single instance would block all other operations when reading a database and if you need multiple readers and one writer use WAL feature with separate instances so SINGLETON should not be used according to me – user1530779 Jul 03 '15 at 17:18
1

Yes, that is the way you should go about it, having a helper class for the activities that need an instance of the Database.

coder_For_Life22
  • 24,973
  • 20
  • 82
  • 117
  • how to pass context as an argument to the constructor of database helper class that extends sqliteopenhelper? Since it varies for different activities, is there a base context that we can pass as an argument? – Hari Kiran Oct 29 '19 at 17:30