diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cbed67dcf7..71190dc6685 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - If you're using code obfuscation, adjust your proguard-rules accordingly, so your custom view class name is not minified - Fix ensure Application Context is used even when SDK is initialized via Activity Context ([#3669](https://github.com/getsentry/sentry-java/pull/3669)) - Fix potential ANRs due to `Calendar.getInstance` usage in Breadcrumbs constructor ([#3736](https://github.com/getsentry/sentry-java/pull/3736)) +- Fix potential ANRs due to default integrations ([#3778](https://github.com/getsentry/sentry-java/pull/3778)) - Lazily initialize heavy `SentryOptions` members to avoid ANRs on app start ([#3749](https://github.com/getsentry/sentry-java/pull/3749)) *Breaking changes*: diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java index 7556afb2354..6e7a22ac057 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java @@ -382,7 +382,7 @@ public synchronized void onActivityCreated( firstActivityCreated = true; - if (fullyDisplayedReporter != null) { + if (performanceEnabled && ttfdSpan != null && fullyDisplayedReporter != null) { fullyDisplayedReporter.registerFullyDrawnListener(() -> onFullFrameDrawn(ttfdSpan)); } } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java index 97c7d06ce7d..84267a28124 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegration.java @@ -85,41 +85,20 @@ public void close() throws IOException { @SuppressWarnings("deprecation") @Override public void onConfigurationChanged(@NotNull Configuration newConfig) { - if (hub != null) { - final Device.DeviceOrientation deviceOrientation = - DeviceOrientations.getOrientation(context.getResources().getConfiguration().orientation); - - String orientation; - if (deviceOrientation != null) { - orientation = deviceOrientation.name().toLowerCase(Locale.ROOT); - } else { - orientation = "undefined"; - } - - final Breadcrumb breadcrumb = new Breadcrumb(); - breadcrumb.setType("navigation"); - breadcrumb.setCategory("device.orientation"); - breadcrumb.setData("position", orientation); - breadcrumb.setLevel(SentryLevel.INFO); - - final Hint hint = new Hint(); - hint.set(ANDROID_CONFIGURATION, newConfig); - - hub.addBreadcrumb(breadcrumb, hint); - } + executeInBackground(() -> captureConfigurationChangedBreadcrumb(newConfig)); } @Override public void onLowMemory() { - createLowMemoryBreadcrumb(null); + executeInBackground(() -> captureLowMemoryBreadcrumb(null)); } @Override public void onTrimMemory(final int level) { - createLowMemoryBreadcrumb(level); + executeInBackground(() -> captureLowMemoryBreadcrumb(level)); } - private void createLowMemoryBreadcrumb(final @Nullable Integer level) { + private void captureLowMemoryBreadcrumb(final @Nullable Integer level) { if (hub != null) { final Breadcrumb breadcrumb = new Breadcrumb(); if (level != null) { @@ -147,4 +126,41 @@ private void createLowMemoryBreadcrumb(final @Nullable Integer level) { hub.addBreadcrumb(breadcrumb); } } + + private void captureConfigurationChangedBreadcrumb(final @NotNull Configuration newConfig) { + if (hub != null) { + final Device.DeviceOrientation deviceOrientation = + DeviceOrientations.getOrientation(context.getResources().getConfiguration().orientation); + + String orientation; + if (deviceOrientation != null) { + orientation = deviceOrientation.name().toLowerCase(Locale.ROOT); + } else { + orientation = "undefined"; + } + + final Breadcrumb breadcrumb = new Breadcrumb(); + breadcrumb.setType("navigation"); + breadcrumb.setCategory("device.orientation"); + breadcrumb.setData("position", orientation); + breadcrumb.setLevel(SentryLevel.INFO); + + final Hint hint = new Hint(); + hint.set(ANDROID_CONFIGURATION, newConfig); + + hub.addBreadcrumb(breadcrumb, hint); + } + } + + private void executeInBackground(final @NotNull Runnable runnable) { + if (options != null) { + try { + options.getExecutorService().submit(runnable); + } catch (Throwable t) { + options + .getLogger() + .log(SentryLevel.ERROR, t, "Failed to submit app components breadcrumb task"); + } + } + } } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java index f196b7ca90a..5d5068c64fa 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java @@ -211,7 +211,7 @@ static final class SystemEventsBroadcastReceiver extends BroadcastReceiver { private static final long DEBOUNCE_WAIT_TIME_MS = 60 * 1000; private final @NotNull IHub hub; private final @NotNull SentryAndroidOptions options; - private final @NotNull Debouncer debouncer = + private final @NotNull Debouncer batteryChangedDebouncer = new Debouncer(AndroidCurrentDateProvider.getInstance(), DEBOUNCE_WAIT_TIME_MS, 0); SystemEventsBroadcastReceiver( @@ -221,19 +221,38 @@ static final class SystemEventsBroadcastReceiver extends BroadcastReceiver { } @Override - public void onReceive(Context context, Intent intent) { - final boolean shouldDebounce = debouncer.checkForDebounce(); - final String action = intent.getAction(); + public void onReceive(final Context context, final @NotNull Intent intent) { + final @Nullable String action = intent.getAction(); final boolean isBatteryChanged = ACTION_BATTERY_CHANGED.equals(action); - if (isBatteryChanged && shouldDebounce) { - // aligning with iOS which only captures battery status changes every minute at maximum + + // aligning with iOS which only captures battery status changes every minute at maximum + if (isBatteryChanged && batteryChangedDebouncer.checkForDebounce()) { return; } + try { + options + .getExecutorService() + .submit( + () -> { + final Breadcrumb breadcrumb = createBreadcrumb(intent, action, isBatteryChanged); + final Hint hint = new Hint(); + hint.set(ANDROID_INTENT, intent); + hub.addBreadcrumb(breadcrumb, hint); + }); + } catch (Throwable t) { + options + .getLogger() + .log(SentryLevel.ERROR, t, "Failed to submit system event breadcrumb action."); + } + } + + private @NotNull Breadcrumb createBreadcrumb( + final @NotNull Intent intent, final @Nullable String action, boolean isBatteryChanged) { final Breadcrumb breadcrumb = new Breadcrumb(); breadcrumb.setType("system"); breadcrumb.setCategory("device.event"); - String shortAction = StringUtils.getStringAfterDot(action); + final String shortAction = StringUtils.getStringAfterDot(action); if (shortAction != null) { breadcrumb.setData("action", shortAction); } @@ -273,11 +292,7 @@ public void onReceive(Context context, Intent intent) { } } breadcrumb.setLevel(SentryLevel.INFO); - - final Hint hint = new Hint(); - hint.set(ANDROID_INTENT, intent); - - hub.addBreadcrumb(breadcrumb, hint); + return breadcrumb; } } } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt index b355075ff1c..addeb948194 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt @@ -78,7 +78,6 @@ class ActivityLifecycleIntegrationTest { } val bundle = mock() val activityFramesTracker = mock() - val fullyDisplayedReporter = FullyDisplayedReporter.getInstance() val transactionFinishedCallback = mock() lateinit var shadowActivityManager: ShadowActivityManager @@ -619,11 +618,30 @@ class ActivityLifecycleIntegrationTest { sut.onActivityCreated(activity, mock()) val ttfdSpan = sut.ttfdSpanMap[activity] sut.ttidSpanMap.values.first().finish() - fixture.fullyDisplayedReporter.reportFullyDrawn() + fixture.options.fullyDisplayedReporter.reportFullyDrawn() assertTrue(ttfdSpan!!.isFinished) assertNotEquals(SpanStatus.CANCELLED, ttfdSpan.status) } + @Test + fun `if ttfd is disabled, no listener is registered for FullyDisplayedReporter`() { + val ttfdReporter = mock() + + val sut = fixture.getSut() + fixture.options.apply { + tracesSampleRate = 1.0 + isEnableTimeToFullDisplayTracing = false + fullyDisplayedReporter = ttfdReporter + } + + sut.register(fixture.hub, fixture.options) + + val activity = mock() + sut.onActivityCreated(activity, mock()) + + verify(ttfdReporter, never()).registerFullyDrawnListener(any()) + } + @Test fun `App start is Cold when savedInstanceState is null`() { val sut = fixture.getSut() diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegrationTest.kt index 15a6d690e55..75152767a59 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegrationTest.kt @@ -7,6 +7,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import io.sentry.Breadcrumb import io.sentry.IHub import io.sentry.SentryLevel +import io.sentry.test.ImmediateExecutorService import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull @@ -88,7 +89,9 @@ class AppComponentsBreadcrumbsIntegrationTest { @Test fun `When low memory event, a breadcrumb with type, category and level should be set`() { val sut = fixture.getSut() - val options = SentryAndroidOptions() + val options = SentryAndroidOptions().apply { + executorService = ImmediateExecutorService() + } val hub = mock() sut.register(hub, options) sut.onLowMemory() @@ -104,7 +107,9 @@ class AppComponentsBreadcrumbsIntegrationTest { @Test fun `When trim memory event with level, a breadcrumb with type, category and level should be set`() { val sut = fixture.getSut() - val options = SentryAndroidOptions() + val options = SentryAndroidOptions().apply { + executorService = ImmediateExecutorService() + } val hub = mock() sut.register(hub, options) sut.onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 059262d2c31..f8dc3b52eef 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -2523,6 +2523,7 @@ public class io/sentry/SentryOptions { public fun setEnvironment (Ljava/lang/String;)V public fun setExecutorService (Lio/sentry/ISentryExecutorService;)V public fun setFlushTimeoutMillis (J)V + public fun setFullyDisplayedReporter (Lio/sentry/FullyDisplayedReporter;)V public fun setGestureTargetLocators (Ljava/util/List;)V public fun setIdleTimeout (Ljava/lang/Long;)V public fun setIgnoredCheckIns (Ljava/util/List;)V diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index 61c721847c3..3873bc93808 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -431,7 +431,7 @@ public class SentryOptions { private boolean enableTimeToFullDisplayTracing = false; /** Screen fully displayed reporter, used for time-to-full-display spans. */ - private final @NotNull FullyDisplayedReporter fullyDisplayedReporter = + private @NotNull FullyDisplayedReporter fullyDisplayedReporter = FullyDisplayedReporter.getInstance(); private @NotNull IConnectionStatusProvider connectionStatusProvider = @@ -2100,6 +2100,13 @@ public void setEnableTimeToFullDisplayTracing(final boolean enableTimeToFullDisp return fullyDisplayedReporter; } + @ApiStatus.Internal + @TestOnly + public void setFullyDisplayedReporter( + final @NotNull FullyDisplayedReporter fullyDisplayedReporter) { + this.fullyDisplayedReporter = fullyDisplayedReporter; + } + /** * Whether OPTIONS requests should be traced. *