11package com .mixpanel .android .mpmetrics ;
22
3- import java .util .Date ;
43import java .util .HashMap ;
54import java .util .Iterator ;
65import java .util .Map ;
1413import android .annotation .SuppressLint ;
1514import android .content .Context ;
1615import android .content .SharedPreferences ;
16+ import android .os .Looper ;
1717
1818import 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