├── keystorepassword.txt
├── .gitignore
├── dblocking.apk
├── res
├── drawable-hdpi
│ └── icon.png
├── drawable-ldpi
│ └── icon.png
├── drawable-mdpi
│ └── icon.png
├── values
│ └── strings.xml
└── layout
│ └── main.xml
├── default.properties
├── src
└── co
│ └── touchlab
│ └── dblocking
│ ├── SqliteTransactionEndListener.java
│ ├── TransactionEndListener.java
│ ├── EventTimer.java
│ ├── TickerBar.java
│ ├── DatabaseHelper.java
│ └── SqliteDbX.java
├── AndroidManifest.xml
├── proguard.cfg
├── dblocking.iml
├── README
└── dblocking.ipr
/keystorepassword.txt:
--------------------------------------------------------------------------------
1 | flapjack
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | *.iml
3 | .idea/*
4 |
5 | bin
6 | gen
7 | target
8 | out
9 |
--------------------------------------------------------------------------------
/dblocking.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2point0/Android-Database-Locking-Collisions-Example/HEAD/dblocking.apk
--------------------------------------------------------------------------------
/res/drawable-hdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2point0/Android-Database-Locking-Collisions-Example/HEAD/res/drawable-hdpi/icon.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2point0/Android-Database-Locking-Collisions-Example/HEAD/res/drawable-ldpi/icon.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2point0/Android-Database-Locking-Collisions-Example/HEAD/res/drawable-mdpi/icon.png
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | SQLite DB Example, by touchlab
4 |
5 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/co/touchlab/dblocking/SqliteTransactionEndListener.java:
--------------------------------------------------------------------------------
1 | package co.touchlab.dblocking;
2 |
3 | import android.database.sqlite.SQLiteTransactionListener;
4 |
5 | /**
6 | * Created with IntelliJ IDEA.
7 | * User: qui
8 | * Date: 10/10/12
9 | * Time: 5:34 PM
10 | * To change this template use File | Settings | File Templates.
11 | */
12 | public interface SqliteTransactionEndListener extends SQLiteTransactionListener {
13 |
14 | public static final int BEGIN = 0;
15 | public static final int COMMIT = BEGIN + 1;
16 | public static final int ROLLBACK = COMMIT + 1;
17 | public static final int END = ROLLBACK + 1;
18 |
19 | public void onEnd();
20 | }
21 |
--------------------------------------------------------------------------------
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/co/touchlab/dblocking/TransactionEndListener.java:
--------------------------------------------------------------------------------
1 | package co.touchlab.dblocking;
2 |
3 | import android.util.Log;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | /**
9 | * Created with IntelliJ IDEA.
10 | * User: qui
11 | * Date: 10/10/12
12 | * Time: 4:58 PM
13 | * To change this template use File | Settings | File Templates.
14 | */
15 | public class TransactionEndListener implements SqliteTransactionEndListener {
16 | private static final String TAG = TransactionEndListener.class.getSimpleName();
17 |
18 | protected final List mEvents;
19 |
20 | protected final EventTimer mEventTimer;
21 |
22 | public TransactionEndListener(EventTimer eventTimer) {
23 | mEvents = new ArrayList();
24 | mEventTimer = eventTimer;
25 | }
26 |
27 | @Override public void onBegin() {}
28 |
29 | // @debug Counts commits to compare to ends
30 | private int mCommitCount = 0;
31 |
32 | @Override
33 | public void onCommit() {
34 | // @reminder Comparison of commits vs ends
35 | Log.i(TAG, String.format("onCommit: %1$d, events: %2$d", ++mCommitCount, mEvents.size()));
36 | }
37 |
38 | // @Override
39 | public void onEnd() {
40 | mEvents.add(END);
41 |
42 | if(mEvents.size() == mEventTimer.expectedEvents) {
43 | mEventTimer.stop();
44 | }
45 | }
46 |
47 | @Override public void onRollback() {}
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/co/touchlab/dblocking/EventTimer.java:
--------------------------------------------------------------------------------
1 | package co.touchlab.dblocking;
2 |
3 | import java.util.List;
4 |
5 | /**
6 | * Created with IntelliJ IDEA.
7 | * User: qui
8 | * Date: 10/10/12
9 | * Time: 4:51 PM
10 | * To change this template use File | Settings | File Templates.
11 | */
12 | public final class EventTimer {
13 |
14 | public final int expectedEvents;
15 |
16 | private long mStartTime;
17 | private List mTickTimes;
18 | private long mEndTime;
19 |
20 | public EventTimer(int expectedEvents) {
21 | this.expectedEvents = expectedEvents;
22 | mStartTime = 0;
23 | mEndTime = 0;
24 | }
25 |
26 | public long start() {
27 | mStartTime = System.currentTimeMillis();
28 | return mStartTime;
29 | }
30 |
31 | public long tick() {
32 | long tick = System.currentTimeMillis();
33 | mTickTimes.add(tick);
34 | return tick;
35 | }
36 |
37 | public long stop() {
38 | mEndTime = System.currentTimeMillis();
39 |
40 | if(mOnEventListener != null) {
41 | mOnEventListener.onStop(mEndTime - mStartTime);
42 | }
43 |
44 | return mEndTime;
45 | }
46 |
47 | public long delta() {
48 | return mEndTime - mStartTime;
49 | }
50 |
51 | public long[] tickDeltas() {
52 | final int S = mTickTimes.size();
53 | long[] deltas = new long[S];
54 | for(int i=0; i
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 |
--------------------------------------------------------------------------------
/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.
4 |
5 | Button :Description
6 | 1 Write :Perform writes from multiple threads using only one instance of a DatabaseHelper. This should never cause a failure
7 | + Write :Perform writes from multiple threads, each thread with its own DatabaseHelper. First failure in a thread will end that thread leaving only one thread able to complete it's process. The ticker bar should have one dominant color [blue,white,yellow,red] (red at beginning and end signify start and end of run)
8 | 1 Write + Read :One thread performs writes while other threads perform reads. All threads have their own copy of DatabaseHelper. Failures can occur but if you're lucky, they won't.
9 | + Read :All threads perform reads with their own copy of DatabaseHelper. Failures can occur but if you're lucky, they won't.
10 | Test Transaction Isolation :Interleaving reads and writes with the same copy of DatabaseHelper will not fail but transactions are exclusive and cannot be disturbed.
11 | No Transaction :Perform a timed series of writes individually
12 | Transaction :Perform a timed series of writes in a transaction
13 | Exclusive No Yielding :One long running transaction and many short running transactions are performed. Each transaction is exclusive and must complete before another transaction/operation is allowed. The ticker bar should show the long transaction finishing before trailing short transactions run.
14 | Safely Contended Yield :One long running transaction yielding at times to allow short running transactions to perform their operations. The ticker bar should show short transactions finishing before the long transaction finishes.
15 | No Yield Read :A thread performing writes in a transaction is followed by other threads performing reads. The write transaction must complete before reads can be made
16 | Yield and Read :A thread performing writes in a transaction yields to following threads performing reads. The yielding allows a few reads to execute before waiting for the entire write transaction to complete.
17 |
--------------------------------------------------------------------------------
/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
18 |
23 |
24 |
29 |
34 |
35 |
36 |
37 |
42 |
43 |
47 |
48 |
53 |
54 |
60 |
66 |
67 |
68 |
73 |
74 |
79 |
80 |
85 |
86 |
87 |
88 |
89 |
94 |
95 |
100 |
101 |
106 |
107 |
108 |
109 |
113 |
114 |
119 |
120 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/src/co/touchlab/dblocking/TickerBar.java:
--------------------------------------------------------------------------------
1 | package co.touchlab.dblocking;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.util.AttributeSet;
8 | import android.view.View;
9 |
10 | import java.util.ArrayList;
11 | import java.util.List;
12 |
13 | /**
14 | * Created with IntelliJ IDEA.
15 | * User: qui
16 | * Date: 10/10/12
17 | * Time: 12:12 PM
18 | * To change this template use File | Settings | File Templates.
19 | */
20 | public class TickerBar extends View {
21 |
22 | public static final int THIN = 0;
23 | public static final int MEDIUM = THIN + 1;
24 | public static final int THICK = MEDIUM + 1;
25 |
26 | protected static final int DEFAULT_BACKGROUND_COLOR = Color.TRANSPARENT;
27 | protected static final int DEFAULT_TICK_COLOR = Color.GREEN;
28 |
29 | protected int mTickColor;
30 | protected float mTickWidth;
31 |
32 | protected float mTotalWidth;
33 |
34 | private Paint mBackgroundPaint;
35 | private Paint mBarPaint;
36 |
37 | protected final float DP;
38 |
39 | protected final float TICK_1_WIDTH;
40 | protected final float TICK_2_WIDTH;
41 | protected final float TICK_3_WIDTH;
42 |
43 | private List mColorList;
44 | private List mWidthList;
45 |
46 | public TickerBar(Context context) {
47 | this(context, null);
48 | }
49 |
50 | public TickerBar(Context context, AttributeSet attrs) {
51 | this(context, attrs, 0);
52 | }
53 |
54 | public TickerBar(Context context, AttributeSet attrs, int defStyle) {
55 | super(context, attrs, defStyle);
56 |
57 | DP = context.getResources().getDisplayMetrics().density;
58 |
59 | TICK_1_WIDTH = DP;
60 | TICK_2_WIDTH = DP * 2;
61 | TICK_3_WIDTH = DP * 3;
62 |
63 | init(context, attrs, defStyle);
64 | }
65 |
66 | protected void init(Context context, AttributeSet attrs, int defStyle) {
67 |
68 | mBackgroundPaint = new Paint();
69 | mBackgroundPaint.setStyle(Paint.Style.FILL);
70 | mBackgroundPaint.setColor(DEFAULT_BACKGROUND_COLOR);
71 |
72 | mBarPaint = new Paint();
73 | mBarPaint.setStyle(Paint.Style.FILL);
74 |
75 | mColorList = new ArrayList();
76 | mWidthList = new ArrayList();
77 |
78 | setDefaults();
79 |
80 | }
81 |
82 | private void setDefaults() {
83 |
84 | mColorList.clear();
85 | mWidthList.clear();
86 |
87 | mTotalWidth = 0f;
88 |
89 | mTickWidth = TICK_1_WIDTH;
90 | mTickColor = DEFAULT_TICK_COLOR;
91 | mBarPaint.setColor(mTickColor);
92 | mBarPaint.setStrokeWidth(mTickWidth);
93 |
94 | }
95 |
96 | /**
97 | * Clears tick data redraws
98 | */
99 | public void clear() {
100 | setDefaults();
101 | invalidate();
102 | }
103 |
104 | /*
105 | * All {@code tick} methods do not update the view
106 | * All {@code drawTick} methods do update the view
107 | */
108 | public void tick() {
109 | tick(mTickColor);
110 | }
111 |
112 | public void tick(int color) {
113 | tick(color, THIN);
114 | }
115 |
116 | public void tick(int color, int widthEnum) {
117 |
118 | float width = getWidthFromEnum(widthEnum);
119 |
120 | if(color != mTickColor) {
121 |
122 | mTickColor = color;
123 | mBarPaint.setColor(color);
124 |
125 | mColorList.add(color);
126 | mWidthList.add(width);
127 |
128 | } else {
129 |
130 | int lastIndex = mWidthList.size() - 1;
131 | mWidthList.set(lastIndex, mWidthList.get(lastIndex) + width);
132 |
133 | }
134 |
135 | if(width != mTickWidth) {
136 | mTickWidth = width;
137 | }
138 |
139 | mTotalWidth += width;
140 |
141 | }
142 |
143 | public void drawTick() {
144 | tick();
145 | invalidate();
146 | }
147 |
148 | public void drawTick(int color) {
149 | tick(color);
150 | invalidate();
151 | }
152 |
153 | public void drawTick(int color, int widthEnum) {
154 | tick(color, widthEnum);
155 | invalidate();
156 | }
157 |
158 | protected float getWidthFromEnum(int widthEnum) {
159 | switch (widthEnum) {
160 | case THIN: return TICK_1_WIDTH;
161 | case MEDIUM: return TICK_2_WIDTH;
162 | case THICK: return TICK_3_WIDTH;
163 | default: return TICK_1_WIDTH;
164 | }
165 | }
166 |
167 | @Override
168 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
169 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
170 | }
171 |
172 | @Override
173 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
174 | super.onLayout(changed, left, top, right, bottom);
175 | }
176 |
177 | @Override
178 | protected void onDraw(Canvas canvas) {
179 | super.onDraw(canvas);
180 |
181 | final int viewWidth = getWidth();
182 | final int viewHeight = getHeight();
183 |
184 | if(mBackgroundPaint.getColor() != Color.TRANSPARENT) {
185 | canvas.drawRect(0, 0, viewWidth, viewHeight, mBackgroundPaint);
186 | }
187 |
188 | final int S = mColorList.size();
189 |
190 | if(S > 0) {
191 |
192 | float x = 0;
193 | float barWidth;
194 | float normalizedWidth;
195 |
196 | // TODO Exit on full width drawn if not normalizing
197 | // TODO Left align tickbar if not normalizing
198 | for(int i=0; i loadAllSessions()
137 | {
138 | List sessions = new ArrayList();
139 |
140 | final SQLiteDatabase readableDatabase = getReadableDatabase();
141 | final Cursor cursor = readableDatabase.query(SessionTable.NAME, SessionTable.COLS
142 | , null, null, null, null, SessionTable._ID, null);
143 |
144 | try
145 | {
146 | while (cursor.moveToNext())
147 | {
148 | final Session session = sessionFromCursor(cursor);
149 |
150 | sessions.add(session);
151 | }
152 | }
153 | finally
154 | {
155 | cursor.close();
156 | }
157 |
158 | return sessions;
159 | }
160 |
161 | public static class Session
162 | {
163 | public final int id;
164 | public final String description;
165 |
166 | public Session(int id, String description)
167 | {
168 | this.description = description;
169 | this.id = id;
170 | }
171 |
172 | }
173 |
174 | /**
175 | * Gets a single session from the DB
176 | * If record doesn't exist or error, empty session is returned
177 | * @param id
178 | * @return
179 | */
180 | public Session getSession(int id)
181 | {
182 | final SQLiteDatabase readableDatabase = getReadableDatabase();
183 |
184 | final Session session;
185 | final Cursor cursor = readableDatabase.query(SessionTable.NAME,
186 | SessionTable.COLS,
187 | SessionTable._ID+"=?",
188 | new String[]{String.valueOf(id)},
189 | null,null,null,null
190 | );
191 | try
192 | {
193 | cursor.moveToNext();
194 |
195 | session = sessionFromCursor(cursor);
196 | }
197 | finally
198 | {
199 | cursor.close();
200 | }
201 |
202 | return session;
203 | }
204 |
205 | /**
206 | * Record should be [ID, DESCRIPTION]
207 | * @param cursor
208 | * @return
209 | */
210 | private Session sessionFromCursor(Cursor cursor)
211 | {
212 | final Session session = new Session(cursor.getInt(0), cursor.getString(1));
213 | return session;
214 | }
215 |
216 | }
217 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/co/touchlab/dblocking/SqliteDbX.java:
--------------------------------------------------------------------------------
1 | package co.touchlab.dblocking;
2 |
3 | import android.app.Activity;
4 | import android.content.ContentValues;
5 | import android.database.SQLException;
6 | import android.database.sqlite.SQLiteDatabase;
7 | import android.graphics.Color;
8 | import android.os.AsyncTask;
9 | import android.os.Bundle;
10 | import android.os.Handler;
11 | import android.util.Log;
12 | import android.view.View;
13 | import android.widget.EditText;
14 | import android.widget.TextView;
15 | import co.touchlab.R;
16 |
17 | import java.util.ArrayList;
18 | import java.util.List;
19 | import java.util.Random;
20 | import java.util.concurrent.atomic.AtomicInteger;
21 |
22 | /**
23 | * SqliteDbX = SqliteDbExample
24 | * This app is just a collection of examples written to illustrate simple concepts
25 | * Testing is minimal and this app is prone to many failures not related to the illustrated concepts
26 | */
27 | public class SqliteDbX extends Activity
28 | {
29 | private static final String TAG = SqliteDbX.class.getSimpleName();
30 |
31 | private static final int DEFAULT_INSERTS = 20;
32 |
33 | private TextView mTvRunTime;
34 | private EditText mEtInsertCount;
35 | private TextView mTvResults;
36 | private TickerBar mTbThreadTicker;
37 |
38 | private AtomicInteger allCount = new AtomicInteger();
39 | private Handler uiHandler;
40 |
41 | private static int sThreadCounter = 0;
42 |
43 | @Override
44 | public void onCreate(Bundle savedInstanceState)
45 | {
46 |
47 | super.onCreate(savedInstanceState);
48 | setContentView(R.layout.main);
49 |
50 | mTvRunTime = (TextView) findViewById(R.id.timeOut);
51 | mEtInsertCount = (EditText) findViewById(R.id.numberOfInserts);
52 | mTvResults = (TextView) findViewById(R.id.results);
53 | mTbThreadTicker = (TickerBar) findViewById(R.id.tickerBar);
54 |
55 | mEtInsertCount.setHint(String.valueOf(DEFAULT_INSERTS));
56 |
57 | allCount.set(0);
58 | uiHandler = new Handler();
59 |
60 | // One instance of a SqliteDatabaseHelper will not crash
61 | findViewById(R.id.oneWrite).setOnClickListener(new View.OnClickListener() {
62 | public void onClick(View view)
63 | {
64 | runHelperInstances(true);
65 | }
66 | });
67 |
68 | // More than one instance of SqliteDatabaseHelper will eventually conflict and crash
69 | findViewById(R.id.manyWrite).setOnClickListener(new View.OnClickListener() {
70 | public void onClick(View view)
71 | {
72 | runHelperInstances(false);
73 | }
74 | });
75 |
76 | // One write many reads can crash on conflict (but won't if no conflict)
77 | findViewById(R.id.oneWriteManyRead).setOnClickListener(new View.OnClickListener() {
78 | public void onClick(View view)
79 | {
80 | runWriteRead(1);
81 | }
82 | });
83 |
84 | // All reads can crash on conflict (but won't if no conflict)
85 | findViewById(R.id.manyRead).setOnClickListener(new View.OnClickListener() {
86 | public void onClick(View view)
87 | {
88 | runWriteRead(0);
89 | }
90 | });
91 |
92 | // Read and writes will take turns
93 | findViewById(R.id.testTransactionIsolation).setOnClickListener(new View.OnClickListener()
94 | {
95 | public void onClick(View view)
96 | {
97 | clearFeedback();
98 | DatabaseHelper helper = DatabaseHelper.getInstance(SqliteDbX.this);
99 | new FastSelectThread(helper, sThreadCounter++).start();
100 | new SlowInsertThread(helper, sThreadCounter++).start();
101 | }
102 | });
103 |
104 | // Individual inserts take time
105 | findViewById(R.id.noTrans).setOnClickListener(new View.OnClickListener()
106 | {
107 | public void onClick(View view)
108 | {
109 | runNoTrans();
110 | }
111 | });
112 |
113 | // Inserts in a transaction are faster
114 | findViewById(R.id.trans).setOnClickListener(new View.OnClickListener()
115 | {
116 | public void onClick(View view)
117 | {
118 | runTrans();
119 | }
120 | });
121 |
122 | // No yield in a long running transaction will block database writes until the transaction is complete
123 | findViewById(R.id.no_yielding).setOnClickListener(new View.OnClickListener()
124 | {
125 | public void onClick(View view) {
126 | runYield(false);
127 | }
128 | });
129 |
130 | // Yielding allows openings in long running transactions for other operations to perform
131 | findViewById(R.id.yielding).setOnClickListener(new View.OnClickListener()
132 | {
133 | public void onClick(View view) {
134 | runYield(true);
135 | }
136 | });
137 |
138 | // Reads aren't performed when a transaction is exclusive
139 | findViewById(R.id.no_yield_read).setOnClickListener(new View.OnClickListener()
140 | {
141 | public void onClick(View view) {
142 | runYieldRead(false);
143 | }
144 | });
145 |
146 | // Yielding in a transaction allows other operations to perform at yield points
147 | findViewById(R.id.yield_and_read).setOnClickListener(new View.OnClickListener()
148 | {
149 | public void onClick(View view) {
150 | runYieldRead(true);
151 | }
152 | });
153 |
154 | }
155 |
156 | @Override
157 | protected void onDestroy() {
158 | Log.i(TAG, "Deleting database");
159 | deleteDatabase(DatabaseHelper.DATABASE_NAME);
160 | super.onDestroy();
161 | }
162 |
163 | private void clearFeedback() {
164 | mTbThreadTicker.clear();
165 | mTbThreadTicker.drawTick(Color.RED, TickerBar.THICK);
166 | mTvResults.setText("");
167 | mTvRunTime.setText("");
168 | }
169 |
170 | private void runHelperInstances(boolean singleInstance) {
171 |
172 | // Draw a tick when the example ends
173 | final TickerBar tickerBar = mTbThreadTicker;
174 | EventTimer yieldEventTimer = new EventTimer(4);
175 | yieldEventTimer.setOnEventListener(new EventTimer.OnEventListener() {
176 | @Override
177 | public void onStop(long delta) {
178 | runOnUiThread(new Runnable() {
179 | @Override
180 | public void run() {
181 | tickerBar.drawTick(Color.RED, TickerBar.THICK);
182 | }
183 | });
184 |
185 | }
186 | });
187 |
188 | TransactionEndListener endListener = new TransactionEndListener(yieldEventTimer);
189 |
190 | allCount.set(0);
191 |
192 | final int threadCount = 4;
193 | final List allThreads = new ArrayList(threadCount);
194 |
195 | final int runCount = 25;
196 |
197 | DatabaseHelper helper = new DatabaseHelper(SqliteDbX.this);
198 |
199 | int[] colors = new int[] {Color.BLUE, Color.WHITE, Color.RED, Color.YELLOW};
200 |
201 | for(int i=0; i threadCount)
222 | writeCount = threadCount;
223 |
224 | allCount.set(0);
225 |
226 | final List allThreads = new ArrayList(threadCount);
227 |
228 | final int insertCount = 50;
229 |
230 | for(int i=0; i threads = new ArrayList(9);
378 |
379 | threads.add(new SingleTransactionRepeatedInserts(helper, sThreadCounter++, 1000, doYield));
380 |
381 | for(int i=0; i<5; i++)
382 | threads.add(new FastSelectThread(helper, sThreadCounter++, 5));
383 |
384 | runAllThreads(threads);
385 |
386 | }
387 |
388 | private void showTime(long start, DatabaseHelper helper)
389 | {
390 | long delta = System.currentTimeMillis() - start;
391 | mTvRunTime.setText(String.format("Execution time: %2$d, total rows: %1$d", helper.getSessionCount(), delta));
392 | }
393 |
394 | /**
395 | * Inserts number of records specified by UI
396 | * @param helper
397 | * @throws android.database.SQLException
398 | */
399 | private void performInserts(DatabaseHelper helper) throws SQLException
400 | {
401 | // @reminder UI query is ok but never perform UI updates
402 | String sInsert = mEtInsertCount.getText().toString();
403 | int numberOfInserts = Integer.getInteger(sInsert, DEFAULT_INSERTS);
404 | while(numberOfInserts > 0)
405 | {
406 | helper.insertSession("Count: " + numberOfInserts);
407 | numberOfInserts--;
408 | }
409 | }
410 |
411 | class SingleTransactionRepeatedInserts extends Thread
412 | {
413 | private final String TAG = SingleTransactionRepeatedInserts.class.getSimpleName();
414 |
415 | private final SQLiteDatabase mDb;
416 |
417 | private final SqliteTransactionEndListener mTransactionListener;
418 |
419 | private final int mCount;
420 |
421 | private static final int DEFAULT_TICK_COLOR = Color.YELLOW;
422 |
423 | private final int mTickColor;
424 |
425 | private final long mDelay;
426 |
427 | private final boolean mYield;
428 |
429 | SingleTransactionRepeatedInserts(DatabaseHelper helper, int id, int insertCount
430 | , SqliteTransactionEndListener sqliteTransactionListener) {
431 | this(helper, id, insertCount, sqliteTransactionListener, 0);
432 | }
433 | SingleTransactionRepeatedInserts(DatabaseHelper helper, int id, int insertCount, boolean enableYield) {
434 | this(helper, id, insertCount, null, DEFAULT_TICK_COLOR, 0, enableYield);
435 | }
436 | SingleTransactionRepeatedInserts(DatabaseHelper helper, int id, int insertCount
437 | , SqliteTransactionEndListener sqLiteTransactionListener, long delay) {
438 | this(helper, id, insertCount, sqLiteTransactionListener, DEFAULT_TICK_COLOR, delay, false);
439 | }
440 | SingleTransactionRepeatedInserts(DatabaseHelper helper, int id, int insertCount
441 | , SqliteTransactionEndListener sqLiteTransactionListener, int tickColor, long delay
442 | , boolean enableYield) {
443 |
444 | setName(String.format("%1$s-%2$d", TAG, id));
445 |
446 | mDb = helper.getWritableDatabase();
447 | mTransactionListener = sqLiteTransactionListener;
448 | mCount = insertCount;
449 | mTickColor = tickColor;
450 | mDelay = delay;
451 | mYield = enableYield;
452 |
453 | Log.i(getName(), helper.toString());
454 |
455 | }
456 |
457 | @Override
458 | public void run()
459 | {
460 | if(mDelay > 0) {
461 | try {
462 | sleep(mDelay);
463 | } catch (InterruptedException e) {}
464 | }
465 |
466 | final ContentValues contentValues = new ContentValues();
467 |
468 | if(mTransactionListener != null)
469 | mDb.beginTransactionWithListener(mTransactionListener);
470 | else
471 | mDb.beginTransaction();
472 |
473 | Log.i(getName(), String.format("After beginTransaction, starting insert of %1$d records, total records %2$d"
474 | , mCount, DatabaseHelper.getInstance(SqliteDbX.this).getSessionCount()));
475 |
476 | for(int i=0;i 0 ? insertCount : 10;
527 | }
528 |
529 | @Override
530 | public void run()
531 | {
532 | int count = 0;
533 | while(count++ < mCount)
534 | {
535 | final ContentValues contentValues = new ContentValues();
536 |
537 | mDb.beginTransaction();
538 |
539 | contentValues.put(DatabaseHelper.SessionTable.DESCRIPTION, getName() + count);
540 |
541 | Log.i(getName(), "inserting record");
542 | mDb.insertOrThrow(DatabaseHelper.SessionTable.NAME, null, contentValues);
543 |
544 | Log.i(getName(), "start wait");
545 | try
546 | {
547 | Thread.sleep(5000);
548 | }
549 | catch (InterruptedException e)
550 | {
551 | e.printStackTrace();
552 | }
553 | Log.i(getName(), "end wait");
554 |
555 | mDb.setTransactionSuccessful();
556 | mDb.endTransaction();
557 |
558 | count++;
559 | }
560 |
561 | Log.i(getName(), "finished!");
562 | }
563 | }
564 |
565 | /**
566 | * Performs a query on all DB records with a pause between selections
567 | */
568 | class FastSelectThread extends Thread
569 | {
570 | private final String TAG = FastSelectThread.class.getSimpleName();
571 |
572 | private final DatabaseHelper mHelper;
573 | private final Handler mHandler;
574 | private final int mCount;
575 |
576 | FastSelectThread(DatabaseHelper helper, int id) {
577 | this(helper, id, -1);
578 | }
579 | FastSelectThread(DatabaseHelper helper, int id, int selectCount)
580 | {
581 | setName(String.format("%1$s-%2$d", TAG, id));
582 |
583 | mHelper = helper;
584 | mHandler = new Handler();
585 | mCount = selectCount > 0 ? selectCount : 50;
586 |
587 | Log.i(getName(), helper.toString());
588 | }
589 |
590 | @Override
591 | public void run()
592 | {
593 | int count = 0;
594 | while(count < mCount)
595 | {
596 | mHelper.loadAllSessions();
597 | Log.i(getName(), "selected records");
598 |
599 | Log.i(getName(), "start wait");
600 | try
601 | {
602 | Thread.sleep(200);
603 | }
604 | catch (InterruptedException e)
605 | {
606 | e.printStackTrace();
607 | }
608 |
609 | Log.i(getName(), "end wait");
610 |
611 | count++;
612 | }
613 |
614 | Log.i(getName(), "finished!");
615 | }
616 | }
617 |
618 | /**
619 | * Executes all threads from another thread printing summary when all threads are complete
620 | * @param allThreads
621 | */
622 | private void runAllThreads(final List allThreads)
623 | {
624 | new Thread(new Runnable()
625 | {
626 | public void run()
627 | {
628 |
629 | for (Thread thread : allThreads)
630 | {
631 | thread.start();
632 | }
633 |
634 | // Wait for all threads to complete before running
635 | for (Thread thread : allThreads)
636 | {
637 | try
638 | {
639 | thread.join();
640 | Log.i(thread.getName(), "collected");
641 | }
642 | catch (InterruptedException e)
643 | {
644 | Log.e(TAG, "Interrupted", e);
645 | }
646 | }
647 |
648 | uiHandler.post(new Runnable()
649 | {
650 | public void run()
651 | {
652 | mTvResults.setText(String.format("Inserted %1$d", allCount.get()));
653 | }
654 | });
655 |
656 | Log.i(TAG, "All threads finished!");
657 |
658 | }
659 | }).start();
660 | }
661 |
662 | class DbInsertThread extends Thread
663 | {
664 | private final String TAG = DbInsertThread.class.getSimpleName();
665 |
666 | private final DatabaseHelper mDbHelper;
667 | private final TransactionEndListener mEndListener;
668 | private int mRunCount;
669 |
670 | private final int mColor;
671 |
672 | DbInsertThread(DatabaseHelper helper, int runCount, int id) {
673 | this(helper, runCount, id, null, Color.TRANSPARENT);
674 | }
675 | DbInsertThread(DatabaseHelper helper, int runCount, int id, TransactionEndListener endListener, int color)
676 | {
677 | setName(String.format("%1$s-%2$d", TAG, id));
678 |
679 | mDbHelper = helper;
680 | mEndListener = endListener;
681 | mRunCount = runCount;
682 | mColor = color;
683 |
684 | Log.i(getName(), helper.toString());
685 | }
686 |
687 | @Override
688 | public void run()
689 | {
690 | Random random = new Random();
691 |
692 | for(int i=0; i< mRunCount; i++)
693 | {
694 | if(i % 10 == 0)
695 | Log.i(getName(), "writing...");
696 |
697 | try
698 | {
699 | mDbHelper.insertSession("heyo - " + random.nextInt(12345678));
700 | allCount.incrementAndGet();
701 | mTbThreadTicker.tick(mColor);
702 | }
703 | catch (Exception e)
704 | {
705 | Log.e(getName(), "Insert failed!!!, stopping writes", e);
706 | break;
707 | }
708 | }
709 |
710 | if(mEndListener != null) {
711 | mEndListener.onEnd();
712 | }
713 |
714 | Log.i(getName(), "finished!");
715 |
716 | }
717 | }
718 | }
719 |
--------------------------------------------------------------------------------