Reputation: 16238
I got two tables in my SQLite DB: entities
and user_actions
. Their approximate schemes:
The flow of the program is something like this (all DB accesses handled by ContentProvider):
entities
immediately. locally_modified
value of this entity is set to 1
user_actions
user_actions
are uploaded to the server one by one and removed from the DB in a background threadlocally_modified
flags in entities
At this point I encounter my atomicity issue: the synchronization with the server happens in a background thread, therefore the user can use the app and perform additional actions. As a consequence, right before I clear locally_modified
flag for an entity, I must check that there are no records in user_actions
corresponding to this entity. These three steps must be executed atomically for each entity having locally_modified
set to 1
:
user_actions
for entries corresponding to entity's _id
locally_modified
of that entity to 0
Given the above scenario, I have three questions:
Q1: Is there a way to lock SQLite DB accessed over ContentProvider in Android such that it can be accessed only by the locking thread?
Q2: If the answer to Q1 is positive, what happens if some other thread tries to access a locked DB? What precautions should I take to ensure reliable operation?
Q3: It is possible to execute atomic transactions with conditional logic using ContentProviderOperation? You can use "back-references" as described in this answer and this blog post to reference the result of a previous operations, but is there a way to use that result in some kind of if-else
statement?
Thanks
Upvotes: 1
Views: 722
Reputation: 16238
The answer turned out to be pretty simple, but it is kind of a "hack" - just add additional Uri
to ContentProvider
.
For example: initially my ContentProvider
supported the following URIs:
Uri.withAppendedPath(MyContract.CONTENT_URI, "entities")
Uri.withAppendedPath(MyContract.CONTENT_URI, "user_actions")
In order to support the atomic operation described in the question I added an additional Uri
:
Uri.withAppendedPath(MyContract.CONTENT_URI, "clear_modified_flag")
When this Uri
is updated through:
getContentResolver().update(
MyContract.ClearModifiedFlag.CONTENT_URI,
new ContentValues(),
null,
null);
my ContentProvider
executes an SQLite transaction
that locks the database for the duration of the operation and rolls it back in case of any errors (as described in this answer).
That's it.
P.S. my ContentProvider
is not exported (i.e. other apps can't access and use it), therefore it is safe to add this new Uri
to it. But keep in mind that if you do export your ContentProvider
, then exposing functionality like this one could be problematic.
Upvotes: 1
Reputation: 10699
Is there a way to lock SQLite DB in Android such that it can be accessed only by the locking thread?
Yes, have a look at SQLiteDatabase.beginTransaction()
(source). I believe you need SQLite's exclusive transactions, but you need to study that a bit more for your exact usage.
If the answer to Q1 is positive, what happens if some other thread tries to access a locked DB? What precautions should I take to ensure reliable operation?
There's an SQLite.amIInTransaction()
method that you could check, or just catch an SQLiteDatabaseLockedException
(more SQLite exceptions that you should look up)
It is possible to execute atomic transactions with conditional logic using ContentProviderOperation? You can use "back-references" as described in this answer and this blog post to reference the result of a previous operations, but is there a way to use that result in some kind of if-else statement?
Never done that, but it seems that overriding ContentProvider's applyBatch and wrapping it in a transaction should work:
Android: SQLite transactions when using ContentResolver
Upvotes: 1