Skip to content
This repository
Browse code

Make MPMetrics a singleton to fix a race and other assorted minor fixes.

  • Loading branch information...
commit 468fac14b3a1ec58407ee48b28202e0fa08266e5 1 parent 00bd9a2
Anlu Wang authored
BIN  MPMetrics.jar
Binary file not shown
BIN  MPMetrics_v1.1.jar
Binary file not shown
BIN  bin/classes/com/mixpanel/android/mpmetrics/MPDbAdapter$MPDatabaseHelper.class
Binary file not shown
BIN  bin/classes/com/mixpanel/android/mpmetrics/MPDbAdapter.class
Binary file not shown
BIN  bin/classes/com/mixpanel/android/mpmetrics/MPMetrics$1.class
Binary file not shown
BIN  bin/classes/com/mixpanel/android/mpmetrics/MPMetrics$TrackTask.class
Binary file not shown
BIN  bin/classes/com/mixpanel/android/mpmetrics/MPMetrics.class
Binary file not shown
BIN  bin/mixpanel-android.jar
Binary file not shown
2  demo/Hello Mixpanel/.classpath
@@ -3,7 +3,7 @@
3 3 <classpathentry kind="src" path="src"/>
4 4 <classpathentry kind="src" path="gen"/>
5 5 <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
6   - <classpathentry kind="lib" path="lib/MPMetrics.jar"/>
7 6 <classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
  7 + <classpathentry kind="lib" path="/Users/anlu/Desktop/mixpanel-android/MPMetrics_v1.1.jar"/>
8 8 <classpathentry kind="output" path="bin/classes"/>
9 9 </classpath>
BIN  demo/Hello Mixpanel/bin/Hello Mixpanel.apk
Binary file not shown
BIN  demo/Hello Mixpanel/bin/classes.dex
Binary file not shown
BIN  demo/Hello Mixpanel/bin/classes/com/mixpanel/android/hellomixpanel/HelloMixpanel$1.class
Binary file not shown
BIN  demo/Hello Mixpanel/bin/classes/com/mixpanel/android/hellomixpanel/HelloMixpanel.class
Binary file not shown
2  demo/Hello Mixpanel/src/com/mixpanel/android/hellomixpanel/HelloMixpanel.java
@@ -22,7 +22,7 @@ public void onCreate(Bundle savedInstanceState) {
22 22 super.onCreate(savedInstanceState);
23 23 setContentView(R.layout.main);
24 24
25   - mMPMetrics = new MPMetrics(this, "c35a4b5163ee2c097de447765f691544");
  25 + mMPMetrics = MPMetrics.getInstance(this, "c35a4b5163ee2c097de447765f691544");
26 26
27 27 mButton = (Button) findViewById(R.id.button);
28 28 mButton.setOnClickListener(new View.OnClickListener() {
70 src/com/mixpanel/android/mpmetrics/MPDbAdapter.java
@@ -29,20 +29,27 @@
29 29
30 30 private static final String DATABASE_NAME = "mixpanel";
31 31 private static final String DATABASE_TABLE = "events";
32   - private static final int DATABASE_VERSION = 2;
  32 + private static final int DATABASE_VERSION = 3;
33 33
34 34 public static final String KEY_DATA = "data";
35 35 public static final String KEY_CREATED_AT = "created_at";
  36 + public static final String KEY_TOKEN = "token";
36 37
37 38 private static final String DATABASE_CREATE =
38 39 "CREATE TABLE " + DATABASE_TABLE + " (_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
39 40 KEY_DATA + " STRING NOT NULL," +
40   - KEY_CREATED_AT + " INTEGER NOT NULL);";
41   - private static final String DATABASE_INDEX =
  41 + KEY_CREATED_AT + " INTEGER NOT NULL," +
  42 + KEY_TOKEN + " STRING NOT NULL);";
  43 + private static final String TIME_INDEX =
42 44 "CREATE INDEX IF NOT EXISTS time_idx ON " + DATABASE_TABLE +
43 45 " (" + KEY_CREATED_AT + ");";
  46 + private static final String TOKEN_INDEX =
  47 + "CREATE INDEX IF NOT EXISTS token_idx ON " + DATABASE_TABLE +
  48 + " (" + KEY_TOKEN + ");";
44 49
45 50 private MPDatabaseHelper mDb;
  51 +
  52 + private String mToken;
46 53
47 54 private static class MPDatabaseHelper extends SQLiteOpenHelper {
48 55 MPDatabaseHelper(Context context) {
@@ -52,7 +59,8 @@
52 59 @Override
53 60 public void onCreate(SQLiteDatabase db) {
54 61 db.execSQL(DATABASE_CREATE);
55   - db.execSQL(DATABASE_INDEX);
  62 + db.execSQL(TIME_INDEX);
  63 + db.execSQL(TOKEN_INDEX);
56 64 }
57 65
58 66 @Override
@@ -63,12 +71,14 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
63 71
64 72 db.execSQL("DROP TABLE " + DATABASE_TABLE);
65 73 db.execSQL(DATABASE_CREATE);
66   - db.execSQL(DATABASE_INDEX);
  74 + db.execSQL(TIME_INDEX);
  75 + db.execSQL(TOKEN_INDEX);
67 76 }
68 77 }
69 78
70   - public MPDbAdapter(Context context) {
  79 + public MPDbAdapter(Context context, String token) {
71 80 mDb = new MPDatabaseHelper(context);
  81 + mToken = token;
72 82 }
73 83
74 84 /**
@@ -89,10 +99,12 @@ public int addEvent(JSONObject j) {
89 99 ContentValues cv = new ContentValues();
90 100 cv.put(KEY_DATA, j.toString());
91 101 cv.put(KEY_CREATED_AT, System.currentTimeMillis());
  102 + cv.put(KEY_TOKEN, mToken);
92 103 db.insert(DATABASE_TABLE, null, cv);
93 104
94   - c = db.rawQuery("SELECT * FROM " + DATABASE_TABLE, null);
95   - count = c.getCount();
  105 + c = db.rawQuery("SELECT COUNT(*) FROM " + DATABASE_TABLE + " WHERE token = '" + mToken + "'", null);
  106 + c.moveToFirst();
  107 + count = c.getInt(0);
96 108 } catch (SQLiteException e) {
97 109 Log.e(LOGTAG, "addEvent", e);
98 110 } finally {
@@ -106,16 +118,36 @@ public int addEvent(JSONObject j) {
106 118 }
107 119
108 120 /**
  121 + * Removes events with an _id <= last_id
  122 + * @param last_id the last id to delete
  123 + */
  124 + public void cleanupEvents(String last_id) {
  125 + synchronized (this) {
  126 + if (Global.DEBUG) { Log.d(LOGTAG, "cleanupEvents _id " + last_id); }
  127 +
  128 + try {
  129 + SQLiteDatabase db = mDb.getWritableDatabase();
  130 + db.delete(DATABASE_TABLE, "_id <= " + last_id + " AND token = '" + mToken + "'", null);
  131 + } catch (SQLiteException e) {
  132 + // If there's an exception, oh well, let the events persist
  133 + Log.e(LOGTAG, "cleanupEvents", e);
  134 + } finally {
  135 + mDb.close();
  136 + }
  137 + }
  138 + }
  139 +
  140 + /**
109 141 * Removes events before time.
110 142 * @param time the unix epoch in milliseconds to remove events before
111 143 */
112 144 public void cleanupEvents(long time) {
113 145 synchronized (this) {
114   - if (Global.DEBUG) { Log.d(LOGTAG, "cleanupEvent"); }
  146 + if (Global.DEBUG) { Log.d(LOGTAG, "cleanupEvents time " + time); }
115 147
116 148 try {
117 149 SQLiteDatabase db = mDb.getWritableDatabase();
118   - db.delete(DATABASE_TABLE, KEY_CREATED_AT + " <= " + time, null);
  150 + db.delete(DATABASE_TABLE, KEY_CREATED_AT + " <= " + time + " AND token = '" + mToken + "'", null);
119 151 } catch (SQLiteException e) {
120 152 // If there's an exception, oh well, let the events persist
121 153 Log.e(LOGTAG, "cleanupEvents", e);
@@ -135,24 +167,25 @@ public void cleanupEvents(long time) {
135 167 * add it to the string to because we have the data here, and so we don't have to
136 168 * call getReadableDatabase() multiple times.
137 169 *
138   - * @return the data string representing the events, or null if none could be
139   - * successfully retrieved.
  170 + * @return String array containing the maximum ID and the data string
  171 + * representing the events, or null if none could be successfully retrieved.
140 172 */
141   - public String generateDataString() {
  173 + public String[] generateDataString() {
142 174 synchronized (this) {
143 175 Cursor c = null;
144 176 String data = null;
145   - String timestamp = null;
  177 + String last_id = null;
146 178
147 179 try {
148 180 SQLiteDatabase db = mDb.getReadableDatabase();
149 181 c = db.rawQuery("SELECT * FROM " + DATABASE_TABLE +
150   - " ORDER BY " + KEY_CREATED_AT + " ASC", null);
  182 + " WHERE token = '" + mToken + "'" +
  183 + " ORDER BY " + KEY_CREATED_AT + " ASC LIMIT 50", null);
151 184 JSONArray arr = new JSONArray();
152 185
153 186 while (c.moveToNext()) {
154 187 if (c.isLast()) {
155   - timestamp = c.getString(c.getColumnIndex(KEY_CREATED_AT));
  188 + last_id = c.getString(c.getColumnIndex("_id"));
156 189 }
157 190 try {
158 191 JSONObject j = new JSONObject(c.getString(c.getColumnIndex(KEY_DATA)));
@@ -174,8 +207,9 @@ public String generateDataString() {
174 207 }
175 208 }
176 209
177   - if (timestamp != null && data != null) {
178   - return timestamp + ":" + data;
  210 + if (last_id != null && data != null) {
  211 + String[] ret = {last_id, data};
  212 + return ret;
179 213 }
180 214 return null;
181 215 }
41 src/com/mixpanel/android/mpmetrics/MPMetrics.java
@@ -2,13 +2,14 @@
2 2
3 3 import java.io.IOException;
4 4 import java.util.ArrayList;
  5 +import java.util.HashMap;
5 6 import java.util.Iterator;
6 7 import java.util.List;
7   -import java.util.StringTokenizer;
8 8 import java.util.Timer;
9 9 import java.util.TimerTask;
10   -import java.util.concurrent.ExecutorService;
11   -import java.util.concurrent.Executors;
  10 +import java.util.concurrent.LinkedBlockingQueue;
  11 +import java.util.concurrent.ThreadPoolExecutor;
  12 +import java.util.concurrent.TimeUnit;
12 13
13 14 import org.apache.http.HttpEntity;
14 15 import org.apache.http.HttpResponse;
@@ -40,6 +41,9 @@
40 41
41 42 // Remove events that have sat around for this many milliseconds
42 43 private static final int DATA_EXPIRATION = 1000 * 60 * 60 * 12; // 12 hours
  44 +
  45 + // Maps each token to a singleton MPMetrics instance
  46 + private static HashMap<String, MPMetrics> mInstanceMap = new HashMap<String, MPMetrics>();
43 47
44 48 private Context mContext;
45 49
@@ -60,9 +64,10 @@
60 64
61 65 // Creates a single thread pool to perform the HTTP requests on
62 66 // Multiple requests may be queued up to prevent races.
63   - private final ExecutorService executor = Executors.newFixedThreadPool(1);
  67 + private ThreadPoolExecutor executor =
  68 + new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
64 69
65   - public MPMetrics(Context context, String token) {
  70 + private MPMetrics(Context context, String token) {
66 71 mContext = context;
67 72 mToken = token;
68 73
@@ -73,7 +78,7 @@ public MPMetrics(Context context, String token) {
73 78
74 79 mSuperProperties = new JSONObject();
75 80
76   - mDbAdapter = new MPDbAdapter(mContext);
  81 + mDbAdapter = new MPDbAdapter(mContext, mToken);
77 82 mDbAdapter.cleanupEvents(System.currentTimeMillis() - DATA_EXPIRATION);
78 83
79 84 mTimer = new Timer();
@@ -154,7 +159,7 @@ public void track(String eventName, JSONObject properties) {
154 159
155 160 int count = mDbAdapter.addEvent(dataObj);
156 161
157   - if (mTestMode || count >= BULK_UPLOAD_LIMIT) {
  162 + if (mTestMode || (count >= BULK_UPLOAD_LIMIT && executor.getQueue().isEmpty())) {
158 163 flush();
159 164 }
160 165 }
@@ -219,14 +224,11 @@ private String getDeviceId() {
219 224
220 225 @Override
221 226 public void run() {
222   - String data = mDbAdapter.generateDataString();
  227 + String[] data = mDbAdapter.generateDataString();
223 228 if (data == null) {
224 229 // Couldn't get data for whatever reason, so just return.
225 230 return;
226 231 }
227   - StringTokenizer tok = new StringTokenizer(data, ":");
228   - long timestamp = Long.parseLong(tok.nextToken());
229   - data = tok.nextToken();
230 232
231 233 // Post the data
232 234 HttpClient httpclient = new DefaultHttpClient();
@@ -235,7 +237,7 @@ public void run() {
235 237 try {
236 238 // Add your data
237 239 List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(1);
238   - nameValuePairs.add(new BasicNameValuePair("data", data));
  240 + nameValuePairs.add(new BasicNameValuePair("data", data[1]));
239 241 httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
240 242
241 243 HttpResponse response = httpclient.execute(httppost);
@@ -249,9 +251,12 @@ public void run() {
249 251 if (Global.DEBUG) {
250 252 Log.d(LOGTAG, "HttpResponse result: " + result);
251 253 }
  254 + if (!result.equals("1\n")) {
  255 + return;
  256 + }
252 257
253 258 // Success, so prune the database.
254   - mDbAdapter.cleanupEvents(timestamp);
  259 + mDbAdapter.cleanupEvents(data[0]);
255 260
256 261 // If anything went wrong, don't remove from the db so we can try again
257 262 // on the next flush.
@@ -272,4 +277,14 @@ public void run() {
272 277 }
273 278 }
274 279 }
  280 +
  281 + public static MPMetrics getInstance(Context context, String token) {
  282 + MPMetrics instance = mInstanceMap.get(token);
  283 + if (instance == null) {
  284 + instance = new MPMetrics(context.getApplicationContext(), token);
  285 + mInstanceMap.put(token, instance);
  286 + }
  287 +
  288 + return instance;
  289 + }
275 290 }

0 comments on commit 468fac1

Please sign in to comment.
Something went wrong with that request. Please try again.