Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Performance

- Start the frame metrics thread lazily on first collection instead of during SDK init ([#5641](https://github.com/getsentry/sentry-java/pull/5641))

## 8.45.0

### Features
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@
import android.view.Window;
import androidx.annotation.RequiresApi;
import io.sentry.ILogger;
import io.sentry.ISentryLifecycleToken;
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.SentryUUID;
import io.sentry.android.core.BuildInfoProvider;
import io.sentry.android.core.ContextUtils;
import io.sentry.android.core.SentryFramesDelayResult;
import io.sentry.util.AutoClosableReentrantLock;
import io.sentry.util.Objects;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
Expand All @@ -45,7 +47,8 @@ public final class SentryFrameMetricsCollector implements Application.ActivityLi
private final @NotNull Set<Window> trackedWindows = new CopyOnWriteArraySet<>();

private final @NotNull ILogger logger;
private @Nullable Handler handler;
private volatile @Nullable Handler handler;
private final @NotNull AutoClosableReentrantLock handlerLock = new AutoClosableReentrantLock();
private @Nullable WeakReference<Window> currentWindow;
private final @NotNull Map<String, FrameMetricsCollectorListener> listenerMap =
new ConcurrentHashMap<>();
Expand Down Expand Up @@ -113,12 +116,8 @@ public SentryFrameMetricsCollector(
}
isAvailable = true;

HandlerThread handlerThread =
new HandlerThread("io.sentry.android.core.internal.util.SentryFrameMetricsCollector");
handlerThread.setUncaughtExceptionHandler(
(thread, e) -> logger.log(SentryLevel.ERROR, "Error during frames measurements.", e));
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
// The frame metrics HandlerThread is started lazily on the first startCollection() call.
// Starting it here would block the main thread on HandlerThread.getLooper() during SDK init.

// We have to register the lifecycle callback, even if no profile is started, otherwise when we
// start a profile, we wouldn't have the current activity and couldn't get the frameMetrics.
Expand Down Expand Up @@ -281,12 +280,34 @@ public void onActivityDestroyed(@NotNull Activity activity) {}
if (!isAvailable) {
return null;
}
ensureHandlerThreadStarted();
final String uid = SentryUUID.generateSentryId();
listenerMap.put(uid, listener);
trackCurrentWindow();
return uid;
}

/**
* Lazily starts the background HandlerThread used to receive frame metrics. Deferred out of the
* constructor because {@link HandlerThread#getLooper()} blocks the caller (the main thread during
* SDK init) until the thread is ready, and the handler is only needed once collection starts.
*/
private void ensureHandlerThreadStarted() {
if (handler != null) {
return;
}
try (final @NotNull ISentryLifecycleToken ignored = handlerLock.acquire()) {
if (handler == null) {
final HandlerThread handlerThread =
new HandlerThread("io.sentry.android.core.internal.util.SentryFrameMetricsCollector");
handlerThread.setUncaughtExceptionHandler(
(thread, e) -> logger.log(SentryLevel.ERROR, "Error during frames measurements.", e));
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
}
}
}

public void stopCollection(final @Nullable String listenerId) {
if (!isAvailable) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,16 @@ class SentryFrameMetricsCollectorTest {
assertNotNull(id)
}

@Test
fun `handler thread is started lazily on first startCollection`() {
val collector = fixture.getSut(context)
// not started during construction (would block the main thread on getLooper at SDK init)
assertNull(collector.getProperty<Handler?>("handler"))

collector.startCollection(mock())
assertNotNull(collector.getProperty<Handler?>("handler"))
}

@Test
fun `collector calls addOnFrameMetricsAvailableListener when an activity starts`() {
val collector = fixture.getSut(context)
Expand Down
Loading