Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Improvements

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

### Fixes

Expand Down
12 changes: 10 additions & 2 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,17 @@ 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 close ()V
public static fun getInstance ()Lio/sentry/android/core/AppState;
public fun isInBackground ()Ljava/lang/Boolean;
}

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/BuildConfig {
public static final field BUILD_TYPE Ljava/lang/String;
public static final field DEBUG Z
Expand Down Expand Up @@ -422,11 +428,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 @@ -128,6 +128,7 @@ static void loadDefaultAndMetadataOptions(
options.setCacheDirPath(getCacheDir(context).getAbsolutePath());

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

@TestOnly
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