diff --git a/CHANGELOG.md b/CHANGELOG.md index 43fc5ccc908..2f306b28c37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - [ANR] Removed AndroidTransactionProfiler lock ([#4817](https://github.com/getsentry/sentry-java/pull/4817)) +### Improvements + +- [ANR] Defer some class availability checks ([#4825](https://github.com/getsentry/sentry-java/pull/4825)) + ### Dependencies - Bump Native SDK from v0.11.2 to v0.11.3 ([#4810](https://github.com/getsentry/sentry-java/pull/4810)) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityFramesTracker.java b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityFramesTracker.java index ade8fdd37c7..3895819fc94 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityFramesTracker.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityFramesTracker.java @@ -10,6 +10,7 @@ import io.sentry.protocol.MeasurementValue; import io.sentry.protocol.SentryId; import io.sentry.util.AutoClosableReentrantLock; +import io.sentry.util.LazyEvaluator; import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; @@ -30,7 +31,7 @@ */ public final class ActivityFramesTracker { - private @Nullable FrameMetricsAggregator frameMetricsAggregator = null; + private @NotNull LazyEvaluator frameMetricsAggregator; private @NotNull final SentryAndroidOptions options; private final @NotNull Map> @@ -41,17 +42,18 @@ public final class ActivityFramesTracker { private final @NotNull MainLooperHandler handler; protected @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); + private final @NotNull LazyEvaluator androidXAvailable; + public ActivityFramesTracker( final @NotNull io.sentry.util.LoadClass loadClass, final @NotNull SentryAndroidOptions options, final @NotNull MainLooperHandler handler) { - final boolean androidXAvailable = - loadClass.isClassAvailable("androidx.core.app.FrameMetricsAggregator", options.getLogger()); + androidXAvailable = + loadClass.isClassAvailableLazy( + "androidx.core.app.FrameMetricsAggregator", options.getLogger()); + frameMetricsAggregator = new LazyEvaluator<>(() -> new FrameMetricsAggregator()); - if (androidXAvailable) { - frameMetricsAggregator = new FrameMetricsAggregator(); - } this.options = options; this.handler = handler; } @@ -67,15 +69,15 @@ public ActivityFramesTracker( final @NotNull io.sentry.util.LoadClass loadClass, final @NotNull SentryAndroidOptions options, final @NotNull MainLooperHandler handler, - final @Nullable FrameMetricsAggregator frameMetricsAggregator) { + final @NotNull FrameMetricsAggregator frameMetricsAggregator) { this(loadClass, options, handler); - this.frameMetricsAggregator = frameMetricsAggregator; + this.frameMetricsAggregator = new LazyEvaluator<>(() -> frameMetricsAggregator); } @VisibleForTesting public boolean isFrameMetricsAggregatorAvailable() { - return frameMetricsAggregator != null + return androidXAvailable.getValue() && options.isEnableFramesTracking() && !options.isEnablePerformanceV2(); } @@ -87,7 +89,8 @@ public void addActivity(final @NotNull Activity activity) { return; } - runSafelyOnUiThread(() -> frameMetricsAggregator.add(activity), "FrameMetricsAggregator.add"); + runSafelyOnUiThread( + () -> frameMetricsAggregator.getValue().add(activity), "FrameMetricsAggregator.add"); snapshotFrameCountsAtStart(activity); } } @@ -104,11 +107,11 @@ private void snapshotFrameCountsAtStart(final @NotNull Activity activity) { return null; } - if (frameMetricsAggregator == null) { + if (!androidXAvailable.getValue()) { return null; } - final @Nullable SparseIntArray[] framesRates = frameMetricsAggregator.getMetrics(); + final @Nullable SparseIntArray[] framesRates = frameMetricsAggregator.getValue().getMetrics(); int totalFrames = 0; int slowFrames = 0; @@ -153,7 +156,7 @@ public void setMetrics(final @NotNull Activity activity, final @NotNull SentryId // there was no // Observers, See // https://android.googlesource.com/platform/frameworks/base/+/140ff5ea8e2d99edc3fbe63a43239e459334c76b - runSafelyOnUiThread(() -> frameMetricsAggregator.remove(activity), null); + runSafelyOnUiThread(() -> frameMetricsAggregator.getValue().remove(activity), null); final @Nullable FrameCounts frameCounts = diffFrameCountsAtEnd(activity); @@ -215,8 +218,9 @@ public void setMetrics(final @NotNull Activity activity, final @NotNull SentryId public void stop() { try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { if (isFrameMetricsAggregatorAvailable()) { - runSafelyOnUiThread(() -> frameMetricsAggregator.stop(), "FrameMetricsAggregator.stop"); - frameMetricsAggregator.reset(); + runSafelyOnUiThread( + () -> frameMetricsAggregator.getValue().stop(), "FrameMetricsAggregator.stop"); + frameMetricsAggregator.getValue().reset(); } activityMeasurements.clear(); } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java index 7f98b9fbb14..2b7a32820c3 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java @@ -200,8 +200,8 @@ static void initializeIntegrationsAndProcessors( options.setVersionDetector(new DefaultVersionDetector(options)); } - final boolean isAndroidXScrollViewAvailable = - loadClass.isClassAvailable("androidx.core.view.ScrollingView", options); + final @NotNull LazyEvaluator isAndroidXScrollViewAvailable = + loadClass.isClassAvailableLazy("androidx.core.view.ScrollingView", options); final boolean isComposeUpstreamAvailable = loadClass.isClassAvailable(COMPOSE_CLASS_NAME, options); diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/AndroidViewGestureTargetLocator.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/AndroidViewGestureTargetLocator.java index f271c3da9e3..c85fb80dc35 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/AndroidViewGestureTargetLocator.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/AndroidViewGestureTargetLocator.java @@ -8,6 +8,7 @@ import io.sentry.android.core.internal.util.ClassUtil; import io.sentry.internal.gestures.GestureTargetLocator; import io.sentry.internal.gestures.UiElement; +import io.sentry.util.LazyEvaluator; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -17,9 +18,10 @@ public final class AndroidViewGestureTargetLocator implements GestureTargetLocat private static final String ORIGIN = "old_view_system"; - private final boolean isAndroidXAvailable; + private final @NotNull LazyEvaluator isAndroidXAvailable; - public AndroidViewGestureTargetLocator(final boolean isAndroidXAvailable) { + public AndroidViewGestureTargetLocator( + final @NotNull LazyEvaluator isAndroidXAvailable) { this.isAndroidXAvailable = isAndroidXAvailable; } @@ -33,7 +35,7 @@ public AndroidViewGestureTargetLocator(final boolean isAndroidXAvailable) { if (targetType == UiElement.Type.CLICKABLE && isViewTappable(view)) { return createUiElement(view); } else if (targetType == UiElement.Type.SCROLLABLE - && isViewScrollable(view, isAndroidXAvailable)) { + && isViewScrollable(view, isAndroidXAvailable.getValue())) { return createUiElement(view); } return null; diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityFramesTrackerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityFramesTrackerTest.kt index 8bfeb1c726d..55af0d71b0e 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityFramesTrackerTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityFramesTrackerTest.kt @@ -5,8 +5,10 @@ import android.util.SparseIntArray import androidx.core.app.FrameMetricsAggregator import androidx.test.ext.junit.runners.AndroidJUnit4 import io.sentry.ILogger +import io.sentry.SentryOptions import io.sentry.protocol.MeasurementValue import io.sentry.protocol.SentryId +import io.sentry.util.LazyEvaluator import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -34,12 +36,14 @@ class ActivityFramesTrackerTest { options.isEnablePerformanceV2 = false } - fun getSut(mockAggregator: Boolean = true): ActivityFramesTracker = - if (mockAggregator) { - ActivityFramesTracker(loadClass, options, handler, aggregator) - } else { - ActivityFramesTracker(loadClass, options, handler) - } + fun getSut(isAndroidxAvailable: Boolean = true): ActivityFramesTracker { + whenever(loadClass.isClassAvailableLazy(any(), any())) + .thenReturn(LazyEvaluator { isAndroidxAvailable }) + whenever(loadClass.isClassAvailableLazy(any(), any())) + .thenReturn(LazyEvaluator { isAndroidxAvailable }) + + return ActivityFramesTracker(loadClass, options, handler, aggregator) + } } private val fixture = Fixture() @@ -340,7 +344,6 @@ class ActivityFramesTrackerTest { @Test fun `addActivity does not throw if no AndroidX`() { - whenever(fixture.loadClass.isClassAvailable(any(), any())).thenReturn(false) val sut = fixture.getSut(false) sut.addActivity(fixture.activity) @@ -348,7 +351,6 @@ class ActivityFramesTrackerTest { @Test fun `setMetrics does not throw if no AndroidX`() { - whenever(fixture.loadClass.isClassAvailable(any(), any())).thenReturn(false) val sut = fixture.getSut(false) sut.setMetrics(fixture.activity, fixture.sentryId) @@ -356,7 +358,6 @@ class ActivityFramesTrackerTest { @Test fun `addActivity and setMetrics combined do not throw if no AndroidX`() { - whenever(fixture.loadClass.isClassAvailable(any(), any())).thenReturn(false) val sut = fixture.getSut(false) sut.addActivity(fixture.activity) @@ -373,7 +374,6 @@ class ActivityFramesTrackerTest { @Test fun `stop does not throw if no AndroidX`() { - whenever(fixture.loadClass.isClassAvailable(any(), any())).thenReturn(false) val sut = fixture.getSut(false) sut.stop() @@ -390,9 +390,13 @@ class ActivityFramesTrackerTest { @Test fun `takeMetrics returns null if no AndroidX`() { - whenever(fixture.loadClass.isClassAvailable(any(), any())).thenReturn(false) val sut = fixture.getSut(false) + whenever(fixture.aggregator.metrics).thenReturn(emptyArray(), getArray()) + + sut.addActivity(fixture.activity) + sut.setMetrics(fixture.activity, fixture.sentryId) + assertNull(sut.takeMetrics(fixture.sentryId)) } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerClickTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerClickTest.kt index efe651e4389..81950647fa9 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerClickTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerClickTest.kt @@ -17,6 +17,7 @@ import io.sentry.Scope.IWithPropagationContext import io.sentry.ScopeCallback import io.sentry.SentryLevel.INFO import io.sentry.android.core.SentryAndroidOptions +import io.sentry.util.LazyEvaluator import kotlin.test.Test import kotlin.test.assertEquals import org.mockito.kotlin.any @@ -38,7 +39,7 @@ class SentryGestureListenerClickTest { SentryAndroidOptions().apply { isEnableUserInteractionBreadcrumbs = true isEnableUserInteractionTracing = true - gestureTargetLocators = listOf(AndroidViewGestureTargetLocator(true)) + gestureTargetLocators = listOf(AndroidViewGestureTargetLocator(LazyEvaluator { true })) dsn = "https://key@sentry.io/proj" } val scopes = mock() diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerScrollTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerScrollTest.kt index 3dd1f726d7b..633bb2fdb86 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerScrollTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerScrollTest.kt @@ -20,6 +20,7 @@ import io.sentry.ScopeCallback import io.sentry.SentryLevel import io.sentry.SentryLevel.INFO import io.sentry.android.core.SentryAndroidOptions +import io.sentry.util.LazyEvaluator import kotlin.test.Test import kotlin.test.assertEquals import org.mockito.kotlin.any @@ -46,7 +47,7 @@ class SentryGestureListenerScrollTest { dsn = "https://key@sentry.io/proj" isEnableUserInteractionBreadcrumbs = true isEnableUserInteractionTracing = true - gestureTargetLocators = listOf(AndroidViewGestureTargetLocator(true)) + gestureTargetLocators = listOf(AndroidViewGestureTargetLocator(LazyEvaluator { true })) } val scopes = mock() val scope = mock() diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerTracingTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerTracingTest.kt index 3f8ba2d3003..fe994f4a828 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerTracingTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerTracingTest.kt @@ -23,6 +23,7 @@ import io.sentry.TransactionOptions import io.sentry.android.core.SentryAndroidOptions import io.sentry.protocol.SentryId import io.sentry.protocol.TransactionNameSource +import io.sentry.util.LazyEvaluator import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotEquals @@ -65,7 +66,8 @@ class SentryGestureListenerTracingTest { options.tracesSampleRate = tracesSampleRate options.isEnableUserInteractionTracing = isEnableUserInteractionTracing options.isEnableUserInteractionBreadcrumbs = true - options.gestureTargetLocators = listOf(AndroidViewGestureTargetLocator(true)) + options.gestureTargetLocators = + listOf(AndroidViewGestureTargetLocator(LazyEvaluator { true })) options.isEnableAutoTraceIdGeneration = isEnableAutoTraceIdGeneration whenever(scopes.options).thenReturn(options) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 2b05a5b0d01..2211f053912 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -7086,6 +7086,8 @@ public class io/sentry/util/LoadClass { public fun ()V public fun isClassAvailable (Ljava/lang/String;Lio/sentry/ILogger;)Z public fun isClassAvailable (Ljava/lang/String;Lio/sentry/SentryOptions;)Z + public fun isClassAvailableLazy (Ljava/lang/String;Lio/sentry/ILogger;)Lio/sentry/util/LazyEvaluator; + public fun isClassAvailableLazy (Ljava/lang/String;Lio/sentry/SentryOptions;)Lio/sentry/util/LazyEvaluator; public fun loadClass (Ljava/lang/String;Lio/sentry/ILogger;)Ljava/lang/Class; } diff --git a/sentry/src/main/java/io/sentry/util/LoadClass.java b/sentry/src/main/java/io/sentry/util/LoadClass.java index a48225abe8c..1946ce8381f 100644 --- a/sentry/src/main/java/io/sentry/util/LoadClass.java +++ b/sentry/src/main/java/io/sentry/util/LoadClass.java @@ -45,4 +45,14 @@ public boolean isClassAvailable( final @NotNull String clazz, final @Nullable SentryOptions options) { return isClassAvailable(clazz, options != null ? options.getLogger() : null); } + + public LazyEvaluator isClassAvailableLazy( + final @NotNull String clazz, final @Nullable ILogger logger) { + return new LazyEvaluator<>(() -> isClassAvailable(clazz, logger)); + } + + public LazyEvaluator isClassAvailableLazy( + final @NotNull String clazz, final @Nullable SentryOptions options) { + return new LazyEvaluator<>(() -> isClassAvailable(clazz, options)); + } }