1414import android .view .Window ;
1515import androidx .annotation .RequiresApi ;
1616import io .sentry .ILogger ;
17+ import io .sentry .ISentryLifecycleToken ;
1718import io .sentry .SentryLevel ;
1819import io .sentry .SentryOptions ;
1920import io .sentry .SentryUUID ;
2021import io .sentry .android .core .BuildInfoProvider ;
2122import io .sentry .android .core .ContextUtils ;
23+ import io .sentry .util .AutoClosableReentrantLock ;
2224import io .sentry .util .Objects ;
2325import java .lang .ref .WeakReference ;
2426import java .lang .reflect .Field ;
@@ -38,6 +40,8 @@ public final class SentryFrameMetricsCollector implements Application.ActivityLi
3840
3941 private final @ NotNull BuildInfoProvider buildInfoProvider ;
4042 private final @ NotNull Set <Window > trackedWindows = new CopyOnWriteArraySet <>();
43+ private final @ NotNull AutoClosableReentrantLock trackedWindowsLock =
44+ new AutoClosableReentrantLock ();
4145 private final @ NotNull ILogger logger ;
4246 private @ Nullable Handler handler ;
4347 private @ Nullable WeakReference <Window > currentWindow ;
@@ -282,23 +286,24 @@ public void stopCollection(final @Nullable String listenerId) {
282286
283287 @ SuppressLint ("NewApi" )
284288 private void stopTrackingWindow (final @ NotNull Window window ) {
285- final boolean wasTracked = trackedWindows .remove (window );
286- if (wasTracked ) {
287- new Handler (Looper .getMainLooper ())
288- .post (
289- () -> {
290- try {
291- // Re-check if we should still stop tracking this window
292- if (!trackedWindows .contains (window )) {
293- windowFrameMetricsManager .removeOnFrameMetricsAvailableListener (
294- window , frameMetricsAvailableListener );
295- }
296- } catch (Throwable e ) {
297- logger .log (
298- SentryLevel .ERROR , "Failed to remove frameMetricsAvailableListener" , e );
289+ new Handler (Looper .getMainLooper ())
290+ .post (
291+ () -> {
292+ try {
293+ // Re-check if we should still remove the listener for this window
294+ // in case trackCurrentWindow was called in the meantime
295+ final boolean shouldRemove ;
296+ try (final @ NotNull ISentryLifecycleToken ignored = trackedWindowsLock .acquire ()) {
297+ shouldRemove = trackedWindows .contains (window ) && trackedWindows .remove (window );
299298 }
300- });
301- }
299+ if (shouldRemove ) {
300+ windowFrameMetricsManager .removeOnFrameMetricsAvailableListener (
301+ window , frameMetricsAvailableListener );
302+ }
303+ } catch (Throwable e ) {
304+ logger .log (SentryLevel .ERROR , "Failed to remove frameMetricsAvailableListener" , e );
305+ }
306+ });
302307 }
303308
304309 private void setCurrentWindow (final @ NotNull Window window ) {
@@ -316,23 +321,22 @@ private void trackCurrentWindow() {
316321 return ;
317322 }
318323
319- if (trackedWindows .contains (window )) {
320- return ;
321- }
322-
323324 if (listenerMap .isEmpty ()) {
324325 return ;
325326 }
326327
327328 if (handler != null ) {
328- trackedWindows .add (window );
329329 // Ensure the addOnFrameMetricsAvailableListener is called on the main thread
330330 new Handler (Looper .getMainLooper ())
331331 .post (
332332 () -> {
333333 // Re-check if we should still track this window
334- // in case stopTrackingWindow was called in the meantime
335- if (trackedWindows .contains (window )) {
334+ // in case stopTrackingWindow was called for the same Window in the meantime
335+ final boolean shouldAdd ;
336+ try (final @ NotNull ISentryLifecycleToken ignored = trackedWindowsLock .acquire ()) {
337+ shouldAdd = !trackedWindows .contains (window ) && trackedWindows .add (window );
338+ }
339+ if (shouldAdd ) {
336340 try {
337341 windowFrameMetricsManager .addOnFrameMetricsAvailableListener (
338342 window , frameMetricsAvailableListener , handler );
0 commit comments