Skip to content

Commit 0f44edd

Browse files
committed
added lazy class available check to LoadClass
lazy checking available classes in: * ActivityFramesTracker * AndroidViewGestureTargetLocator * UserInteractionIntegration
1 parent d5a29b6 commit 0f44edd

File tree

11 files changed

+72
-44
lines changed

11 files changed

+72
-44
lines changed

sentry-android-core/src/main/java/io/sentry/android/core/ActivityFramesTracker.java

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import io.sentry.protocol.MeasurementValue;
1111
import io.sentry.protocol.SentryId;
1212
import io.sentry.util.AutoClosableReentrantLock;
13+
import io.sentry.util.LazyEvaluator;
1314
import java.util.HashMap;
1415
import java.util.Map;
1516
import java.util.WeakHashMap;
@@ -30,7 +31,7 @@
3031
*/
3132
public final class ActivityFramesTracker {
3233

33-
private @Nullable FrameMetricsAggregator frameMetricsAggregator = null;
34+
private @NotNull LazyEvaluator<FrameMetricsAggregator> frameMetricsAggregator;
3435
private @NotNull final SentryAndroidOptions options;
3536

3637
private final @NotNull Map<SentryId, Map<String, @NotNull MeasurementValue>>
@@ -41,17 +42,18 @@ public final class ActivityFramesTracker {
4142
private final @NotNull MainLooperHandler handler;
4243
protected @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock();
4344

45+
private final @NotNull LazyEvaluator<Boolean> androidXAvailable;
46+
4447
public ActivityFramesTracker(
4548
final @NotNull io.sentry.util.LoadClass loadClass,
4649
final @NotNull SentryAndroidOptions options,
4750
final @NotNull MainLooperHandler handler) {
4851

49-
final boolean androidXAvailable =
50-
loadClass.isClassAvailable("androidx.core.app.FrameMetricsAggregator", options.getLogger());
52+
androidXAvailable =
53+
loadClass.isClassAvailableLazy(
54+
"androidx.core.app.FrameMetricsAggregator", options.getLogger());
55+
frameMetricsAggregator = new LazyEvaluator<>(() -> new FrameMetricsAggregator());
5156

52-
if (androidXAvailable) {
53-
frameMetricsAggregator = new FrameMetricsAggregator();
54-
}
5557
this.options = options;
5658
this.handler = handler;
5759
}
@@ -67,15 +69,15 @@ public ActivityFramesTracker(
6769
final @NotNull io.sentry.util.LoadClass loadClass,
6870
final @NotNull SentryAndroidOptions options,
6971
final @NotNull MainLooperHandler handler,
70-
final @Nullable FrameMetricsAggregator frameMetricsAggregator) {
72+
final @NotNull FrameMetricsAggregator frameMetricsAggregator) {
7173

7274
this(loadClass, options, handler);
73-
this.frameMetricsAggregator = frameMetricsAggregator;
75+
this.frameMetricsAggregator = new LazyEvaluator<>(() -> frameMetricsAggregator);
7476
}
7577

7678
@VisibleForTesting
7779
public boolean isFrameMetricsAggregatorAvailable() {
78-
return frameMetricsAggregator != null
80+
return androidXAvailable.getValue()
7981
&& options.isEnableFramesTracking()
8082
&& !options.isEnablePerformanceV2();
8183
}
@@ -87,7 +89,8 @@ public void addActivity(final @NotNull Activity activity) {
8789
return;
8890
}
8991

90-
runSafelyOnUiThread(() -> frameMetricsAggregator.add(activity), "FrameMetricsAggregator.add");
92+
runSafelyOnUiThread(
93+
() -> frameMetricsAggregator.getValue().add(activity), "FrameMetricsAggregator.add");
9194
snapshotFrameCountsAtStart(activity);
9295
}
9396
}
@@ -104,11 +107,11 @@ private void snapshotFrameCountsAtStart(final @NotNull Activity activity) {
104107
return null;
105108
}
106109

107-
if (frameMetricsAggregator == null) {
110+
if (!androidXAvailable.getValue()) {
108111
return null;
109112
}
110113

111-
final @Nullable SparseIntArray[] framesRates = frameMetricsAggregator.getMetrics();
114+
final @Nullable SparseIntArray[] framesRates = frameMetricsAggregator.getValue().getMetrics();
112115

113116
int totalFrames = 0;
114117
int slowFrames = 0;
@@ -153,7 +156,7 @@ public void setMetrics(final @NotNull Activity activity, final @NotNull SentryId
153156
// there was no
154157
// Observers, See
155158
// https://android.googlesource.com/platform/frameworks/base/+/140ff5ea8e2d99edc3fbe63a43239e459334c76b
156-
runSafelyOnUiThread(() -> frameMetricsAggregator.remove(activity), null);
159+
runSafelyOnUiThread(() -> frameMetricsAggregator.getValue().remove(activity), null);
157160

158161
final @Nullable FrameCounts frameCounts = diffFrameCountsAtEnd(activity);
159162

@@ -215,8 +218,9 @@ public void setMetrics(final @NotNull Activity activity, final @NotNull SentryId
215218
public void stop() {
216219
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
217220
if (isFrameMetricsAggregatorAvailable()) {
218-
runSafelyOnUiThread(() -> frameMetricsAggregator.stop(), "FrameMetricsAggregator.stop");
219-
frameMetricsAggregator.reset();
221+
runSafelyOnUiThread(
222+
() -> frameMetricsAggregator.getValue().stop(), "FrameMetricsAggregator.stop");
223+
frameMetricsAggregator.getValue().reset();
220224
}
221225
activityMeasurements.clear();
222226
}

sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,8 @@ static void initializeIntegrationsAndProcessors(
200200
options.setVersionDetector(new DefaultVersionDetector(options));
201201
}
202202

203-
final boolean isAndroidXScrollViewAvailable =
204-
loadClass.isClassAvailable("androidx.core.view.ScrollingView", options);
203+
final @NotNull LazyEvaluator<Boolean> isAndroidXScrollViewAvailable =
204+
loadClass.isClassAvailableLazy("androidx.core.view.ScrollingView", options);
205205
final boolean isComposeUpstreamAvailable =
206206
loadClass.isClassAvailable(COMPOSE_CLASS_NAME, options);
207207

sentry-android-core/src/main/java/io/sentry/android/core/UserInteractionIntegration.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import io.sentry.android.core.internal.gestures.NoOpWindowCallback;
1616
import io.sentry.android.core.internal.gestures.SentryGestureListener;
1717
import io.sentry.android.core.internal.gestures.SentryWindowCallback;
18+
import io.sentry.util.LazyEvaluator;
1819
import io.sentry.util.Objects;
1920
import java.io.Closeable;
2021
import java.io.IOException;
@@ -28,16 +29,16 @@ public final class UserInteractionIntegration
2829
private @Nullable IScopes scopes;
2930
private @Nullable SentryAndroidOptions options;
3031

31-
private final boolean isAndroidXAvailable;
32-
private final boolean isAndroidxLifecycleAvailable;
32+
private final @NotNull LazyEvaluator<Boolean> isAndroidXAvailable;
33+
private final @NotNull LazyEvaluator<Boolean> isAndroidxLifecycleAvailable;
3334

3435
public UserInteractionIntegration(
3536
final @NotNull Application application, final @NotNull io.sentry.util.LoadClass classLoader) {
3637
this.application = Objects.requireNonNull(application, "Application is required");
3738
isAndroidXAvailable =
38-
classLoader.isClassAvailable("androidx.core.view.GestureDetectorCompat", options);
39+
classLoader.isClassAvailableLazy("androidx.core.view.GestureDetectorCompat", options);
3940
isAndroidxLifecycleAvailable =
40-
classLoader.isClassAvailable("androidx.lifecycle.Lifecycle", options);
41+
classLoader.isClassAvailableLazy("androidx.lifecycle.Lifecycle", options);
4142
}
4243

4344
private void startTracking(final @NotNull Activity activity) {
@@ -128,13 +129,13 @@ public void register(@NotNull IScopes scopes, @NotNull SentryOptions options) {
128129
.log(SentryLevel.DEBUG, "UserInteractionIntegration enabled: %s", integrationEnabled);
129130

130131
if (integrationEnabled) {
131-
if (isAndroidXAvailable) {
132+
if (isAndroidXAvailable.getValue()) {
132133
application.registerActivityLifecycleCallbacks(this);
133134
this.options.getLogger().log(SentryLevel.DEBUG, "UserInteractionIntegration installed.");
134135
addIntegrationToSdkVersion("UserInteraction");
135136

136137
// In case of a deferred init, we hook into any resumed activity
137-
if (isAndroidxLifecycleAvailable) {
138+
if (isAndroidxLifecycleAvailable.getValue()) {
138139
final @Nullable Activity activity = CurrentActivityHolder.getInstance().getActivity();
139140
if (activity instanceof LifecycleOwner) {
140141
if (((LifecycleOwner) activity).getLifecycle().getCurrentState()

sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/AndroidViewGestureTargetLocator.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import io.sentry.android.core.internal.util.ClassUtil;
99
import io.sentry.internal.gestures.GestureTargetLocator;
1010
import io.sentry.internal.gestures.UiElement;
11+
import io.sentry.util.LazyEvaluator;
1112
import org.jetbrains.annotations.ApiStatus;
1213
import org.jetbrains.annotations.NotNull;
1314
import org.jetbrains.annotations.Nullable;
@@ -17,9 +18,10 @@ public final class AndroidViewGestureTargetLocator implements GestureTargetLocat
1718

1819
private static final String ORIGIN = "old_view_system";
1920

20-
private final boolean isAndroidXAvailable;
21+
private final @NotNull LazyEvaluator<Boolean> isAndroidXAvailable;
2122

22-
public AndroidViewGestureTargetLocator(final boolean isAndroidXAvailable) {
23+
public AndroidViewGestureTargetLocator(
24+
final @NotNull LazyEvaluator<Boolean> isAndroidXAvailable) {
2325
this.isAndroidXAvailable = isAndroidXAvailable;
2426
}
2527

@@ -33,7 +35,7 @@ public AndroidViewGestureTargetLocator(final boolean isAndroidXAvailable) {
3335
if (targetType == UiElement.Type.CLICKABLE && isViewTappable(view)) {
3436
return createUiElement(view);
3537
} else if (targetType == UiElement.Type.SCROLLABLE
36-
&& isViewScrollable(view, isAndroidXAvailable)) {
38+
&& isViewScrollable(view, isAndroidXAvailable.getValue())) {
3739
return createUiElement(view);
3840
}
3941
return null;

sentry-android-core/src/test/java/io/sentry/android/core/ActivityFramesTrackerTest.kt

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import android.util.SparseIntArray
55
import androidx.core.app.FrameMetricsAggregator
66
import androidx.test.ext.junit.runners.AndroidJUnit4
77
import io.sentry.ILogger
8+
import io.sentry.SentryOptions
89
import io.sentry.protocol.MeasurementValue
910
import io.sentry.protocol.SentryId
11+
import io.sentry.util.LazyEvaluator
1012
import kotlin.test.Test
1113
import kotlin.test.assertEquals
1214
import kotlin.test.assertFalse
@@ -34,12 +36,14 @@ class ActivityFramesTrackerTest {
3436
options.isEnablePerformanceV2 = false
3537
}
3638

37-
fun getSut(mockAggregator: Boolean = true): ActivityFramesTracker =
38-
if (mockAggregator) {
39-
ActivityFramesTracker(loadClass, options, handler, aggregator)
40-
} else {
41-
ActivityFramesTracker(loadClass, options, handler)
42-
}
39+
fun getSut(isAndroidxAvailable: Boolean = true): ActivityFramesTracker {
40+
whenever(loadClass.isClassAvailableLazy(any(), any<ILogger>()))
41+
.thenReturn(LazyEvaluator { isAndroidxAvailable })
42+
whenever(loadClass.isClassAvailableLazy(any(), any<SentryOptions>()))
43+
.thenReturn(LazyEvaluator { isAndroidxAvailable })
44+
45+
return ActivityFramesTracker(loadClass, options, handler, aggregator)
46+
}
4347
}
4448

4549
private val fixture = Fixture()
@@ -340,23 +344,20 @@ class ActivityFramesTrackerTest {
340344

341345
@Test
342346
fun `addActivity does not throw if no AndroidX`() {
343-
whenever(fixture.loadClass.isClassAvailable(any(), any<ILogger>())).thenReturn(false)
344347
val sut = fixture.getSut(false)
345348

346349
sut.addActivity(fixture.activity)
347350
}
348351

349352
@Test
350353
fun `setMetrics does not throw if no AndroidX`() {
351-
whenever(fixture.loadClass.isClassAvailable(any(), any<ILogger>())).thenReturn(false)
352354
val sut = fixture.getSut(false)
353355

354356
sut.setMetrics(fixture.activity, fixture.sentryId)
355357
}
356358

357359
@Test
358360
fun `addActivity and setMetrics combined do not throw if no AndroidX`() {
359-
whenever(fixture.loadClass.isClassAvailable(any(), any<ILogger>())).thenReturn(false)
360361
val sut = fixture.getSut(false)
361362

362363
sut.addActivity(fixture.activity)
@@ -373,7 +374,6 @@ class ActivityFramesTrackerTest {
373374

374375
@Test
375376
fun `stop does not throw if no AndroidX`() {
376-
whenever(fixture.loadClass.isClassAvailable(any(), any<ILogger>())).thenReturn(false)
377377
val sut = fixture.getSut(false)
378378

379379
sut.stop()
@@ -390,9 +390,13 @@ class ActivityFramesTrackerTest {
390390

391391
@Test
392392
fun `takeMetrics returns null if no AndroidX`() {
393-
whenever(fixture.loadClass.isClassAvailable(any(), any<ILogger>())).thenReturn(false)
394393
val sut = fixture.getSut(false)
395394

395+
whenever(fixture.aggregator.metrics).thenReturn(emptyArray(), getArray())
396+
397+
sut.addActivity(fixture.activity)
398+
sut.setMetrics(fixture.activity, fixture.sentryId)
399+
396400
assertNull(sut.takeMetrics(fixture.sentryId))
397401
}
398402

sentry-android-core/src/test/java/io/sentry/android/core/UserInteractionIntegrationTest.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
99
import io.sentry.Scopes
1010
import io.sentry.android.core.internal.gestures.NoOpWindowCallback
1111
import io.sentry.android.core.internal.gestures.SentryWindowCallback
12+
import io.sentry.util.LazyEvaluator
1213
import junit.framework.TestCase.assertNull
1314
import kotlin.test.BeforeTest
1415
import kotlin.test.Test
@@ -43,19 +44,19 @@ class UserInteractionIntegrationTest {
4344
isLifecycleAvailable: Boolean = true,
4445
): UserInteractionIntegration {
4546
whenever(
46-
loadClass.isClassAvailable(
47+
loadClass.isClassAvailableLazy(
4748
eq("androidx.core.view.GestureDetectorCompat"),
4849
anyOrNull<SentryAndroidOptions>(),
4950
)
5051
)
51-
.thenReturn(isAndroidXAvailable)
52+
.thenReturn(LazyEvaluator { isAndroidXAvailable })
5253
whenever(
53-
loadClass.isClassAvailable(
54+
loadClass.isClassAvailableLazy(
5455
eq("androidx.lifecycle.Lifecycle"),
5556
anyOrNull<SentryAndroidOptions>(),
5657
)
5758
)
58-
.thenReturn(isLifecycleAvailable)
59+
.thenReturn(LazyEvaluator { isLifecycleAvailable })
5960
whenever(scopes.options).thenReturn(options)
6061
if (callback != null) {
6162
window.callback = callback

sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerClickTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import io.sentry.Scope.IWithPropagationContext
1717
import io.sentry.ScopeCallback
1818
import io.sentry.SentryLevel.INFO
1919
import io.sentry.android.core.SentryAndroidOptions
20+
import io.sentry.util.LazyEvaluator
2021
import kotlin.test.Test
2122
import kotlin.test.assertEquals
2223
import org.mockito.kotlin.any
@@ -38,7 +39,7 @@ class SentryGestureListenerClickTest {
3839
SentryAndroidOptions().apply {
3940
isEnableUserInteractionBreadcrumbs = true
4041
isEnableUserInteractionTracing = true
41-
gestureTargetLocators = listOf(AndroidViewGestureTargetLocator(true))
42+
gestureTargetLocators = listOf(AndroidViewGestureTargetLocator(LazyEvaluator { true }))
4243
dsn = "https://key@sentry.io/proj"
4344
}
4445
val scopes = mock<IScopes>()

sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerScrollTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import io.sentry.ScopeCallback
2020
import io.sentry.SentryLevel
2121
import io.sentry.SentryLevel.INFO
2222
import io.sentry.android.core.SentryAndroidOptions
23+
import io.sentry.util.LazyEvaluator
2324
import kotlin.test.Test
2425
import kotlin.test.assertEquals
2526
import org.mockito.kotlin.any
@@ -46,7 +47,7 @@ class SentryGestureListenerScrollTest {
4647
dsn = "https://key@sentry.io/proj"
4748
isEnableUserInteractionBreadcrumbs = true
4849
isEnableUserInteractionTracing = true
49-
gestureTargetLocators = listOf(AndroidViewGestureTargetLocator(true))
50+
gestureTargetLocators = listOf(AndroidViewGestureTargetLocator(LazyEvaluator { true }))
5051
}
5152
val scopes = mock<IScopes>()
5253
val scope = mock<IScope>()

sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerTracingTest.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import io.sentry.TransactionOptions
2323
import io.sentry.android.core.SentryAndroidOptions
2424
import io.sentry.protocol.SentryId
2525
import io.sentry.protocol.TransactionNameSource
26+
import io.sentry.util.LazyEvaluator
2627
import kotlin.test.Test
2728
import kotlin.test.assertEquals
2829
import kotlin.test.assertNotEquals
@@ -65,7 +66,8 @@ class SentryGestureListenerTracingTest {
6566
options.tracesSampleRate = tracesSampleRate
6667
options.isEnableUserInteractionTracing = isEnableUserInteractionTracing
6768
options.isEnableUserInteractionBreadcrumbs = true
68-
options.gestureTargetLocators = listOf(AndroidViewGestureTargetLocator(true))
69+
options.gestureTargetLocators =
70+
listOf(AndroidViewGestureTargetLocator(LazyEvaluator { true }))
6971
options.isEnableAutoTraceIdGeneration = isEnableAutoTraceIdGeneration
7072

7173
whenever(scopes.options).thenReturn(options)

sentry/api/sentry.api

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7086,6 +7086,8 @@ public class io/sentry/util/LoadClass {
70867086
public fun <init> ()V
70877087
public fun isClassAvailable (Ljava/lang/String;Lio/sentry/ILogger;)Z
70887088
public fun isClassAvailable (Ljava/lang/String;Lio/sentry/SentryOptions;)Z
7089+
public fun isClassAvailableLazy (Ljava/lang/String;Lio/sentry/ILogger;)Lio/sentry/util/LazyEvaluator;
7090+
public fun isClassAvailableLazy (Ljava/lang/String;Lio/sentry/SentryOptions;)Lio/sentry/util/LazyEvaluator;
70897091
public fun loadClass (Ljava/lang/String;Lio/sentry/ILogger;)Ljava/lang/Class;
70907092
}
70917093

0 commit comments

Comments
 (0)