Skip to content

Commit 70879c1

Browse files
committed
Reduce number of foreground checks and add maestro tests
1 parent 4164619 commit 70879c1

File tree

6 files changed

+398
-60
lines changed

6 files changed

+398
-60
lines changed

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

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import io.sentry.android.core.SentryAndroidOptions;
2222
import io.sentry.android.core.internal.util.FirstDrawDoneListener;
2323
import io.sentry.util.AutoClosableReentrantLock;
24+
import io.sentry.util.LazyEvaluator;
2425
import java.util.ArrayList;
2526
import java.util.Collections;
2627
import java.util.HashMap;
@@ -56,7 +57,14 @@ public enum AppStartType {
5657
new AutoClosableReentrantLock();
5758

5859
private @NotNull AppStartType appStartType = AppStartType.UNKNOWN;
59-
private boolean appLaunchedInForeground;
60+
private final LazyEvaluator<Boolean> appLaunchedInForeground =
61+
new LazyEvaluator<>(
62+
new LazyEvaluator.Evaluator<Boolean>() {
63+
@Override
64+
public @NotNull Boolean evaluate() {
65+
return ContextUtils.isForegroundImportance();
66+
}
67+
});
6068
private volatile long firstPostUptimeMillis = -1;
6169

6270
private final @NotNull TimeSpan appStartSpan;
@@ -90,7 +98,6 @@ public AppStartMetrics() {
9098
applicationOnCreate = new TimeSpan();
9199
contentProviderOnCreates = new HashMap<>();
92100
activityLifecycles = new ArrayList<>();
93-
appLaunchedInForeground = ContextUtils.isForegroundImportance();
94101
}
95102

96103
/**
@@ -141,12 +148,12 @@ public void setAppStartType(final @NotNull AppStartType appStartType) {
141148
}
142149

143150
public boolean isAppLaunchedInForeground() {
144-
return appLaunchedInForeground;
151+
return appLaunchedInForeground.getValue();
145152
}
146153

147154
@VisibleForTesting
148155
public void setAppLaunchedInForeground(final boolean appLaunchedInForeground) {
149-
this.appLaunchedInForeground = appLaunchedInForeground;
156+
this.appLaunchedInForeground.setValue(appLaunchedInForeground);
150157
}
151158

152159
/**
@@ -177,7 +184,7 @@ public void onAppStartSpansSent() {
177184
}
178185

179186
public boolean shouldSendStartMeasurements() {
180-
return shouldSendStartMeasurements && appLaunchedInForeground;
187+
return shouldSendStartMeasurements && appLaunchedInForeground.getValue();
181188
}
182189

183190
public long getClassLoadedUptimeMs() {
@@ -192,7 +199,7 @@ public long getClassLoadedUptimeMs() {
192199
final @NotNull SentryAndroidOptions options) {
193200
// If the app start type was never determined or app wasn't launched in foreground,
194201
// the app start is considered invalid
195-
if (appStartType != AppStartType.UNKNOWN && appLaunchedInForeground) {
202+
if (appStartType != AppStartType.UNKNOWN && appLaunchedInForeground.getValue()) {
196203
if (options.isEnablePerformanceV2()) {
197204
// Only started when sdk version is >= N
198205
final @NotNull TimeSpan appStartSpan = getAppStartTimeSpan();
@@ -213,6 +220,16 @@ public long getClassLoadedUptimeMs() {
213220
return new TimeSpan();
214221
}
215222

223+
@TestOnly
224+
void setFirstPostUptimeMillis(final long firstPostUptimeMillis) {
225+
this.firstPostUptimeMillis = firstPostUptimeMillis;
226+
}
227+
228+
@TestOnly
229+
long getFirstPostUptimeMillis() {
230+
return firstPostUptimeMillis;
231+
}
232+
216233
@TestOnly
217234
public void clear() {
218235
appStartType = AppStartType.UNKNOWN;
@@ -230,7 +247,7 @@ public void clear() {
230247
}
231248
appStartContinuousProfiler = null;
232249
appStartSamplingDecision = null;
233-
appLaunchedInForeground = false;
250+
appLaunchedInForeground.setValue(false);
234251
isCallbackRegistered = false;
235252
shouldSendStartMeasurements = true;
236253
firstDrawDone.set(false);
@@ -312,7 +329,7 @@ public void registerLifecycleCallbacks(final @NotNull Application application) {
312329
return;
313330
}
314331
isCallbackRegistered = true;
315-
appLaunchedInForeground = appLaunchedInForeground || ContextUtils.isForegroundImportance();
332+
appLaunchedInForeground.resetValue();
316333
application.registerActivityLifecycleCallbacks(instance);
317334
// We post on the main thread a task to post a check on the main thread. On Pixel devices
318335
// (possibly others) the first task posted on the main thread is called before the
@@ -335,7 +352,7 @@ private void checkCreateTimeOnMain() {
335352
() -> {
336353
// if no activity has ever been created, app was launched in background
337354
if (activeActivitiesCounter.get() == 0) {
338-
appLaunchedInForeground = false;
355+
appLaunchedInForeground.setValue(false);
339356

340357
// we stop the app start profilers, as they are useless and likely to timeout
341358
if (appStartProfiler != null && appStartProfiler.isRunning()) {
@@ -352,6 +369,7 @@ private void checkCreateTimeOnMain() {
352369

353370
@Override
354371
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
372+
final long activityCreatedUptimeMillis = SystemClock.uptimeMillis();
355373
CurrentActivityHolder.getInstance().setActivity(activity);
356374

357375
// the first activity determines the app start type
@@ -360,25 +378,27 @@ public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle saved
360378

361379
// If the app (process) was launched more than 1 minute ago, consider it a warm start
362380
final long durationSinceAppStartMillis = nowUptimeMs - appStartSpan.getStartUptimeMs();
363-
if (!appLaunchedInForeground || durationSinceAppStartMillis > TimeUnit.MINUTES.toMillis(1)) {
381+
if (!appLaunchedInForeground.getValue()
382+
|| durationSinceAppStartMillis > TimeUnit.MINUTES.toMillis(1)) {
364383
appStartType = AppStartType.WARM;
365-
366384
shouldSendStartMeasurements = true;
367385
appStartSpan.reset();
368-
appStartSpan.start();
369-
appStartSpan.setStartedAt(nowUptimeMs);
370-
CLASS_LOADED_UPTIME_MS = nowUptimeMs;
386+
appStartSpan.setStartedAt(activityCreatedUptimeMillis);
387+
CLASS_LOADED_UPTIME_MS = activityCreatedUptimeMillis;
371388
contentProviderOnCreates.clear();
372389
applicationOnCreate.reset();
373390
} else if (savedInstanceState != null) {
374391
appStartType = AppStartType.WARM;
375-
} else if (firstPostUptimeMillis > 0 && nowUptimeMs > firstPostUptimeMillis) {
392+
} else if (firstPostUptimeMillis != -1
393+
&& activityCreatedUptimeMillis > firstPostUptimeMillis) {
394+
// Application creation always queues Activity creation
395+
// So if Activity is created after our first measured post, it's a warm start
376396
appStartType = AppStartType.WARM;
377397
} else {
378398
appStartType = AppStartType.COLD;
379399
}
380400
}
381-
appLaunchedInForeground = true;
401+
appLaunchedInForeground.setValue(true);
382402
}
383403

384404
@Override
@@ -417,9 +437,9 @@ public void onActivityDestroyed(@NonNull Activity activity) {
417437

418438
final int remainingActivities = activeActivitiesCounter.decrementAndGet();
419439
// if the app is moving into background
420-
// as the next Activity is considered like a new app start
440+
// as the next onActivityCreated will treat it as a new warm app start
421441
if (remainingActivities == 0 && !activity.isChangingConfigurations()) {
422-
appLaunchedInForeground = false;
442+
appLaunchedInForeground.setValue(true);
423443
shouldSendStartMeasurements = true;
424444
firstDrawDone.set(false);
425445
}

0 commit comments

Comments
 (0)