SentryFrameMetricsCollector created and started its HandlerThread in the constructor and then called new Handler(handlerThread.getLooper()). getLooper() blocks the caller until the new thread's looper is ready — and the collector is constructed on the main thread during SentryAndroid.init (loadDefaultAndMetadataOptions).
The customer-provided Perfetto trace (sentryinvest) showed the main thread blocking ~5.75 ms on HandlerThread.getLooper() during init via SentryFrameMetricsCollector.<init>.
Fix
The handler is only used by trackCurrentWindow(), which no-ops until a listener is registered via startCollection(). Start the HandlerThread lazily on the first startCollection() (profiling / SpanFrameMetricsCollector). The constructor still registers the activity-lifecycle callback (current-window tracking unchanged) but no longer blocks. Apps that never collect frame metrics never start the thread at all.
Reusing the SDK's shared executor isn't viable: addOnFrameMetricsAvailableListener requires a dedicated non-main-thread Handler (Looper), which the shared ScheduledExecutorService doesn't provide.
Pixel 3 benchmark (Android 12, ART method trace → Perfetto trace_processor)
Constructor HandlerThread.getLooper() / blocking Object.wait() slices: old = 1 / 1 → new = 0 / 0. The synchronous main-thread wait is removed from construction.
PR
#5641
SentryFrameMetricsCollectorcreated and started itsHandlerThreadin the constructor and then callednew Handler(handlerThread.getLooper()).getLooper()blocks the caller until the new thread's looper is ready — and the collector is constructed on the main thread duringSentryAndroid.init(loadDefaultAndMetadataOptions).The customer-provided Perfetto trace (
sentryinvest) showed the main thread blocking ~5.75 ms onHandlerThread.getLooper()during init viaSentryFrameMetricsCollector.<init>.Fix
The
handleris only used bytrackCurrentWindow(), which no-ops until a listener is registered viastartCollection(). Start theHandlerThreadlazily on the firststartCollection()(profiling /SpanFrameMetricsCollector). The constructor still registers the activity-lifecycle callback (current-window tracking unchanged) but no longer blocks. Apps that never collect frame metrics never start the thread at all.Reusing the SDK's shared executor isn't viable:
addOnFrameMetricsAvailableListenerrequires a dedicated non-main-threadHandler(Looper), which the sharedScheduledExecutorServicedoesn't provide.Pixel 3 benchmark (Android 12, ART method trace → Perfetto trace_processor)
Constructor
HandlerThread.getLooper()/ blockingObject.wait()slices: old = 1 / 1 → new = 0 / 0. The synchronous main-thread wait is removed from construction.PR
#5641