Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2a0d6da
perf(connectivity): Cache network capabilities and status to reduce I…
romtsn Jul 16, 2025
0ac3081
Merge branch 'main' into rz/perf/less-ipc
romtsn Jul 16, 2025
14bb2d5
changelog
romtsn Jul 16, 2025
c82586d
Changelog
romtsn Jul 16, 2025
7418781
revert
romtsn Jul 16, 2025
f739d00
fix(breadcrumbs): Deduplicate battery breadcrumbs
romtsn Jul 16, 2025
833b026
ref
romtsn Jul 16, 2025
e4596ff
Changelog
romtsn Jul 16, 2025
0153ab5
Fix test
romtsn Jul 17, 2025
15794c6
Merge branch 'rz/perf/less-ipc' into rz/fix/diff-battery-crumbs
romtsn Jul 17, 2025
c31d648
perf(connectivity): Have only one NetworkCallback active at a time
romtsn Jul 18, 2025
83d80d7
Changelog
romtsn Jul 18, 2025
5b32662
perf(integrations): Use single lifecycle observer
romtsn Jul 23, 2025
5c5238a
Add tests
romtsn Jul 24, 2025
d2263b8
Changelog
romtsn Jul 24, 2025
42ff8e9
Fix tests
romtsn Jul 24, 2025
fde11a2
perf(integrations): Do not register for SystemEvents and NetworkCallb…
romtsn Jul 29, 2025
bba4efe
Do not cache importance
romtsn Jul 30, 2025
5edb0d3
Revert SR
romtsn Jul 30, 2025
d8402b6
Add tests
romtsn Jul 30, 2025
b7a5e0a
Spotless
romtsn Jul 30, 2025
c7cad14
Comment
romtsn Jul 30, 2025
8a6f145
Revert profiling
romtsn Jul 30, 2025
d868d1a
Changelog
romtsn Jul 30, 2025
c890fe3
fix test name
romtsn Jul 30, 2025
900ecb5
Update sentry-samples/sentry-samples-android/sdkperf/README.md
romtsn Jul 30, 2025
ac613d6
Update sentry-samples/sentry-samples-android/sdkperf/README.md
romtsn Jul 30, 2025
85f5bae
Update sentry-samples/sentry-samples-android/sdkperf/README.md
romtsn Jul 30, 2025
3f3a499
Update sentry-samples/sentry-samples-android/sdkperf/screen_flap.sh
romtsn Aug 1, 2025
c231dfc
Update sentry-samples/sentry-samples-android/sdkperf/wifi_flap.sh
romtsn Aug 1, 2025
cda0b1d
Address PR review
romtsn Aug 1, 2025
f7179a5
Formatting
romtsn Aug 1, 2025
d4e8b1f
Merge branch 'main' into rz/perf/no-network-on-background
romtsn Aug 5, 2025
c648064
Fix merge conflict
romtsn Aug 5, 2025
12a6347
Fix tests
romtsn Aug 5, 2025
4cbaa1c
Revert
romtsn Aug 6, 2025
38a7cbb
Merge branch 'main' into rz/perf/no-network-on-background
romtsn Aug 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@

## Unreleased

### Internal

- Use single `LifecycleObserver` and multi-cast it to the integrations interested in lifecycle states ([#4567](https://github.com/getsentry/sentry-java/pull/4567))

### Fixes

- Allow multiple UncaughtExceptionHandlerIntegrations to be active at the same time ([#4462](https://github.com/getsentry/sentry-java/pull/4462))
- Cache network capabilities and status to reduce IPC calls ([#4560](https://github.com/getsentry/sentry-java/pull/4560))
- Deduplicate battery breadcrumbs ([#4561](https://github.com/getsentry/sentry-java/pull/4561))
- Have single `NetworkCallback` registered at a time to reduce IPC calls ([#4562](https://github.com/getsentry/sentry-java/pull/4562))
- Do not register for SystemEvents and NetworkCallbacks immediately when launched with non-foreground importance ([#4579](https://github.com/getsentry/sentry-java/pull/4579))

## 8.17.0

Expand Down
27 changes: 24 additions & 3 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,28 @@ public final class io/sentry/android/core/AppLifecycleIntegration : io/sentry/In
public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V
}

public final class io/sentry/android/core/AppState {
public final class io/sentry/android/core/AppState : java/io/Closeable {
public fun addAppStateListener (Lio/sentry/android/core/AppState$AppStateListener;)V
public fun close ()V
public static fun getInstance ()Lio/sentry/android/core/AppState;
public fun getLifecycleObserver ()Lio/sentry/android/core/AppState$LifecycleObserver;
public fun isInBackground ()Ljava/lang/Boolean;
public fun registerLifecycleObserver (Lio/sentry/SentryOptions;)V
public fun removeAppStateListener (Lio/sentry/android/core/AppState$AppStateListener;)V
public fun resetInstance ()V
public fun unregisterLifecycleObserver ()V
}

public abstract interface class io/sentry/android/core/AppState$AppStateListener {
public abstract fun onBackground ()V
public abstract fun onForeground ()V
}

public final class io/sentry/android/core/AppState$LifecycleObserver : androidx/lifecycle/DefaultLifecycleObserver {
public fun <init> (Lio/sentry/android/core/AppState;)V
public fun getListeners ()Ljava/util/List;
public fun onStart (Landroidx/lifecycle/LifecycleOwner;)V
public fun onStop (Landroidx/lifecycle/LifecycleOwner;)V
}

public final class io/sentry/android/core/BuildConfig {
Expand Down Expand Up @@ -263,7 +282,7 @@ public final class io/sentry/android/core/NdkIntegration : io/sentry/Integration
}

public final class io/sentry/android/core/NetworkBreadcrumbsIntegration : io/sentry/Integration, java/io/Closeable {
public fun <init> (Landroid/content/Context;Lio/sentry/android/core/BuildInfoProvider;Lio/sentry/ILogger;)V
public fun <init> (Landroid/content/Context;Lio/sentry/android/core/BuildInfoProvider;)V
public fun close ()V
public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V
}
Expand Down Expand Up @@ -420,11 +439,13 @@ public class io/sentry/android/core/SpanFrameMetricsCollector : io/sentry/IPerfo
public fun onSpanStarted (Lio/sentry/ISpan;)V
}

public final class io/sentry/android/core/SystemEventsBreadcrumbsIntegration : io/sentry/Integration, java/io/Closeable {
public final class io/sentry/android/core/SystemEventsBreadcrumbsIntegration : io/sentry/Integration, io/sentry/android/core/AppState$AppStateListener, java/io/Closeable {
public fun <init> (Landroid/content/Context;)V
public fun <init> (Landroid/content/Context;Ljava/util/List;)V
public fun close ()V
public static fun getDefaultActions ()Ljava/util/List;
public fun onBackground ()V
public fun onForeground ()V
public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import io.sentry.android.core.internal.gestures.AndroidViewGestureTargetLocator;
import io.sentry.android.core.internal.modules.AssetsModulesLoader;
import io.sentry.android.core.internal.util.AndroidConnectionStatusProvider;
import io.sentry.android.core.internal.util.AndroidCurrentDateProvider;
import io.sentry.android.core.internal.util.AndroidThreadChecker;
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
import io.sentry.android.core.performance.AppStartMetrics;
Expand Down Expand Up @@ -127,6 +128,7 @@ static void loadDefaultAndMetadataOptions(
options.setCacheDirPath(getCacheDir(context).getAbsolutePath());

readDefaultOptionValues(options, context, buildInfoProvider);
AppState.getInstance().registerLifecycleObserver(options);
}

@TestOnly
Expand Down Expand Up @@ -157,7 +159,8 @@ static void initializeIntegrationsAndProcessors(

if (options.getConnectionStatusProvider() instanceof NoOpConnectionStatusProvider) {
options.setConnectionStatusProvider(
new AndroidConnectionStatusProvider(context, options.getLogger(), buildInfoProvider));
new AndroidConnectionStatusProvider(
context, options, buildInfoProvider, AndroidCurrentDateProvider.getInstance()));
}

if (options.getCacheDirPath() != null) {
Expand Down Expand Up @@ -380,8 +383,7 @@ static void installDefaultIntegrations(
}
options.addIntegration(new AppComponentsBreadcrumbsIntegration(context));
options.addIntegration(new SystemEventsBreadcrumbsIntegration(context));
options.addIntegration(
new NetworkBreadcrumbsIntegration(context, buildInfoProvider, options.getLogger()));
options.addIntegration(new NetworkBreadcrumbsIntegration(context, buildInfoProvider));
if (isReplayAvailable) {
final ReplayIntegration replay =
new ReplayIntegration(context, CurrentDateProvider.getInstance());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion;

import androidx.lifecycle.ProcessLifecycleOwner;
import io.sentry.IScopes;
import io.sentry.ISentryLifecycleToken;
import io.sentry.Integration;
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.android.core.internal.util.AndroidThreadChecker;
import io.sentry.util.AutoClosableReentrantLock;
import io.sentry.util.Objects;
import java.io.Closeable;
import java.io.IOException;
Expand All @@ -17,20 +17,11 @@

public final class AppLifecycleIntegration implements Integration, Closeable {

private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock();
@TestOnly @Nullable volatile LifecycleWatcher watcher;

private @Nullable SentryAndroidOptions options;

private final @NotNull MainLooperHandler handler;

public AppLifecycleIntegration() {
this(new MainLooperHandler());
}

AppLifecycleIntegration(final @NotNull MainLooperHandler handler) {
this.handler = handler;
}

@Override
public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions options) {
Objects.requireNonNull(scopes, "Scopes are required");
Expand All @@ -55,85 +46,47 @@ public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions

if (this.options.isEnableAutoSessionTracking()
|| this.options.isEnableAppLifecycleBreadcrumbs()) {
try {
Class.forName("androidx.lifecycle.DefaultLifecycleObserver");
Class.forName("androidx.lifecycle.ProcessLifecycleOwner");
if (AndroidThreadChecker.getInstance().isMainThread()) {
addObserver(scopes);
} else {
// some versions of the androidx lifecycle-process require this to be executed on the main
// thread.
handler.post(() -> addObserver(scopes));
try (final ISentryLifecycleToken ignored = lock.acquire()) {
if (watcher != null) {
return;
}
} catch (ClassNotFoundException e) {
options
.getLogger()
.log(
SentryLevel.WARNING,
"androidx.lifecycle is not available, AppLifecycleIntegration won't be installed");
} catch (IllegalStateException e) {
options
.getLogger()
.log(SentryLevel.ERROR, "AppLifecycleIntegration could not be installed", e);
}
}
}

private void addObserver(final @NotNull IScopes scopes) {
// this should never happen, check added to avoid warnings from NullAway
if (this.options == null) {
return;
}
watcher =
new LifecycleWatcher(
scopes,
this.options.getSessionTrackingIntervalMillis(),
this.options.isEnableAutoSessionTracking(),
this.options.isEnableAppLifecycleBreadcrumbs());

watcher =
new LifecycleWatcher(
scopes,
this.options.getSessionTrackingIntervalMillis(),
this.options.isEnableAutoSessionTracking(),
this.options.isEnableAppLifecycleBreadcrumbs());
AppState.getInstance().addAppStateListener(watcher);
}

try {
ProcessLifecycleOwner.get().getLifecycle().addObserver(watcher);
options.getLogger().log(SentryLevel.DEBUG, "AppLifecycleIntegration installed.");
addIntegrationToSdkVersion("AppLifecycle");
} catch (Throwable e) {
// This is to handle a potential 'AbstractMethodError' gracefully. The error is triggered in
// connection with conflicting dependencies of the androidx.lifecycle.
// //See the issue here: https://github.com/getsentry/sentry-java/pull/2228
watcher = null;
options
.getLogger()
.log(
SentryLevel.ERROR,
"AppLifecycleIntegration failed to get Lifecycle and could not be installed.",
e);
}
}

private void removeObserver() {
final @Nullable LifecycleWatcher watcherRef = watcher;
final @Nullable LifecycleWatcher watcherRef;
try (final ISentryLifecycleToken ignored = lock.acquire()) {
watcherRef = watcher;
watcher = null;
}

if (watcherRef != null) {
ProcessLifecycleOwner.get().getLifecycle().removeObserver(watcherRef);
AppState.getInstance().removeAppStateListener(watcherRef);
if (options != null) {
options.getLogger().log(SentryLevel.DEBUG, "AppLifecycleIntegration removed.");
}
}
watcher = null;
}

@Override
public void close() throws IOException {
if (watcher == null) {
return;
}
if (AndroidThreadChecker.getInstance().isMainThread()) {
removeObserver();
} else {
// some versions of the androidx lifecycle-process require this to be executed on the main
// thread.
// avoid method refs on Android due to some issues with older AGP setups
// noinspection Convert2MethodRef
handler.post(() -> removeObserver());
}
removeObserver();
// TODO: probably should move it to Scopes.close(), but that'd require a new interface and
// different implementations for Java and Android. This is probably fine like this too, because
// integrations are closed in the same place
AppState.getInstance().unregisterLifecycleObserver();
}
}
Loading
Loading