2

In my content provider I create and maintain 3 SQLiteDatabase objects. They are created like this:

    private ContentProviderHelper helper;

    @Override
    public boolean onCreate() { // that's the ContentProvider onCreate()
       SQLiteDatabase dbLog = new DbLog(getContext()).getWritableDatabase();
       SQLiteDatabase dbSession = new DbSession(getContext()).getWritableDatabase();
       SQLiteDatabase dbLocation = new DbLocation(getContext()).getWritableDatabase();

       helper = new ContentProviderHelper(UriManager.getAuthority(getContext()));
       helper.addDb(dbLog, DbLog.TABLE_NAME, UriManager.LOG, SQLiteDatabase.CONFLICT_REPLACE);
       helper.addDb(dbSession, DbSession.TABLE_NAME, UriManager.SESSION, SQLiteDatabase.CONFLICT_REPLACE);
       helper.addDb(dbLocation, DbLocation.TABLE_NAME, UriManager.LOCATION, SQLiteDatabase.CONFLICT_REPLACE);

the ContentProviderHelper stores those SQLiteDatabase in an ArrayList indexed with the UriMatcher.

The <provider> is properly registered in the manifest and my app have SD-card permission.

It runs fine for most of our 500.000 user base, but every once in a while I get a SQLiteCantOpenDatabaseException: unable to open database file from the Google Play

the relevant stack track is:

Caused by: android.database.sqlite.SQLiteCantOpenDatabaseException: unable to open database file
at android.database.sqlite.SQLiteDatabase.dbopen(Native Method)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1013)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:986)
at android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:1051)
at android.app.ContextImpl.openOrCreateDatabase(ContextImpl.java:787)
at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:221)
at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:157)

most often than not those error reports come from generic brandless poor quality devices.

Any help on how to properly avoid those errors will be much appreciated.

edit:

a different SQL exceptions that I'm getting on the same code.

Caused by: android.database.sqlite.SQLiteDiskIOException: disk I/O error: COMMIT;
at android.database.sqlite.SQLiteDatabase.native_execSQL(Native Method)
at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java:1763)
at android.database.sqlite.SQLiteDatabase.endTransaction(SQLiteDatabase.java:583)
at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:137)
at ***.***.***.data.ContentManager.onCreate(ContentManager.java:26)

so is there any other way of handling this besides try{} catch(){} the hell out of it ?

edit:

Extra info regarding cursors usage:

In general cursors are kept opened for the minimum amount of time as possible. All 3 SQLiteDatabase have a Wrapper that do the actual Cursor calls, read the data from them and close them. There's only 1 instance where the cursor is passed back to the calling object, but it is an IntentService, again, single thread, single instance, single process (just like ContentProvider) that loops through the cursor and close it.

Thomas
  • 2,751
  • 5
  • 31
  • 52
Budius
  • 39,391
  • 16
  • 102
  • 144
  • as saw in other threads, maybe its related to the path of the DB (inside android file system). http://stackoverflow.com/questions/4651797/database-handling-stoped-working-on-android-2-2-1-desire-hd-1-72-405-3/4828540#4828540 Maybe the situation is someone who has moved the App to the SD and are accessing/using your App while the SD is unmounted (being used in Windows). Just a theory! Maybe try it :) – nsL Dec 02 '13 at 20:37

2 Answers2

2

This doesn't look like a programming fault on your (Java) side, neither of the Android SQLite wrapping classes.

The provided stacktraces and code doesn't provide much information to proof my guess, but I think this should be caused by the unreliable nature of those SDCards (you already mentioned that the observed traces usually come from low quality phones).

The exception itself is generated in the native sqlite code, and while I havn't looked up the C/C++ part of the JNI interface, this should come directly from the underlying sqlite3_open call. For non obvious reasons the actuall errorcode is not included in the thrown exception, so you are basically out of luck here to find the root cause.

As this comes directly from the native layer it's some kind of filesystem/hardware problem. The card may be broken, the cardsocket may be broken/dirty/whatever or anything in between could be messed up (most likely physically).

To give you a possible solution for a problem you can't fix: Don't use the (sometimes unreliable) SDCard. If your databases are small enough (less than a few MBs) you should be fine storing them on the internal flash. This has to be reliable storage as if thouse writes or queries fail the user has more serious problems anyway.

If this is not an option, you could write "most recent data" (I don't know which kind of data you store) internally and migrate this data reguallary on the SDCard. This way you could at least try to store it without flodding your code with try/catch (if you try this in a seperate thread which catches any exception at the top level), but it doesn't solve the read part of the problem.

Another, while a little ugly, thing I can imagine: Just let the exceptions fly right through and catch them in the Application class (you can overload it), push it to disk (internal, obviously) and rethrow it to crash your app. On the next start you could then lookup if you've been crashed by an SQLite Exception and present a msg to the user which states something like "Sorry, your SDCard seems to be broken as we couldn't use it. Consider buying a new one of order a better device". ACRA may help you with this, it does what I tried to describe pretty well the last time I used it (1 year ago).

Luminger
  • 2,144
  • 15
  • 22
  • 2
    Apparently at the end I'll `try{}catch{}` stuff. But that's a well discussed and well based answer. It's not lots of data, its part of our soon to be open-sourced analytics (usage/session logs) library and I want it as stable as possible, but the app can be moved to SDcard (which I assume it would also transfer the DB files), I'm not specifying any crazy location for the DB, just the standard that the OpenHelper is using. The ACRA seems like a pretty sweet thing to use (even if not for this specific problem) and I'll be exploring using it in the future. – Budius Dec 04 '13 at 11:34
0

What I suppose is that you have some problem with concurrency and you are trying to get DB object more than once. Moreover - having 3 different DB (probably) makes the application run 3 DB engines at a time (memory problems). There is no single and simple solution while the problem is located in other place than exception is thrown, but some steps that you can do: Put 3 databases into one file (if possible). Make a simple singleton to keep DB object in memory and avoid concurrent attempts to the stored file. Remember that SQLite is claimed as "thread safe" not as "multithread" so the IO error can be just a natural behavior to protect DB corruption. Make sure that cursors are closed as quick as possible (i.e. after query the object read the cursor and map data to some POJO objects.

Workaround - use try/catch while opening db, in case of exception try to do it after some time (1 second).

piotrpo
  • 12,398
  • 7
  • 42
  • 58
  • hi, thanks for the attempt but, we're using ContentProvider (as stated in the question). ContentProvider are by nature singleton objects running in a single process and single thread connected to the main app by some IPC. The whole reason to put them into ContentProvider was to eliminate concurrency. There're 3 DBs because they do hold 3 completely independent data, and I don't believe Android would be such low efficiency as run 3 separate instances of DB engines. And I have DbWrapper classes that do exactly that, run the query, put in POJO, close cursor n return (I'll add that info). – Budius Dec 02 '13 at 12:11
  • Are you 100% sure that there is no concurrent actions on DB? Esp. if you try to insert some data into the table and select them from it at this same time? – piotrpo Dec 02 '13 at 15:07
  • well, the error is happening during `onCreate` of the content provider that as per docs: `This method is called for all registered content providers on the application main thread at application launch time`. I did Google a bit more and found out that it's called even before my Application.onCreate(). So yes, I'm 100% sure there's no concurrent actions on my DB. – Budius Dec 02 '13 at 17:28
  • There is still a possibility that your db file is locked by sqlite lib if your onCreate command is called frequently. – piotrpo Dec 03 '13 at 08:39