├── AndroidManifest.xml
├── README
├── build.properties
├── build.xml
├── dblocking.apk
├── dblocking.iml
├── dblocking.ipr
├── default.properties
├── gen
└── co
│ └── touchlab
│ └── R.java
├── keystorepassword.txt
├── proguard.cfg
├── res
├── drawable-hdpi
│ └── icon.png
├── drawable-ldpi
│ └── icon.png
├── drawable-mdpi
│ └── icon.png
├── layout
│ └── main.xml
└── values
│ └── strings.xml
└── src
└── co
└── touchlab
└── dblocking
├── DatabaseHelper.java
└── MyActivity.java
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | Example app showing how you can cause DB collisions pretty easily with multiple SQLiteHelper instances.
2 |
3 | Also demonstrate dramatic time difference using transactions.
--------------------------------------------------------------------------------
/build.properties:
--------------------------------------------------------------------------------
1 | # This file is used to override default values used by the Ant build system.
2 | #
3 | # This file must be checked in Version Control Systems, as it is
4 | # integral to the build system of your project.
5 |
6 | # This file is only used by the Ant script.
7 |
8 | # You can use this to override default values such as
9 | # 'source.dir' for the location of your java source folder and
10 | # 'out.dir' for the location of your output folder.
11 |
12 | # You can also use it define how the release builds are signed by declaring
13 | # the following properties:
14 | # 'key.store' for the location of your keystore and
15 | # 'key.alias' for the name of the key to use.
16 | # The password will be asked during the build when you use the 'release' target.
17 |
18 |
--------------------------------------------------------------------------------
/build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
27 |
28 |
29 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
42 |
54 |
55 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/dblocking.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kpgalligan/Android-Database-Locking-Collisions-Example/d5524854a23222a813dee80bc122f5bcd271edce/dblocking.apk
--------------------------------------------------------------------------------
/dblocking.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/dblocking.ipr:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | -
54 |
55 |
56 | -
57 |
58 |
59 | -
60 |
61 |
62 | -
63 |
64 |
65 | -
66 |
67 |
68 |
69 |
70 |
71 | -
72 |
73 |
74 |
75 |
76 |
77 | -
78 |
79 |
80 |
81 |
82 |
83 | -
84 |
85 |
86 |
87 |
88 |
89 | -
90 |
91 |
92 |
93 |
94 | -
95 |
96 |
97 |
98 |
99 | -
100 |
101 |
102 |
103 |
104 | -
105 |
106 |
107 |
108 |
109 | -
110 |
111 |
112 |
113 |
114 | -
115 |
116 |
117 |
118 |
119 | -
120 |
121 |
122 | -
123 |
124 |
125 |
126 |
127 | -
128 |
129 |
130 |
131 |
132 | -
133 |
134 |
135 |
136 |
137 | -
138 |
139 |
140 |
141 |
142 | -
143 |
144 |
145 |
146 |
147 | -
148 |
149 |
150 | -
151 |
152 |
153 | -
154 |
155 |
156 | -
157 |
158 |
159 | -
160 |
161 |
162 |
163 |
164 | -
165 |
166 |
167 | -
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 | http://www.w3.org/1999/xhtml
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 | 1.6
192 |
193 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
--------------------------------------------------------------------------------
/default.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system use,
7 | # "build.properties", and override values to adapt the script to your
8 | # project structure.
9 |
10 | # Project target.
11 | target=Google Inc.:Google APIs:10
12 |
--------------------------------------------------------------------------------
/gen/co/touchlab/R.java:
--------------------------------------------------------------------------------
1 | /* AUTO-GENERATED FILE. DO NOT MODIFY.
2 | *
3 | * This class was automatically generated by the
4 | * aapt tool from the resource data it found. It
5 | * should not be modified by hand.
6 | */
7 |
8 | package co.touchlab;
9 |
10 | public final class R {
11 | public static final class attr {
12 | }
13 | public static final class drawable {
14 | public static final int icon=0x7f020000;
15 | }
16 | public static final class id {
17 | public static final int manyHelpers=0x7f050001;
18 | public static final int noTrans=0x7f050004;
19 | public static final int numberOfInserts=0x7f050003;
20 | public static final int oneHelper=0x7f050000;
21 | public static final int results=0x7f050007;
22 | public static final int testTransactionIsolation=0x7f050002;
23 | public static final int timeOut=0x7f050006;
24 | public static final int trans=0x7f050005;
25 | }
26 | public static final class layout {
27 | public static final int main=0x7f030000;
28 | }
29 | public static final class string {
30 | public static final int app_name=0x7f040000;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/keystorepassword.txt:
--------------------------------------------------------------------------------
1 | flapjack
--------------------------------------------------------------------------------
/proguard.cfg:
--------------------------------------------------------------------------------
1 | -optimizationpasses 5
2 | -dontusemixedcaseclassnames
3 | -dontskipnonpubliclibraryclasses
4 | -dontpreverify
5 | -verbose
6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
7 |
8 | -keep public class * extends android.app.Activity
9 | -keep public class * extends android.app.Application
10 | -keep public class * extends android.app.Service
11 | -keep public class * extends android.content.BroadcastReceiver
12 | -keep public class * extends android.content.ContentProvider
13 | -keep public class * extends android.app.backup.BackupAgentHelper
14 | -keep public class * extends android.preference.Preference
15 | -keep public class com.android.vending.licensing.ILicensingService
16 |
17 | -keepclasseswithmembernames class * {
18 | native ;
19 | }
20 |
21 | -keepclasseswithmembers class * {
22 | public (android.content.Context, android.util.AttributeSet);
23 | }
24 |
25 | -keepclasseswithmembers class * {
26 | public (android.content.Context, android.util.AttributeSet, int);
27 | }
28 |
29 | -keepclassmembers class * extends android.app.Activity {
30 | public void *(android.view.View);
31 | }
32 |
33 | -keepclassmembers enum * {
34 | public static **[] values();
35 | public static ** valueOf(java.lang.String);
36 | }
37 |
38 | -keep class * implements android.os.Parcelable {
39 | public static final android.os.Parcelable$Creator *;
40 | }
41 |
--------------------------------------------------------------------------------
/res/drawable-hdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kpgalligan/Android-Database-Locking-Collisions-Example/d5524854a23222a813dee80bc122f5bcd271edce/res/drawable-hdpi/icon.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kpgalligan/Android-Database-Locking-Collisions-Example/d5524854a23222a813dee80bc122f5bcd271edce/res/drawable-ldpi/icon.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kpgalligan/Android-Database-Locking-Collisions-Example/d5524854a23222a813dee80bc122f5bcd271edce/res/drawable-mdpi/icon.png
--------------------------------------------------------------------------------
/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
12 |
17 |
22 |
23 |
28 |
29 |
33 |
37 |
43 |
49 |
50 |
51 |
55 |
56 |
57 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | dblocking
4 |
5 |
--------------------------------------------------------------------------------
/src/co/touchlab/dblocking/DatabaseHelper.java:
--------------------------------------------------------------------------------
1 | package co.touchlab.dblocking;
2 |
3 | import android.content.ContentValues;
4 | import android.content.Context;
5 | import android.database.Cursor;
6 | import android.database.sqlite.SQLiteDatabase;
7 | import android.database.sqlite.SQLiteOpenHelper;
8 |
9 | import java.util.*;
10 |
11 | /**
12 | * Created by IntelliJ IDEA.
13 | * User: kgalligan
14 | * Date: 10/9/11
15 | * Time: 6:46 PM
16 | * To change this template use File | Settings | File Templates.
17 | */
18 | public class DatabaseHelper extends SQLiteOpenHelper
19 | {
20 | private static final String DATABASE_NAME = "dblocking.db";
21 | private static final int DATABASE_VERSION = 1;
22 | private static DatabaseHelper helper;
23 | public static final String[] SESSION_COLS = new String[]
24 | {
25 | "id",
26 | "description"
27 | };
28 |
29 |
30 | public static synchronized DatabaseHelper getInstance(Context context)
31 | {
32 | if(helper == null)
33 | {
34 | helper = new DatabaseHelper(context);
35 | }
36 |
37 | return helper;
38 | }
39 |
40 | public DatabaseHelper(Context context)
41 | {
42 | super(context, DATABASE_NAME, null, DATABASE_VERSION);
43 | }
44 |
45 | @Override
46 | public void onCreate(SQLiteDatabase sqLiteDatabase)
47 | {
48 | String createSessionTable =
49 | "CREATE TABLE `session` " +
50 | "(" +
51 | "`id` INTEGER PRIMARY KEY AUTOINCREMENT, " +
52 | "`description` VARCHAR" +
53 | ") ";
54 | sqLiteDatabase.execSQL(createSessionTable);
55 | }
56 |
57 | @Override
58 | public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1)
59 | {
60 | sqLiteDatabase.execSQL("drop table session");
61 | onCreate(sqLiteDatabase);
62 | }
63 |
64 | public void createSession(String desc)
65 | {
66 | final SQLiteDatabase writableDatabase = getWritableDatabase();
67 | final ContentValues contentValues = new ContentValues();
68 |
69 | contentValues.put("description", desc);
70 |
71 | writableDatabase.insertOrThrow("session", null, contentValues);
72 | }
73 |
74 | public int countSessions()
75 | {
76 | Cursor cursor = getReadableDatabase().rawQuery("select count(*) from session", null);
77 | cursor.moveToFirst();
78 | return cursor.getInt(0);
79 | }
80 |
81 | public void updateSession(Integer id, String desc)
82 | {
83 | final SQLiteDatabase writableDatabase = getWritableDatabase();
84 | try
85 | {
86 | final ContentValues contentValues = new ContentValues();
87 |
88 | contentValues.put("description", desc);
89 |
90 | writableDatabase.update("session", contentValues, "id = ?", new String[]{id.toString()});
91 | }
92 | finally
93 | {
94 | }
95 | }
96 |
97 | public List loadAllSessions()
98 | {
99 | final SQLiteDatabase readableDatabase = getReadableDatabase();
100 | List sessions;
101 | final Cursor sessionCursor = readableDatabase.query("session", SESSION_COLS, null, null, null, null, "id", null);
102 |
103 | try
104 | {
105 | sessions = new ArrayList();
106 |
107 | while (sessionCursor.moveToNext())
108 | {
109 | final Session session = sessionFromCursor(sessionCursor);
110 |
111 | sessions.add(session);
112 | }
113 | }
114 | finally
115 | {
116 | sessionCursor.close();
117 | }
118 |
119 | return sessions;
120 | }
121 |
122 | public static class Session
123 | {
124 | private Integer id;
125 | private String description;
126 |
127 | public Session(Integer id, String description)
128 | {
129 | this.description = description;
130 | this.id = id;
131 | }
132 |
133 | public String getDescription()
134 | {
135 | return description;
136 | }
137 |
138 | public Integer getId()
139 | {
140 | return id;
141 | }
142 | }
143 |
144 | public Session loadSession(Integer id)
145 | {
146 | final SQLiteDatabase readableDatabase = getReadableDatabase();
147 |
148 | final Session session;
149 | final Cursor cursor = readableDatabase.query("session",
150 | SESSION_COLS,
151 | "id = ?",
152 | new String[]{id.toString()},
153 | null,
154 | null,
155 | null,
156 | null
157 | );
158 | try
159 | {
160 |
161 |
162 | cursor.moveToNext();
163 |
164 | session = sessionFromCursor(cursor);
165 | }
166 | finally
167 | {
168 | cursor.close();
169 | }
170 |
171 | return session;
172 | }
173 |
174 | private Session sessionFromCursor(Cursor cursor)
175 | {
176 | final Session session = new Session(cursor.getInt(0), cursor.getString(1));
177 | return session;
178 | }
179 |
180 |
181 | }
182 |
--------------------------------------------------------------------------------
/src/co/touchlab/dblocking/MyActivity.java:
--------------------------------------------------------------------------------
1 | package co.touchlab.dblocking;
2 |
3 | import android.app.Activity;
4 | import android.content.ContentValues;
5 | import android.database.sqlite.SQLiteDatabase;
6 | import android.os.AsyncTask;
7 | import android.os.Bundle;
8 | import android.os.Handler;
9 | import android.util.Log;
10 | import android.view.View;
11 | import android.widget.EditText;
12 | import android.widget.TextView;
13 | import co.touchlab.R;
14 |
15 | import java.util.ArrayList;
16 | import java.util.List;
17 | import java.util.Random;
18 | import java.util.concurrent.atomic.AtomicInteger;
19 |
20 | public class MyActivity extends Activity
21 | {
22 | private AtomicInteger allCount = new AtomicInteger();
23 | private Handler uiHandler;
24 |
25 |
26 | @Override
27 | public void onCreate(Bundle savedInstanceState)
28 | {
29 | super.onCreate(savedInstanceState);
30 | setContentView(R.layout.main);
31 | uiHandler = new Handler();
32 |
33 | findViewById(R.id.oneHelper)
34 | .setOnClickListener(new View.OnClickListener()
35 | {
36 |
37 | public void onClick(View view)
38 | {
39 | allCount.set(0);
40 | final List allThreads = new ArrayList();
41 |
42 | DatabaseHelper helper = new DatabaseHelper(MyActivity.this);
43 |
44 | for(int i=0; i<4; i++)
45 | {
46 | allThreads.add(new DbInsertThread(helper, 100));
47 | }
48 |
49 | runAllThreads(allThreads);
50 | }
51 | });
52 |
53 | findViewById(R.id.manyHelpers)
54 | .setOnClickListener(new View.OnClickListener()
55 | {
56 |
57 | public void onClick(View view)
58 | {
59 | allCount.set(0);
60 | final List allThreads = new ArrayList();
61 |
62 | for(int i=0; i<4; i++)
63 | {
64 | DatabaseHelper helper = new DatabaseHelper(MyActivity.this);
65 | allThreads.add(new DbInsertThread(helper, 100));
66 | }
67 |
68 | runAllThreads(allThreads);
69 | }
70 | });
71 |
72 | findViewById(R.id.testTransactionIsolation).setOnClickListener(new View.OnClickListener()
73 | {
74 | public void onClick(View view)
75 | {
76 | DatabaseHelper helper = new DatabaseHelper(MyActivity.this);
77 | new SlowInsertThread(helper).start();
78 | new FastSelectThread(helper).start();
79 | }
80 | });
81 |
82 | findViewById(R.id.noTrans).setOnClickListener(new View.OnClickListener()
83 | {
84 | public void onClick(View view)
85 | {
86 | runNoTrans();
87 | }
88 | });
89 |
90 | findViewById(R.id.trans).setOnClickListener(new View.OnClickListener()
91 | {
92 | public void onClick(View view)
93 | {
94 | runTrans();
95 | }
96 | });
97 | }
98 |
99 | @SuppressWarnings("unchecked")
100 | private void runTrans()
101 | {
102 | new AsyncTask(){
103 |
104 | @Override
105 | protected Object doInBackground(Object... objects)
106 | {
107 | DatabaseHelper instance = DatabaseHelper.getInstance(MyActivity.this);
108 |
109 | SQLiteDatabase writableDatabase = instance.getWritableDatabase();
110 |
111 | long start = System.currentTimeMillis();
112 | writableDatabase.beginTransaction();
113 | insertTest(instance);
114 | writableDatabase.setTransactionSuccessful();
115 | writableDatabase.endTransaction();
116 |
117 | return start;
118 | }
119 |
120 | @Override
121 | protected void onPostExecute(Object o)
122 | {
123 | showTime((Long)o, DatabaseHelper.getInstance(MyActivity.this));
124 | }
125 | }.execute();
126 |
127 |
128 | }
129 |
130 | @SuppressWarnings("unchecked")
131 | private void runNoTrans()
132 | {
133 | new AsyncTask(){
134 |
135 | @Override
136 | protected Object doInBackground(Object... objects)
137 | {
138 | DatabaseHelper instance = DatabaseHelper.getInstance(MyActivity.this);
139 | long start = System.currentTimeMillis();
140 | insertTest(instance);
141 |
142 | return start;
143 | }
144 |
145 | @Override
146 | protected void onPostExecute(Object o)
147 | {
148 | showTime((Long)o, DatabaseHelper.getInstance(MyActivity.this));
149 | }
150 | }.execute();
151 | }
152 |
153 | private void showTime(long start, DatabaseHelper instance)
154 | {
155 | long time = System.currentTimeMillis() - start;
156 | ((TextView)findViewById(R.id.timeOut)).setText("Total rows: "+ instance.countSessions() +"/time: "+ Long.toString(time));
157 | }
158 |
159 | private void insertTest(DatabaseHelper helper)
160 | {
161 |
162 | int numberOfInserts = Integer.parseInt(((EditText) findViewById(R.id.numberOfInserts)).getText().toString());
163 | while(numberOfInserts > 0)
164 | {
165 | helper.createSession("Count: "+ numberOfInserts);
166 | numberOfInserts--;
167 | }
168 |
169 | }
170 |
171 | class SlowInsertThread extends Thread
172 | {
173 | private DatabaseHelper helper;
174 | private Handler handler;
175 |
176 | SlowInsertThread(DatabaseHelper helper)
177 | {
178 | this.helper = helper;
179 | handler = new Handler();
180 | }
181 |
182 | @Override
183 | public void run()
184 | {
185 | int count = 0;
186 | while(count < 10)
187 | {
188 | SQLiteDatabase db = helper.getWritableDatabase();
189 | db.beginTransaction();
190 | final ContentValues contentValues = new ContentValues();
191 |
192 | contentValues.put("description", "asdlkfj");
193 |
194 | Log.i(getClass().getName(), "insert");
195 | db.insertOrThrow("session", null, contentValues);
196 | Log.i(getClass().getName(), "start wait");
197 | try
198 | {
199 | Thread.sleep(5000);
200 | }
201 | catch (InterruptedException e)
202 | {
203 | e.printStackTrace();
204 | }
205 | Log.i(getClass().getName(), "end wait");
206 | db.endTransaction();
207 |
208 | count++;
209 | }
210 | }
211 | }
212 |
213 | class FastSelectThread extends Thread
214 | {
215 | private DatabaseHelper helper;
216 | private Handler handler;
217 |
218 | FastSelectThread(DatabaseHelper helper)
219 | {
220 | this.helper = helper;
221 | handler = new Handler();
222 | }
223 |
224 | @Override
225 | public void run()
226 | {
227 | int count = 0;
228 | while(count < 100)
229 | {
230 | helper.loadAllSessions();
231 | Log.i(getClass().getName(), "selected");
232 |
233 | Log.i(getClass().getName(), "start wait");
234 | try
235 | {
236 | Thread.sleep(500);
237 | }
238 | catch (InterruptedException e)
239 | {
240 | e.printStackTrace();
241 | }
242 | Log.i(getClass().getName(), "end wait");
243 |
244 | count++;
245 | }
246 | }
247 | }
248 |
249 | private void runAllThreads(final List allThreads)
250 | {
251 | new Thread(new Runnable()
252 | {
253 | public void run()
254 | {
255 | for (DbInsertThread allThread : allThreads)
256 | {
257 | allThread.start();
258 | }
259 | for (DbInsertThread thread : allThreads)
260 | {
261 | try
262 | {
263 | thread.join();
264 | }
265 | catch (InterruptedException e)
266 | {
267 | }
268 | }
269 |
270 | uiHandler.post(new Runnable()
271 | {
272 | public void run()
273 | {
274 | ((TextView)findViewById(R.id.results)).setText("Inserted "+ allCount.get());
275 | }
276 | });
277 | }
278 | }).start();
279 | }
280 |
281 | class DbInsertThread extends Thread
282 | {
283 | private DatabaseHelper helper;
284 | private int runCount;
285 |
286 | DbInsertThread(DatabaseHelper helper, int runCount)
287 | {
288 | this.helper = helper;
289 | this.runCount = runCount;
290 | }
291 |
292 | @Override
293 | public void run()
294 | {
295 | Random random = new Random();
296 | for(int i=0; i