Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

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

  • Loading branch information...
commit 468fac14b3a1ec58407ee48b28202e0fa08266e5 1 parent 00bd9a2
Anlu Wang authored
View
BIN  MPMetrics.jar
Binary file not shown
View
BIN  MPMetrics_v1.1.jar
Binary file not shown
View
BIN  bin/classes/com/mixpanel/android/mpmetrics/MPDbAdapter$MPDatabaseHelper.class
Binary file not shown
View
BIN  bin/classes/com/mixpanel/android/mpmetrics/MPDbAdapter.class
Binary file not shown
View
BIN  bin/classes/com/mixpanel/android/mpmetrics/MPMetrics$1.class
Binary file not shown
View
BIN  bin/classes/com/mixpanel/android/mpmetrics/MPMetrics$TrackTask.class
Binary file not shown
View
BIN  bin/classes/com/mixpanel/android/mpmetrics/MPMetrics.class
Binary file not shown
View
BIN  bin/mixpanel-android.jar
Binary file not shown
View
2  demo/Hello Mixpanel/.classpath
@@ -3,7 +3,7 @@
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
- <classpathentry kind="lib" path="lib/MPMetrics.jar"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+ <classpathentry kind="lib" path="/Users/anlu/Desktop/mixpanel-android/MPMetrics_v1.1.jar"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>
View
BIN  demo/Hello Mixpanel/bin/Hello Mixpanel.apk
Binary file not shown
View
BIN  demo/Hello Mixpanel/bin/classes.dex
Binary file not shown
View
BIN  demo/Hello Mixpanel/bin/classes/com/mixpanel/android/hellomixpanel/HelloMixpanel$1.class
Binary file not shown
View
BIN  demo/Hello Mixpanel/bin/classes/com/mixpanel/android/hellomixpanel/HelloMixpanel.class
Binary file not shown
View
2  demo/Hello Mixpanel/src/com/mixpanel/android/hellomixpanel/HelloMixpanel.java
@@ -22,7 +22,7 @@ public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
- mMPMetrics = new MPMetrics(this, "c35a4b5163ee2c097de447765f691544");
+ mMPMetrics = MPMetrics.getInstance(this, "c35a4b5163ee2c097de447765f691544");
mButton = (Button) findViewById(R.id.button);
mButton.setOnClickListener(new View.OnClickListener() {
View
70 src/com/mixpanel/android/mpmetrics/MPDbAdapter.java
@@ -29,20 +29,27 @@
private static final String DATABASE_NAME = "mixpanel";
private static final String DATABASE_TABLE = "events";
- private static final int DATABASE_VERSION = 2;
+ private static final int DATABASE_VERSION = 3;
public static final String KEY_DATA = "data";
public static final String KEY_CREATED_AT = "created_at";
+ public static final String KEY_TOKEN = "token";
private static final String DATABASE_CREATE =
"CREATE TABLE " + DATABASE_TABLE + " (_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
KEY_DATA + " STRING NOT NULL," +
- KEY_CREATED_AT + " INTEGER NOT NULL);";
- private static final String DATABASE_INDEX =
+ KEY_CREATED_AT + " INTEGER NOT NULL," +
+ KEY_TOKEN + " STRING NOT NULL);";
+ private static final String TIME_INDEX =
"CREATE INDEX IF NOT EXISTS time_idx ON " + DATABASE_TABLE +
" (" + KEY_CREATED_AT + ");";
+ private static final String TOKEN_INDEX =
+ "CREATE INDEX IF NOT EXISTS token_idx ON " + DATABASE_TABLE +
+ " (" + KEY_TOKEN + ");";
private MPDatabaseHelper mDb;
+
+ private String mToken;
private static class MPDatabaseHelper extends SQLiteOpenHelper {
MPDatabaseHelper(Context context) {
@@ -52,7 +59,8 @@
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE);
- db.execSQL(DATABASE_INDEX);
+ db.execSQL(TIME_INDEX);
+ db.execSQL(TOKEN_INDEX);
}
@Override
@@ -63,12 +71,14 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE " + DATABASE_TABLE);
db.execSQL(DATABASE_CREATE);
- db.execSQL(DATABASE_INDEX);
+ db.execSQL(TIME_INDEX);
+ db.execSQL(TOKEN_INDEX);
}
}
- public MPDbAdapter(Context context) {
+ public MPDbAdapter(Context context, String token) {
mDb = new MPDatabaseHelper(context);
+ mToken = token;
}
/**
@@ -89,10 +99,12 @@ public int addEvent(JSONObject j) {
ContentValues cv = new ContentValues();
cv.put(KEY_DATA, j.toString());
cv.put(KEY_CREATED_AT, System.currentTimeMillis());
+ cv.put(KEY_TOKEN, mToken);
db.insert(DATABASE_TABLE, null, cv);
- c = db.rawQuery("SELECT * FROM " + DATABASE_TABLE, null);
- count = c.getCount();
+ c = db.rawQuery("SELECT COUNT(*) FROM " + DATABASE_TABLE + " WHERE token = '" + mToken + "'", null);
+ c.moveToFirst();
+ count = c.getInt(0);
} catch (SQLiteException e) {
Log.e(LOGTAG, "addEvent", e);
} finally {
@@ -106,16 +118,36 @@ public int addEvent(JSONObject j) {
}
/**
+ * Removes events with an _id <= last_id
+ * @param last_id the last id to delete
+ */
+ public void cleanupEvents(String last_id) {
+ synchronized (this) {
+ if (Global.DEBUG) { Log.d(LOGTAG, "cleanupEvents _id " + last_id); }
+
+ try {
+ SQLiteDatabase db = mDb.getWritableDatabase();
+ db.delete(DATABASE_TABLE, "_id <= " + last_id + " AND token = '" + mToken + "'", null);
+ } catch (SQLiteException e) {
+ // If there's an exception, oh well, let the events persist
+ Log.e(LOGTAG, "cleanupEvents", e);
+ } finally {
+ mDb.close();
+ }
+ }
+ }
+
+ /**
* Removes events before time.
* @param time the unix epoch in milliseconds to remove events before
*/
public void cleanupEvents(long time) {
synchronized (this) {
- if (Global.DEBUG) { Log.d(LOGTAG, "cleanupEvent"); }
+ if (Global.DEBUG) { Log.d(LOGTAG, "cleanupEvents time " + time); }
try {
SQLiteDatabase db = mDb.getWritableDatabase();
- db.delete(DATABASE_TABLE, KEY_CREATED_AT + " <= " + time, null);
+ db.delete(DATABASE_TABLE, KEY_CREATED_AT + " <= " + time + " AND token = '" + mToken + "'", null);
} catch (SQLiteException e) {
// If there's an exception, oh well, let the events persist
Log.e(LOGTAG, "cleanupEvents", e);
@@ -135,24 +167,25 @@ public void cleanupEvents(long time) {
* add it to the string to because we have the data here, and so we don't have to
* call getReadableDatabase() multiple times.
*
- * @return the data string representing the events, or null if none could be
- * successfully retrieved.
+ * @return String array containing the maximum ID and the data string
+ * representing the events, or null if none could be successfully retrieved.
*/
- public String generateDataString() {
+ public String[] generateDataString() {
synchronized (this) {
Cursor c = null;
String data = null;
- String timestamp = null;
+ String last_id = null;
try {
SQLiteDatabase db = mDb.getReadableDatabase();
c = db.rawQuery("SELECT * FROM " + DATABASE_TABLE +
- " ORDER BY " + KEY_CREATED_AT + " ASC", null);
+ " WHERE token = '" + mToken + "'" +
+ " ORDER BY " + KEY_CREATED_AT + " ASC LIMIT 50", null);
JSONArray arr = new JSONArray();
while (c.moveToNext()) {
if (c.isLast()) {
- timestamp = c.getString(c.getColumnIndex(KEY_CREATED_AT));
+ last_id = c.getString(c.getColumnIndex("_id"));
}
try {
JSONObject j = new JSONObject(c.getString(c.getColumnIndex(KEY_DATA)));
@@ -174,8 +207,9 @@ public String generateDataString() {
}
}
- if (timestamp != null && data != null) {
- return timestamp + ":" + data;
+ if (last_id != null && data != null) {
+ String[] ret = {last_id, data};
+ return ret;
}
return null;
}
View
41 src/com/mixpanel/android/mpmetrics/MPMetrics.java
@@ -2,13 +2,14 @@
import java.io.IOException;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
-import java.util.StringTokenizer;
import java.util.Timer;
import java.util.TimerTask;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
@@ -40,6 +41,9 @@
// Remove events that have sat around for this many milliseconds
private static final int DATA_EXPIRATION = 1000 * 60 * 60 * 12; // 12 hours
+
+ // Maps each token to a singleton MPMetrics instance
+ private static HashMap<String, MPMetrics> mInstanceMap = new HashMap<String, MPMetrics>();
private Context mContext;
@@ -60,9 +64,10 @@
// Creates a single thread pool to perform the HTTP requests on
// Multiple requests may be queued up to prevent races.
- private final ExecutorService executor = Executors.newFixedThreadPool(1);
+ private ThreadPoolExecutor executor =
+ new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
- public MPMetrics(Context context, String token) {
+ private MPMetrics(Context context, String token) {
mContext = context;
mToken = token;
@@ -73,7 +78,7 @@ public MPMetrics(Context context, String token) {
mSuperProperties = new JSONObject();
- mDbAdapter = new MPDbAdapter(mContext);
+ mDbAdapter = new MPDbAdapter(mContext, mToken);
mDbAdapter.cleanupEvents(System.currentTimeMillis() - DATA_EXPIRATION);
mTimer = new Timer();
@@ -154,7 +159,7 @@ public void track(String eventName, JSONObject properties) {
int count = mDbAdapter.addEvent(dataObj);
- if (mTestMode || count >= BULK_UPLOAD_LIMIT) {
+ if (mTestMode || (count >= BULK_UPLOAD_LIMIT && executor.getQueue().isEmpty())) {
flush();
}
}
@@ -219,14 +224,11 @@ private String getDeviceId() {
@Override
public void run() {
- String data = mDbAdapter.generateDataString();
+ String[] data = mDbAdapter.generateDataString();
if (data == null) {
// Couldn't get data for whatever reason, so just return.
return;
}
- StringTokenizer tok = new StringTokenizer(data, ":");
- long timestamp = Long.parseLong(tok.nextToken());
- data = tok.nextToken();
// Post the data
HttpClient httpclient = new DefaultHttpClient();
@@ -235,7 +237,7 @@ public void run() {
try {
// Add your data
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(1);
- nameValuePairs.add(new BasicNameValuePair("data", data));
+ nameValuePairs.add(new BasicNameValuePair("data", data[1]));
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpclient.execute(httppost);
@@ -249,9 +251,12 @@ public void run() {
if (Global.DEBUG) {
Log.d(LOGTAG, "HttpResponse result: " + result);
}
+ if (!result.equals("1\n")) {
+ return;
+ }
// Success, so prune the database.
- mDbAdapter.cleanupEvents(timestamp);
+ mDbAdapter.cleanupEvents(data[0]);
// If anything went wrong, don't remove from the db so we can try again
// on the next flush.
@@ -272,4 +277,14 @@ public void run() {
}
}
}
+
+ public static MPMetrics getInstance(Context context, String token) {
+ MPMetrics instance = mInstanceMap.get(token);
+ if (instance == null) {
+ instance = new MPMetrics(context.getApplicationContext(), token);
+ mInstanceMap.put(token, instance);
+ }
+
+ return instance;
+ }
}
Please sign in to comment.
Something went wrong with that request. Please try again.