├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
└── src
└── main
├── AndroidManifest.xml
├── java
└── com
│ └── feigdev
│ └── androidserialsql
│ ├── AccessDB.java
│ ├── DBPair.java
│ ├── DefineDB.java
│ ├── GenericDBHelper.java
│ ├── UpgradeRunnable.java
│ └── WriterTask.java
└── res
├── drawable-hdpi
└── ic_launcher.png
├── drawable-mdpi
└── ic_launcher.png
├── drawable-xhdpi
└── ic_launcher.png
└── values
└── strings.xml
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # built application files
3 | *.apk
4 | *.ap_
5 |
6 | # files for the dex VM
7 | *.dex
8 |
9 | # Java class files
10 | *.class
11 |
12 | # generated files
13 | bin/
14 | gen/
15 |
16 | # Local configuration file (sdk path, etc)
17 | local.properties
18 |
19 | # Eclipse project files
20 | .classpath
21 | .project
22 |
23 | # Proguard folder generated by Eclipse
24 | proguard/
25 |
26 | # Intellij project files
27 | *.iml
28 | *.ipr
29 | *.iws
30 | .idea/
31 |
32 | # Gradle files
33 | build/
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 E. John Feig
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AndroidSerialSQL
2 |
3 | This project intends to solve the problem of concurrent write attempts from different threads to an Android SQLite database.
4 |
5 | ## The Problem
6 |
7 | Here's a [blog post explaining the issue](http://touchlabblog.tumblr.com/post/24474398246/android-sqlite-locking), along with a choice quote:
8 |
9 | > 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.
10 |
11 | This goes farther than the singleton pattern of only getting one database object (or one writable database object) and implements a blocking queue with a thread pool executor, where the thread pool has a max size of one. This means that there will be a single thread that handles database write operations, and it will work through the backlog of requests that exists in the queue.
12 |
13 | In order to accomplish this, we cut off access to a writable version of the database outside of a couple abstract runnables, which are intended to be added to the queue. `WriterTask` and `UpgradeRunnable` are those specialized runnables, both of them hold a reference to a database, and have ways of grabbing a reference to the writable db. There are a couple of data structures dedicated to handling the database (or databases), and these special runnables.
14 |
15 | ## Using this lib
16 |
17 | This is a standard Android library project, so if you're using Eclipse, or are familiar with using Android library projects, just do what you normally do. I should probably turn this into a jar at some point, but I'm lazy, and may not get around to it. Plus, if I did that, I'd want to make sure that it was polished enough to submit to Maven Central and all that jazz, but that's not where things are right now.
18 |
19 | **Use at your own risk!** Right now, this is more of a good starting point, and something to look at as a reference. Don't pull it into your project unless you plan on forking it and fixing problems as they appear.
20 |
21 | If you're using Gradle, you can do the following in the parent directory, or wherever you want to put your libraries:
22 |
23 | git submodule add git@github.com:emil10001/AndroidSerialSQL.git
24 |
25 | In your project's `settings.gradle`:
26 |
27 | include ':YourApp', ':AndroidSerialSQL'
28 |
29 | In your app's `build.gradle`:
30 |
31 | dependencies {
32 | compile project(':AndroidSerialSQL')
33 | }
34 |
35 | ## Usage
36 |
37 | There are a few steps to get this up and running. It should all be fairly straght-forward.
38 |
39 | ### 1
40 |
41 | Create a defenition of your database.
42 |
43 | DefineDB myDB = new DefineDB("myDB", 1);
44 | myDB.setTableDefenition("items",
45 | "create table items "
46 | + "( _id integer primary key autoincrement, "
47 | + "item text);");
48 |
49 | ### 2
50 |
51 | Use the defenition to open/create the database, and store it in a data structure for use.
52 |
53 | AccessDB.addDB(context, myDB);
54 |
55 | ### 3
56 |
57 | Insert an item into your database.
58 |
59 | AccessDB.addWriteTask(new WriterTask("myDB", callback) {
60 | @Override
61 | public void run() {
62 | db.beginTransaction();
63 | try {
64 | ContentValues values = new ContentValues();
65 | values.put("item", "five");
66 | db.insert(ITEMS, null, values);
67 | db.setTransactionSuccessful();
68 | } catch (Exception ex) {
69 | Log.e(TAG, "failed to insert", ex);
70 | } finally {
71 | db.endTransaction();
72 | }
73 | callback.run();
74 | }
75 | });
76 |
77 | ### 4
78 |
79 | Retrieve things from the database.
80 |
81 | AccessDB.getReadableDB("myDB").query("items", null,
82 | null, null, null, null, null);
83 |
84 | ### 5
85 |
86 | Handle upgrades by adding to the database defenition.
87 |
88 | myDB.setVersionUpgrade(2, new UpgradeRunnable() {
89 | @Override
90 | public void run() {
91 | db.execSQL("create table two"
92 | + "( _id integer primary key autoincrement, "
93 | + "different_thing text);");
94 | }
95 | });
96 |
97 | ## Sample implementation
98 |
99 | Check out the [sample branch](https://github.com/emil10001/AndroidSerialSQL/tree/sample) for a working app that implements this library.
100 |
101 | By E John Feig
102 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | jcenter()
4 | }
5 | dependencies {
6 | classpath 'com.android.tools.build:gradle:1.1.1'
7 | }
8 | }
9 | apply plugin: 'android-library'
10 |
11 | repositories {
12 | mavenCentral()
13 | }
14 |
15 | android {
16 | compileSdkVersion 22
17 | buildToolsVersion "21.1.1"
18 |
19 | defaultConfig {
20 | minSdkVersion 7
21 | targetSdkVersion 22
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/main/java/com/feigdev/androidserialsql/AccessDB.java:
--------------------------------------------------------------------------------
1 | package com.feigdev.androidserialsql;
2 |
3 | import android.content.Context;
4 | import android.database.sqlite.SQLiteDatabase;
5 |
6 | import java.util.concurrent.ConcurrentHashMap;
7 | import java.util.concurrent.ExecutorService;
8 | import java.util.concurrent.LinkedBlockingQueue;
9 | import java.util.concurrent.ThreadPoolExecutor;
10 | import java.util.concurrent.TimeUnit;
11 |
12 | /**
13 | * Created by ejohn on 9/2/13.
14 | */
15 | public class AccessDB {
16 | private static final LinkedBlockingQueue writerQueue = new LinkedBlockingQueue();
17 | protected static final ConcurrentHashMap databases = new ConcurrentHashMap();
18 | private static final ExecutorService executorService = new ThreadPoolExecutor(1, 1, 30, TimeUnit.SECONDS, writerQueue, new ThreadPoolExecutor.CallerRunsPolicy());
19 |
20 | public static void addDB(Context context, DefineDB myDB) {
21 | GenericDBHelper dbHelper = new GenericDBHelper(context.getApplicationContext(), myDB);
22 | String dbName = myDB.getDbName();
23 | SQLiteDatabase dbWrite = dbHelper.getWritableDatabase();
24 | SQLiteDatabase dbRead = dbHelper.getReadableDatabase();
25 | databases.put(dbName, new DBPair(dbRead, dbWrite));
26 | }
27 |
28 | public static SQLiteDatabase getReadableDB(String dbName){
29 | return databases.get(dbName).getReader();
30 | }
31 |
32 | public static void addWriteTask(WriterTask task) {
33 | executorService.execute(task);
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/feigdev/androidserialsql/DBPair.java:
--------------------------------------------------------------------------------
1 | package com.feigdev.androidserialsql;
2 |
3 | import android.database.sqlite.SQLiteDatabase;
4 |
5 | /**
6 | * Created by ejohn on 9/2/13.
7 | */
8 | public class DBPair {
9 | private final SQLiteDatabase dbRead, dbWrite;
10 |
11 | DBPair(SQLiteDatabase read, SQLiteDatabase write){
12 | dbRead = read;
13 | dbWrite = write;
14 | }
15 |
16 | SQLiteDatabase getReader(){
17 | return dbRead;
18 | }
19 |
20 | SQLiteDatabase getWriter(){
21 | return dbWrite;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/feigdev/androidserialsql/DefineDB.java:
--------------------------------------------------------------------------------
1 | package com.feigdev.androidserialsql;
2 |
3 | import android.util.SparseArray;
4 |
5 | import java.util.HashMap;
6 | import java.util.Set;
7 |
8 | /**
9 | * Created by ejohn on 9/2/13.
10 | */
11 | public class DefineDB {
12 | private final int version;
13 | private final String dbName;
14 | private final HashMap tableDefinitions = new HashMap();
15 | private final SparseArray versionUpgrades = new SparseArray();
16 |
17 | public DefineDB(String dbName, int version) {
18 | this.dbName = dbName;
19 | this.version = version;
20 | }
21 |
22 | public int getVersion() {
23 | return version;
24 | }
25 |
26 | public String getDbName() {
27 | return dbName;
28 | }
29 |
30 | public void setTableDefenition(String table, String tableDefenition) {
31 | tableDefinitions.put(table, tableDefenition);
32 | }
33 |
34 | public String getTableDefenition(String table) {
35 | return tableDefinitions.get(table);
36 | }
37 |
38 | public Set getTables() {
39 | return tableDefinitions.keySet();
40 | }
41 |
42 | public void setVersionUpgrade(int version, UpgradeRunnable upgrade) {
43 | versionUpgrades.put(version, upgrade);
44 | }
45 |
46 | public UpgradeRunnable getVersionUpgrade(int version) {
47 | return versionUpgrades.get(version);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/feigdev/androidserialsql/GenericDBHelper.java:
--------------------------------------------------------------------------------
1 | package com.feigdev.androidserialsql;
2 |
3 | import android.content.Context;
4 | import android.database.sqlite.SQLiteDatabase;
5 | import android.database.sqlite.SQLiteOpenHelper;
6 | import android.util.Log;
7 |
8 | import java.util.Set;
9 |
10 | /**
11 | * Created by ejohn on 9/2/13.
12 | */
13 | public class GenericDBHelper extends SQLiteOpenHelper {
14 | private static final String TAG = "GenericDBHelper";
15 | private final DefineDB myDB;
16 |
17 | public GenericDBHelper(Context context, DefineDB myDB) {
18 | super(context, myDB.getDbName(), null, myDB.getVersion());
19 | this.myDB = myDB;
20 | }
21 |
22 | @Override
23 | public void onCreate(SQLiteDatabase db) {
24 | Set tables = myDB.getTables();
25 |
26 | for (String table : tables) {
27 | Log.d(TAG, "exec " + table + ": " + myDB.getTableDefenition(table));
28 | db.execSQL(myDB.getTableDefenition(table));
29 | }
30 | }
31 |
32 | @Override
33 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
34 | while (oldVersion < newVersion) {
35 | oldVersion++;
36 | UpgradeRunnable r = myDB.getVersionUpgrade(oldVersion);
37 |
38 | if (null == r)
39 | continue;
40 |
41 | r.setDB(db);
42 | r.run();
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/com/feigdev/androidserialsql/UpgradeRunnable.java:
--------------------------------------------------------------------------------
1 | package com.feigdev.androidserialsql;
2 |
3 | import android.database.sqlite.SQLiteDatabase;
4 |
5 | /**
6 | * Created by ejohn on 9/2/13.
7 | */
8 | public abstract class UpgradeRunnable implements Runnable {
9 | public SQLiteDatabase db;
10 |
11 | public void setDB(SQLiteDatabase db) {
12 | this.db = db;
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/feigdev/androidserialsql/WriterTask.java:
--------------------------------------------------------------------------------
1 | package com.feigdev.androidserialsql;
2 |
3 | import android.database.sqlite.SQLiteDatabase;
4 |
5 | /**
6 | * Created by ejohn on 9/2/13.
7 | */
8 | public abstract class WriterTask implements Runnable {
9 | public SQLiteDatabase db;
10 | public Runnable callback;
11 |
12 | public WriterTask(String dbName, Runnable callback) {
13 | this.db = AccessDB.databases.get(dbName).getWriter();
14 | this.callback = callback;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emil10001/AndroidSerialSQL/da5af8e3520ffddb028445d3006a98635d87b619/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emil10001/AndroidSerialSQL/da5af8e3520ffddb028445d3006a98635d87b619/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emil10001/AndroidSerialSQL/da5af8e3520ffddb028445d3006a98635d87b619/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | AndroidSerialSQL
3 |
4 |
--------------------------------------------------------------------------------