Skip to content

Commit 987e47e

Browse files
Preload time events in the background to avoid main thread disk reads (#853)
* Preload time events in the background to avoid main thread disk reads * add mTimeEventsCacheLoading flag to prevent multiple background threads * declare mTimeEventsCacheLoading * check mTimeEventsCacheLoading in preloadTimeEventsAsync too, plus apply Android Studio suggestions
1 parent 4ee0a52 commit 987e47e

File tree

1 file changed

+104
-62
lines changed

1 file changed

+104
-62
lines changed

src/main/java/com/mixpanel/android/mpmetrics/PersistentIdentity.java

Lines changed: 104 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.mixpanel.android.mpmetrics;
22

3-
import java.util.Date;
43
import java.util.HashMap;
54
import java.util.Iterator;
65
import java.util.Map;
@@ -14,12 +13,13 @@
1413
import android.annotation.SuppressLint;
1514
import android.content.Context;
1615
import android.content.SharedPreferences;
16+
import android.os.Looper;
1717

1818
import com.mixpanel.android.util.MPLog;
1919

2020
// In order to use writeEdits, we have to suppress the linter's check for commit()/apply()
2121
@SuppressLint("CommitPrefEdits")
22-
/* package */ class PersistentIdentity {
22+
/* package */ class PersistentIdentity {
2323
// Should ONLY be called from an OnPrefsLoadedListener (since it should NEVER be called concurrently)
2424
public static String getPeopleDistinctId(SharedPreferences storedPreferences) {
2525
return storedPreferences.getString("people_distinct_id", null);
@@ -46,15 +46,15 @@ public PersistentIdentity(Future<SharedPreferences> referrerPreferences, Future<
4646
mSuperPropertiesCache = null;
4747
mReferrerPropertiesCache = null;
4848
mIdentitiesLoaded = false;
49-
mReferrerChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
50-
@Override
51-
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
52-
synchronized (sReferrerPrefsLock) {
53-
readReferrerProperties();
54-
sReferrerPrefsDirty = false;
55-
}
49+
mReferrerChangeListener = (sharedPreferences, key) -> {
50+
synchronized (sReferrerPrefsLock) {
51+
readReferrerProperties();
52+
sReferrerPrefsDirty = false;
5653
}
5754
};
55+
56+
// Preload time events in the background to avoid main thread disk reads
57+
preloadTimeEventsAsync();
5858
}
5959

6060
// Super properties
@@ -265,9 +265,7 @@ public synchronized void clearPreferences() {
265265
writeEdits(prefsEdit);
266266
readSuperProperties();
267267
readIdentities();
268-
} catch (final ExecutionException e) {
269-
throw new RuntimeException(e.getCause());
270-
} catch (final InterruptedException e) {
268+
} catch (final ExecutionException | InterruptedException e) {
271269
throw new RuntimeException(e.getCause());
272270
}
273271
}
@@ -278,30 +276,84 @@ public void clearTimedEvents() {
278276
final SharedPreferences.Editor editor = prefs.edit();
279277
editor.clear();
280278
writeEdits(editor);
279+
280+
// Clear cache if initialized
281+
synchronized (mTimeEventsCacheLock) {
282+
if (mTimeEventsCache != null) {
283+
mTimeEventsCache.clear();
284+
}
285+
}
281286
} catch (InterruptedException e) {
282-
e.printStackTrace();
287+
MPLog.e(LOGTAG, "Failed to clear time events", e);
283288
} catch (ExecutionException e) {
284-
e.printStackTrace();
289+
MPLog.e(LOGTAG, "Failed to clear time events", e.getCause());
285290
}
286291
}
287292

288293
public Map<String, Long> getTimeEvents() {
289-
Map<String, Long> timeEvents = new HashMap<>();
294+
// First check if cache is already loaded
295+
synchronized (mTimeEventsCacheLock) {
296+
if (mTimeEventsCache != null) {
297+
return new HashMap<>(mTimeEventsCache);
298+
}
290299

291-
try {
292-
final SharedPreferences prefs = mTimeEventsPreferences.get();
300+
// Detect if we're on the main thread
301+
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
302+
// Running on main thread - return empty map and load asynchronously
303+
final Map<String, Long> emptyMap = new HashMap<>();
304+
305+
// Only start a new thread if we're not already loading
306+
if (!mTimeEventsCacheLoading) {
307+
mTimeEventsCacheLoading = true;
308+
new Thread(this::loadTimeEventsCache).start();
309+
}
293310

294-
Map<String, ?> allEntries = prefs.getAll();
295-
for (Map.Entry<String, ?> entry : allEntries.entrySet()) {
296-
timeEvents.put(entry.getKey(), Long.valueOf(entry.getValue().toString()));
311+
return emptyMap;
312+
} else {
313+
// Not on main thread - safe to load synchronously
314+
return loadTimeEventsCache();
297315
}
298-
} catch (InterruptedException e) {
299-
e.printStackTrace();
300-
} catch (ExecutionException e) {
301-
e.printStackTrace();
302316
}
317+
}
318+
319+
// Helper method to load time events
320+
private Map<String, Long> loadTimeEventsCache() {
321+
synchronized (mTimeEventsCacheLock) {
322+
if (mTimeEventsCache != null) {
323+
return new HashMap<>(mTimeEventsCache);
324+
}
325+
326+
mTimeEventsCache = new HashMap<>();
327+
328+
try {
329+
final SharedPreferences prefs = mTimeEventsPreferences.get();
330+
Map<String, ?> allEntries = prefs.getAll();
331+
for (Map.Entry<String, ?> entry : allEntries.entrySet()) {
332+
mTimeEventsCache.put(entry.getKey(), Long.valueOf(entry.getValue().toString()));
333+
}
334+
} catch (InterruptedException e) {
335+
MPLog.e(LOGTAG, "Failed to load time events", e);
336+
} catch (ExecutionException e) {
337+
MPLog.e(LOGTAG, "Failed to load time events", e.getCause());
338+
} finally {
339+
// Reset the loading flag when done
340+
mTimeEventsCacheLoading = false;
341+
}
303342

304-
return timeEvents;
343+
return new HashMap<>(mTimeEventsCache);
344+
}
345+
}
346+
347+
// Method to explicitly preload the cache
348+
public void preloadTimeEventsAsync() {
349+
synchronized (mTimeEventsCacheLock) {
350+
if (mTimeEventsCache == null) {
351+
if (!mTimeEventsCacheLoading) {
352+
mTimeEventsCacheLoading = true;
353+
new Thread(this::loadTimeEventsCache).start();
354+
}
355+
}
356+
}
305357
}
306358

307359
// access is synchronized outside (mEventTimings)
@@ -311,10 +363,17 @@ public void removeTimedEvent(String timeEventName) {
311363
final SharedPreferences.Editor editor = prefs.edit();
312364
editor.remove(timeEventName);
313365
writeEdits(editor);
366+
367+
// Update cache if initialized
368+
synchronized (mTimeEventsCacheLock) {
369+
if (mTimeEventsCache != null) {
370+
mTimeEventsCache.remove(timeEventName);
371+
}
372+
}
314373
} catch (InterruptedException e) {
315-
e.printStackTrace();
374+
MPLog.e(LOGTAG, "Failed to remove time event", e);
316375
} catch (ExecutionException e) {
317-
e.printStackTrace();
376+
MPLog.e(LOGTAG, "Failed to remove time event", e.getCause());
318377
}
319378
}
320379

@@ -325,35 +384,17 @@ public void addTimeEvent(String timeEventName, Long timeEventTimestamp) {
325384
final SharedPreferences.Editor editor = prefs.edit();
326385
editor.putLong(timeEventName, timeEventTimestamp);
327386
writeEdits(editor);
328-
} catch (InterruptedException e) {
329-
e.printStackTrace();
330-
} catch (ExecutionException e) {
331-
e.printStackTrace();
332-
}
333-
}
334-
335-
public synchronized boolean isFirstIntegration(String token) {
336-
boolean firstLaunch = false;
337-
try {
338-
SharedPreferences prefs = mMixpanelPreferences.get();
339-
firstLaunch = prefs.getBoolean(token, false);
340-
} catch (final ExecutionException e) {
341-
MPLog.e(LOGTAG, "Couldn't read internal Mixpanel shared preferences.", e.getCause());
342-
} catch (final InterruptedException e) {
343-
MPLog.e(LOGTAG, "Couldn't read internal Mixpanel from shared preferences.", e);
344-
}
345-
return firstLaunch;
346-
}
347387

348-
public synchronized void setIsIntegrated(String token) {
349-
try {
350-
SharedPreferences.Editor mixpanelEditor = mMixpanelPreferences.get().edit();
351-
mixpanelEditor.putBoolean(token, true);
352-
writeEdits(mixpanelEditor);
353-
} catch (ExecutionException e) {
354-
MPLog.e(LOGTAG, "Couldn't write internal Mixpanel shared preferences.", e.getCause());
388+
// Update cache if initialized
389+
synchronized (mTimeEventsCacheLock) {
390+
if (mTimeEventsCache != null) {
391+
mTimeEventsCache.put(timeEventName, timeEventTimestamp);
392+
}
393+
}
355394
} catch (InterruptedException e) {
356-
MPLog.e(LOGTAG, "Couldn't write internal Mixpanel from shared preferences.", e);
395+
MPLog.e(LOGTAG, "Failed to add time event", e);
396+
} catch (ExecutionException e) {
397+
MPLog.e(LOGTAG, "Failed to add time event", e.getCause());
357398
}
358399
}
359400

@@ -375,7 +416,7 @@ public synchronized boolean isNewVersion(String versionCode) {
375416
}
376417
}
377418

378-
if (sPreviousVersionCode.intValue() < version.intValue()) {
419+
if (sPreviousVersionCode < version) {
379420
SharedPreferences.Editor mixpanelPreferencesEditor = mMixpanelPreferences.get().edit();
380421
mixpanelPreferencesEditor.putInt("latest_version_code", version);
381422
writeEdits(mixpanelPreferencesEditor);
@@ -403,9 +444,7 @@ public synchronized boolean isFirstLaunch(boolean dbExists, String token) {
403444
setHasLaunched(token);
404445
}
405446
}
406-
} catch (ExecutionException e) {
407-
sIsFirstAppLaunch = false;
408-
} catch (InterruptedException e) {
447+
} catch (ExecutionException | InterruptedException e) {
409448
sIsFirstAppLaunch = false;
410449
}
411450
}
@@ -474,7 +513,7 @@ private void readSuperProperties() {
474513

475514
// All access should be synchronized on this
476515
private void readReferrerProperties() {
477-
mReferrerPropertiesCache = new HashMap<String, String>();
516+
mReferrerPropertiesCache = new HashMap<>();
478517

479518
try {
480519
final SharedPreferences referrerPrefs = mLoadReferrerPreferences.get();
@@ -579,7 +618,7 @@ protected void removeOptOutFlag(String token) {
579618
try {
580619
final SharedPreferences prefs = mMixpanelPreferences.get();
581620
final SharedPreferences.Editor prefsEditor = prefs.edit();
582-
prefsEditor.clear();
621+
prefsEditor.remove("opt_out_" + token);
583622
writeEdits(prefsEditor);
584623
} catch (final ExecutionException e) {
585624
MPLog.e(LOGTAG, "Can't remove opt-out shared preferences.", e.getCause());
@@ -640,9 +679,12 @@ private static void writeEdits(final SharedPreferences.Editor editor) {
640679
private static Integer sPreviousVersionCode;
641680
private static Boolean sIsFirstAppLaunch;
642681

682+
// Time events caching
683+
private Map<String, Long> mTimeEventsCache = null;
684+
private final Object mTimeEventsCacheLock = new Object();
685+
private boolean mTimeEventsCacheLoading = false;
686+
643687
private static boolean sReferrerPrefsDirty = true;
644688
private static final Object sReferrerPrefsLock = new Object();
645-
private static final String DELIMITER = ",";
646689
private static final String LOGTAG = "MixpanelAPI.PIdentity";
647-
648690
}

0 commit comments

Comments
 (0)