Skip to content

Commit d0e5fdc

Browse files
romtsnmarkushi
andauthored
refactor(lifecycle): Use single lifecycle observer (#4567)
* perf(connectivity): Cache network capabilities and status to reduce IPC calls * changelog * Changelog * revert * fix(breadcrumbs): Deduplicate battery breadcrumbs * ref * Changelog * Fix test * perf(connectivity): Have only one NetworkCallback active at a time * Changelog * perf(integrations): Use single lifecycle observer * Add tests * Changelog * Fix tests * Improve callback handling and test visibility (#4593) * Null-check lifecycleObserver --------- Co-authored-by: Markus Hintersteiner <[email protected]>
1 parent f8aa71b commit d0e5fdc

12 files changed

+690
-357
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Improvements
66

77
- Session Replay: Use main thread looper to schedule replay capture ([#4542](https://github.com/getsentry/sentry-java/pull/4542))
8+
- Use single `LifecycleObserver` and multi-cast it to the integrations interested in lifecycle states ([#4567](https://github.com/getsentry/sentry-java/pull/4567))
89

910
### Fixes
1011

sentry-android-core/api/sentry-android-core.api

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,17 @@ public final class io/sentry/android/core/AppLifecycleIntegration : io/sentry/In
166166
public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V
167167
}
168168

169-
public final class io/sentry/android/core/AppState {
169+
public final class io/sentry/android/core/AppState : java/io/Closeable {
170+
public fun close ()V
170171
public static fun getInstance ()Lio/sentry/android/core/AppState;
171172
public fun isInBackground ()Ljava/lang/Boolean;
172173
}
173174

175+
public abstract interface class io/sentry/android/core/AppState$AppStateListener {
176+
public abstract fun onBackground ()V
177+
public abstract fun onForeground ()V
178+
}
179+
174180
public final class io/sentry/android/core/BuildConfig {
175181
public static final field BUILD_TYPE Ljava/lang/String;
176182
public static final field DEBUG Z
@@ -422,11 +428,13 @@ public class io/sentry/android/core/SpanFrameMetricsCollector : io/sentry/IPerfo
422428
public fun onSpanStarted (Lio/sentry/ISpan;)V
423429
}
424430

425-
public final class io/sentry/android/core/SystemEventsBreadcrumbsIntegration : io/sentry/Integration, java/io/Closeable {
431+
public final class io/sentry/android/core/SystemEventsBreadcrumbsIntegration : io/sentry/Integration, io/sentry/android/core/AppState$AppStateListener, java/io/Closeable {
426432
public fun <init> (Landroid/content/Context;)V
427433
public fun <init> (Landroid/content/Context;Ljava/util/List;)V
428434
public fun close ()V
429435
public static fun getDefaultActions ()Ljava/util/List;
436+
public fun onBackground ()V
437+
public fun onForeground ()V
430438
public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V
431439
}
432440

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ static void loadDefaultAndMetadataOptions(
128128
options.setCacheDirPath(getCacheDir(context).getAbsolutePath());
129129

130130
readDefaultOptionValues(options, context, buildInfoProvider);
131+
AppState.getInstance().registerLifecycleObserver(options);
131132
}
132133

133134
@TestOnly

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

Lines changed: 26 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

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

5-
import androidx.lifecycle.ProcessLifecycleOwner;
65
import io.sentry.IScopes;
6+
import io.sentry.ISentryLifecycleToken;
77
import io.sentry.Integration;
88
import io.sentry.SentryLevel;
99
import io.sentry.SentryOptions;
10-
import io.sentry.android.core.internal.util.AndroidThreadChecker;
10+
import io.sentry.util.AutoClosableReentrantLock;
1111
import io.sentry.util.Objects;
1212
import java.io.Closeable;
1313
import java.io.IOException;
@@ -17,20 +17,11 @@
1717

1818
public final class AppLifecycleIntegration implements Integration, Closeable {
1919

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

2223
private @Nullable SentryAndroidOptions options;
2324

24-
private final @NotNull MainLooperHandler handler;
25-
26-
public AppLifecycleIntegration() {
27-
this(new MainLooperHandler());
28-
}
29-
30-
AppLifecycleIntegration(final @NotNull MainLooperHandler handler) {
31-
this.handler = handler;
32-
}
33-
3425
@Override
3526
public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions options) {
3627
Objects.requireNonNull(scopes, "Scopes are required");
@@ -55,85 +46,47 @@ public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions
5546

5647
if (this.options.isEnableAutoSessionTracking()
5748
|| this.options.isEnableAppLifecycleBreadcrumbs()) {
58-
try {
59-
Class.forName("androidx.lifecycle.DefaultLifecycleObserver");
60-
Class.forName("androidx.lifecycle.ProcessLifecycleOwner");
61-
if (AndroidThreadChecker.getInstance().isMainThread()) {
62-
addObserver(scopes);
63-
} else {
64-
// some versions of the androidx lifecycle-process require this to be executed on the main
65-
// thread.
66-
handler.post(() -> addObserver(scopes));
49+
try (final ISentryLifecycleToken ignored = lock.acquire()) {
50+
if (watcher != null) {
51+
return;
6752
}
68-
} catch (ClassNotFoundException e) {
69-
options
70-
.getLogger()
71-
.log(
72-
SentryLevel.WARNING,
73-
"androidx.lifecycle is not available, AppLifecycleIntegration won't be installed");
74-
} catch (IllegalStateException e) {
75-
options
76-
.getLogger()
77-
.log(SentryLevel.ERROR, "AppLifecycleIntegration could not be installed", e);
78-
}
79-
}
80-
}
8153

82-
private void addObserver(final @NotNull IScopes scopes) {
83-
// this should never happen, check added to avoid warnings from NullAway
84-
if (this.options == null) {
85-
return;
86-
}
54+
watcher =
55+
new LifecycleWatcher(
56+
scopes,
57+
this.options.getSessionTrackingIntervalMillis(),
58+
this.options.isEnableAutoSessionTracking(),
59+
this.options.isEnableAppLifecycleBreadcrumbs());
8760

88-
watcher =
89-
new LifecycleWatcher(
90-
scopes,
91-
this.options.getSessionTrackingIntervalMillis(),
92-
this.options.isEnableAutoSessionTracking(),
93-
this.options.isEnableAppLifecycleBreadcrumbs());
61+
AppState.getInstance().addAppStateListener(watcher);
62+
}
9463

95-
try {
96-
ProcessLifecycleOwner.get().getLifecycle().addObserver(watcher);
9764
options.getLogger().log(SentryLevel.DEBUG, "AppLifecycleIntegration installed.");
9865
addIntegrationToSdkVersion("AppLifecycle");
99-
} catch (Throwable e) {
100-
// This is to handle a potential 'AbstractMethodError' gracefully. The error is triggered in
101-
// connection with conflicting dependencies of the androidx.lifecycle.
102-
// //See the issue here: https://github.com/getsentry/sentry-java/pull/2228
103-
watcher = null;
104-
options
105-
.getLogger()
106-
.log(
107-
SentryLevel.ERROR,
108-
"AppLifecycleIntegration failed to get Lifecycle and could not be installed.",
109-
e);
11066
}
11167
}
11268

11369
private void removeObserver() {
114-
final @Nullable LifecycleWatcher watcherRef = watcher;
70+
final @Nullable LifecycleWatcher watcherRef;
71+
try (final ISentryLifecycleToken ignored = lock.acquire()) {
72+
watcherRef = watcher;
73+
watcher = null;
74+
}
75+
11576
if (watcherRef != null) {
116-
ProcessLifecycleOwner.get().getLifecycle().removeObserver(watcherRef);
77+
AppState.getInstance().removeAppStateListener(watcherRef);
11778
if (options != null) {
11879
options.getLogger().log(SentryLevel.DEBUG, "AppLifecycleIntegration removed.");
11980
}
12081
}
121-
watcher = null;
12282
}
12383

12484
@Override
12585
public void close() throws IOException {
126-
if (watcher == null) {
127-
return;
128-
}
129-
if (AndroidThreadChecker.getInstance().isMainThread()) {
130-
removeObserver();
131-
} else {
132-
// some versions of the androidx lifecycle-process require this to be executed on the main
133-
// thread.
134-
// avoid method refs on Android due to some issues with older AGP setups
135-
// noinspection Convert2MethodRef
136-
handler.post(() -> removeObserver());
137-
}
86+
removeObserver();
87+
// TODO: probably should move it to Scopes.close(), but that'd require a new interface and
88+
// different implementations for Java and Android. This is probably fine like this too, because
89+
// integrations are closed in the same place
90+
AppState.getInstance().unregisterLifecycleObserver();
13891
}
13992
}

0 commit comments

Comments
 (0)