Skip to content

Commit e0d714c

Browse files
committed
Fix headless app-start idle scheduling
1 parent 41ebd25 commit e0d714c

2 files changed

Lines changed: 39 additions & 3 deletions

File tree

sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ public enum AppStartType {
8989
private boolean shouldSendStartMeasurements = true;
9090
private final AtomicInteger activeActivitiesCounter = new AtomicInteger();
9191
private final AtomicBoolean firstDrawDone = new AtomicBoolean(false);
92+
private final AtomicBoolean headlessAppStartCheckScheduled = new AtomicBoolean(false);
93+
private final AtomicBoolean headlessAppStartListenerNotified = new AtomicBoolean(false);
9294
private volatile @Nullable HeadlessAppStartListener headlessAppStartListener;
9395
private @Nullable SentryId appStartTraceId;
9496
private @Nullable ApplicationStartInfo cachedStartInfo;
@@ -171,6 +173,12 @@ public void setAppLaunchedInForeground(final boolean appLaunchedInForeground) {
171173

172174
public void setHeadlessAppStartListener(final @Nullable HeadlessAppStartListener listener) {
173175
this.headlessAppStartListener = listener;
176+
if (listener != null
177+
&& isCallbackRegistered
178+
&& activeActivitiesCounter.get() == 0
179+
&& !firstDrawDone.get()) {
180+
scheduleHeadlessAppStartCheckOnMain();
181+
}
174182
}
175183

176184
/** Trace ID from a headless app start transaction, to be reused by a later activity. */
@@ -295,6 +303,8 @@ public void clear() {
295303
firstDrawDone.set(false);
296304
activeActivitiesCounter.set(0);
297305
firstIdle = -1;
306+
headlessAppStartCheckScheduled.set(false);
307+
headlessAppStartListenerNotified.set(false);
298308
headlessAppStartListener = null;
299309
appStartTraceId = null;
300310
cachedStartInfo = null;
@@ -398,16 +408,22 @@ public void registerLifecycleCallbacks(final @NotNull Application application) {
398408
}
399409
}
400410

401-
scheduleHeadlessAppStartCheckOnMain();
411+
if (appStartType == AppStartType.UNKNOWN || headlessAppStartListener != null) {
412+
scheduleHeadlessAppStartCheckOnMain();
413+
}
402414
}
403415

404416
private void scheduleHeadlessAppStartCheckOnMain() {
417+
if (!headlessAppStartCheckScheduled.compareAndSet(false, true)) {
418+
return;
419+
}
405420
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
406421
Looper.getMainLooper()
407422
.getQueue()
408423
.addIdleHandler(
409424
() -> {
410425
firstIdle = SystemClock.uptimeMillis();
426+
headlessAppStartCheckScheduled.set(false);
411427
handleHeadlessAppStartIfNeededOnMain();
412428
return false;
413429
});
@@ -416,7 +432,11 @@ private void scheduleHeadlessAppStartCheckOnMain() {
416432
handler.post(
417433
() -> {
418434
firstIdle = SystemClock.uptimeMillis();
419-
handler.post(() -> handleHeadlessAppStartIfNeededOnMain());
435+
handler.post(
436+
() -> {
437+
headlessAppStartCheckScheduled.set(false);
438+
handleHeadlessAppStartIfNeededOnMain();
439+
});
420440
});
421441
}
422442
}
@@ -446,7 +466,7 @@ private void handleHeadlessAppStartIfNeededOnMain() {
446466
}
447467

448468
final @Nullable HeadlessAppStartListener listener = headlessAppStartListener;
449-
if (listener != null) {
469+
if (listener != null && headlessAppStartListenerNotified.compareAndSet(false, true)) {
450470
resolveHeadlessAppStartEndTime();
451471
listener.onHeadlessAppStart();
452472
}

sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTestApi35.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,22 @@ class AppStartMetricsTestApi35 {
5050
assertEquals(AppStartMetrics.AppStartType.COLD, AppStartMetrics.getInstance().appStartType)
5151
}
5252

53+
@Test
54+
fun `known ApplicationStartInfo type without listener does not schedule headless check`() {
55+
val mockStartInfo = mock<ApplicationStartInfo>()
56+
whenever(mockStartInfo.startupState).thenReturn(ApplicationStartInfo.STARTUP_STATE_STARTED)
57+
whenever(mockStartInfo.startType).thenReturn(ApplicationStartInfo.START_TYPE_COLD)
58+
SentryShadowActivityManager.setHistoricalProcessStartReasons(listOf(mockStartInfo))
59+
val metrics = AppStartMetrics.getInstance()
60+
61+
val app = ApplicationProvider.getApplicationContext<Application>()
62+
metrics.registerLifecycleCallbacks(app)
63+
waitForMainLooperIdle()
64+
65+
assertEquals(AppStartMetrics.AppStartType.COLD, metrics.appStartType)
66+
assertEquals(-1, metrics.firstIdle)
67+
}
68+
5369
@Test
5470
fun `detects warm start using ApplicationStartInfo on API 35`() {
5571
val mockStartInfo = mock<ApplicationStartInfo>()

0 commit comments

Comments
 (0)