0

I have been trying to solve a problem regarding the shared use of an SQLiteDatabase by my application and the application's service; that is, a service started by the application. The service wakes up every 30 seconds and checks the database, then, as all good processes should, it closes the connection to the database when it's finished. However, when the application is running concurrently and attempts to access the database, the application crashes. The logcat presents a message that warns me that I am trying to reopen a database connection that has already been closed, like the following:

05-16 16:48:30.796: E/AndroidRuntime(10610): java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase: /data/data/com.example.myapp/databases/eRing

A diligent troubleshooting effort showed that if I removed the closure of the database by the service that all would be well, sorta... If I let the service cycle long enough. Eventually, SQLiteConnectionPool would wake up and say,

A SQLiteConnection object for database '+data+data+com_example_myapp+databases+eRing' was leaked! Please fix your application to end transactions in progress properly and to close the database when it is no longer needed.

So, I have gone back to the drawing board, and reviewed the documentation and tutorials. I noticed that many of the available resources can hardly mention SQLiteDatabases without discussing ContentProviders. However, the documentation says that you only need a ContentProvider if you intend on accessing the database from another application. Here is a quote from the Android Developer's website:

Before You Start Building

Before you start building a provider, do the following:

  1. Decide if you need a content provider. You need to build a content provider if you want to provide one or more of the following features:

    • You want to offer complex data or files to other applications.
    • You want to allow users to copy complex data from your app into other apps.
    • You want to provide custom search suggestions using the search framework.

    You don't need a provider to use an SQLite database if the use is entirely within your own application.

What I can't tell from this documentation is the following: For the situation where the App owns the Service, and both the Activity and the Service need to access the database concurrently, does this warrant creating a ContentProvider? I am positive that I have no intent on sharing this database with anything else, or any of the other scenarios the documentation lists. Put another way, does the Service and the Activity count as two "Apps"? The Android fundamentals calls an activity and a service "App components" but not individual "Apps".

Please tell me if putting in all the effort to create a custom ContentProvider, and ripping out the direct access (via the SQLiteDBAdapters/Helpers) to the database is worth it.

For Salem: The following is the close method called by the Activity and Service.

public class SettingsDbAdapter {

private static SQLiteDatabase mDb;
private DatabaseHelper mDbHelper;

public static class DatabaseHelper extends SQLiteOpenHelper {
   //...

    }

//...
public void close() {
        Log.v("close()", "Close settingsdbadapter called");
        mDbHelper.close();
    }

}

//The following is the Services Open method:

public SettingsDbAdapter openReadable() throws SQLException {
    mDbHelper = new DatabaseHelper(mCtx);
    mDb = mDbHelper.getReadableDatabase();

    return this;
}

//The following is the Activities Open Method:
public SettingsDbAdapter open() throws SQLException {
            mDb = mDbHelper.getWritableDatabase();
    if (!mDb.isWriteAheadLoggingEnabled()) {
        Log.v("SettingsDb","Trying to Enable Write Ahead Logging");
        mDb.enableWriteAheadLogging();
    }
    return this;
}
N8sBug
  • 342
  • 3
  • 9
  • Can you provide a snippet of the code where you open/close the connection? – Salem May 16 '14 at 21:35
  • @Salem see the revision above. – N8sBug May 16 '14 at 21:48
  • Try to replace `mDbHelper.close();` with `if (mDbHelper != null && mDbHelper.isOpen()) { mDbHelper.close(); }`. – Salem May 16 '14 at 21:58
  • possible duplicate of [attempt to re-open an already-closed object: SQLiteDatabase](http://stackoverflow.com/questions/12770092/attempt-to-re-open-an-already-closed-object-sqlitedatabase) – rds May 16 '14 at 22:03
  • @Salem, I assume you meant mDb.isOpen? It produces same result. – N8sBug May 16 '14 at 22:07
  • @rds I don't think this is a duplicate for the following reason: In the cited question, the user isn't aware that he is accessing multiple instances. In my case, I'm wondering if a ContentProvider will offer a solution to this issue. – N8sBug May 16 '14 at 22:34
  • How can the database change if it is fully controlled by the application? What does the service check? What is the point of the service in the first place? – rds May 16 '14 at 23:48
  • The service is only ever checking for system conditions of the phone relative to the data in the database as entered by the user via the Activity running the UI. The UI is where the user enters conditions he/she wishes for the service to check, then the service is either launched on BootUp or by the Activity (during first install). The service will then feed back data to the user, independent of the Activity/UI, if the conditions on the phone match conditions in the database. – N8sBug May 17 '14 at 00:34

1 Answers1

1

The content provider is only required when the database is accessed by another application because the later simply has no permission to open the database file.

In your case, the problem is that you have concurrent access on the SQLiteDatabase. If you wrote a service, your activity should use it as well.

Edit: My comment seemed confused, I removed it. I suggest the 4 following components:

  • a BroadcastReceiver that, after the phone has booted, registers an alarm (via AlarmManager), and closes
  • another broadcast receiver that receives the ticks from the alarm (the alarm is a service implemented by Android) and checks the data
  • an activity that shows/let edit the data

The two latter access the data via

  • a service that offers methods to read/persist business objects in the database
rds
  • 26,253
  • 19
  • 107
  • 134
  • You may have just blown my mind. You are telling me I need to rearchitect it so that the service passes the data to the activity? – N8sBug May 16 '14 at 22:07
  • If so, will this hold if the Service is started on bootup ( such as here: //http://stackoverflow.com/questions/2784441/trying-to-start-a-service-on-boot-on-android ) Can the activity still get to the Service? – N8sBug May 16 '14 at 22:10
  • 1
    Yes. The service could offer services to manipulate data and manages the underlying SQLiteDatabase connection. Depending on your preferences and the amount of data the activity requires, you may choose between a low-level service or an `IntentService`. – rds May 16 '14 at 22:13
  • Yep: Mind=Blown. I'll fiddle with these concepts. – N8sBug May 16 '14 at 22:19
  • I will attempt the proposed solution, since it does jive with the documentation, but would still like to know if a working solution could be achieved using a ContentProvider in lieu of a service to manage the database. – N8sBug May 17 '14 at 00:51
  • Of course a Content Provider will also work. Just replace my last sentence by "The two latter access the data via a content provider" – rds May 17 '14 at 15:03