Skip to content

Commit c39f7ef

Browse files
authored
Merge branch 'main' into dconeybe/dataconnect/WarningFixesTargetSdkEtAl
2 parents 5fd45b2 + 287d962 commit c39f7ef

File tree

3 files changed

+90
-9
lines changed

3 files changed

+90
-9
lines changed

firebase-perf/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Unreleased
22

3+
- [fixed] Fixed the behavior of app start traces on API 34+ devices. [#5920]
4+
35
# 22.0.1
46

57
- [fixed] Fixed an ANR on app launch. [#4831]

firebase-perf/src/main/java/com/google/firebase/perf/metrics/AppStartTrace.java

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ public class AppStartTrace implements ActivityLifecycleCallbacks, LifecycleObser
7575
private static final @NonNull Timer PERF_CLASS_LOAD_TIME = new Clock().getTime();
7676
private static final long MAX_LATENCY_BEFORE_UI_INIT = TimeUnit.MINUTES.toMicros(1);
7777

78+
// If the `mainThreadRunnableTime` was set within this duration, the assumption
79+
// is that it was called immediately before `onActivityCreated` in foreground starts on API 34+.
80+
// See b/339891952.
81+
private static final long MAX_BACKGROUND_RUNNABLE_DELAY = TimeUnit.MILLISECONDS.toMicros(50);
82+
7883
// Core pool size 0 allows threads to shut down if they're idle
7984
private static final int CORE_POOL_SIZE = 0;
8085
private static final int MAX_POOL_SIZE = 1; // Only need single thread
@@ -111,6 +116,9 @@ public class AppStartTrace implements ActivityLifecycleCallbacks, LifecycleObser
111116
private final @Nullable Timer processStartTime;
112117
private final @Nullable Timer firebaseClassLoadTime;
113118
private Timer onCreateTime = null;
119+
120+
// TODO(b/339891952): Explore simplifying Timers in app start trace to use timestamps.
121+
private Timer mainThreadRunnableTime = null;
114122
private Timer onStartTime = null;
115123
private Timer onResumeTime = null;
116124
private Timer firstForegroundTime = null;
@@ -319,8 +327,44 @@ private void recordOnDrawFrontOfQueue() {
319327
logExperimentTrace(this.experimentTtid);
320328
}
321329

330+
/**
331+
* Sets the `isStartedFromBackground` flag to `true` if the `mainThreadRunnableTime` was set
332+
* from the `StartFromBackgroundRunnable`.
333+
* <p>
334+
* If it's prior to API 34, it's always set to true if `mainThreadRunnableTime` was set.
335+
* <p>
336+
* If it's on or after API 34, and it was called less than `MAX_BACKGROUND_RUNNABLE_DELAY`
337+
* before `onActivityCreated`, the
338+
* assumption is that it was called immediately before the activity lifecycle callbacks in a
339+
* foreground start.
340+
* See b/339891952.
341+
*/
342+
private void resolveIsStartedFromBackground() {
343+
// If the mainThreadRunnableTime is null, either the runnable hasn't run, or this check has
344+
// already been made.
345+
if (mainThreadRunnableTime == null) {
346+
return;
347+
}
348+
349+
// If the `mainThreadRunnableTime` was set prior to API 34, it's always assumed that's it's
350+
// a background start.
351+
// Otherwise it's assumed to be a background start if the runnable was set more than
352+
// `MAX_BACKGROUND_RUNNABLE_DELAY`
353+
// before the first `onActivityCreated` call.
354+
// TODO(b/339891952): Investigate removing the API check.
355+
if ((Build.VERSION.SDK_INT < 34)
356+
|| (mainThreadRunnableTime.getDurationMicros() > MAX_BACKGROUND_RUNNABLE_DELAY)) {
357+
isStartedFromBackground = true;
358+
}
359+
360+
// Set this to null to prevent additional checks.
361+
mainThreadRunnableTime = null;
362+
}
363+
322364
@Override
323365
public synchronized void onActivityCreated(Activity activity, Bundle savedInstanceState) {
366+
resolveIsStartedFromBackground();
367+
324368
if (isStartedFromBackground || onCreateTime != null // An activity already called onCreate()
325369
) {
326370
return;
@@ -559,9 +603,9 @@ public static boolean isScreenOn(Context appContext) {
559603
/**
560604
* We use StartFromBackgroundRunnable to detect if app is started from background or foreground.
561605
* If app is started from background, we do not generate AppStart trace. This runnable is posted
562-
* to main UI thread from FirebasePerfEarly. If app is started from background, this runnable will
563-
* be executed before any activity's onCreate() method. If app is started from foreground,
564-
* activity's onCreate() method is executed before this runnable.
606+
* to main UI thread from FirebasePerfEarly. If `onActivityCreate` has never been called, we
607+
* record the timestamp - which allows `onActivityCreate` to determine whether it was a background
608+
* app start or not.
565609
*/
566610
public static class StartFromBackgroundRunnable implements Runnable {
567611
private final AppStartTrace trace;
@@ -572,9 +616,9 @@ public StartFromBackgroundRunnable(final AppStartTrace trace) {
572616

573617
@Override
574618
public void run() {
575-
// if no activity has ever been created.
619+
// Only set the `mainThreadRunnableTime` if `onActivityCreate` has never been called.
576620
if (trace.onCreateTime == null) {
577-
trace.isStartedFromBackground = true;
621+
trace.mainThreadRunnableTime = new Timer();
578622
}
579623
}
580624
}
@@ -614,7 +658,7 @@ Timer getOnResumeTime() {
614658
}
615659

616660
@VisibleForTesting
617-
void setIsStartFromBackground() {
618-
isStartedFromBackground = true;
661+
void setMainThreadRunnableTime(Timer timer) {
662+
mainThreadRunnableTime = timer;
619663
}
620664
}

firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import static org.mockito.ArgumentMatchers.isA;
1919
import static org.mockito.Mockito.doAnswer;
2020
import static org.mockito.Mockito.mock;
21+
import static org.mockito.Mockito.spy;
2122
import static org.mockito.Mockito.times;
2223
import static org.mockito.Mockito.verify;
2324
import static org.mockito.Mockito.when;
@@ -238,11 +239,44 @@ public void testDelayedAppStart() {
238239
}
239240

240241
@Test
241-
public void testStartFromBackground() {
242+
public void testStartFromBackground_within50ms() {
242243
FakeScheduledExecutorService fakeExecutorService = new FakeScheduledExecutorService();
244+
Timer fakeTimer = spy(new Timer(currentTime));
243245
AppStartTrace trace =
244246
new AppStartTrace(transportManager, clock, configResolver, fakeExecutorService);
245-
trace.setIsStartFromBackground();
247+
trace.registerActivityLifecycleCallbacks(appContext);
248+
trace.setMainThreadRunnableTime(fakeTimer);
249+
250+
// See AppStartTrace.MAX_BACKGROUND_RUNNABLE_DELAY.
251+
when(fakeTimer.getDurationMicros()).thenReturn(TimeUnit.MILLISECONDS.toMicros(50) - 1);
252+
trace.onActivityCreated(activity1, bundle);
253+
Assert.assertNotNull(trace.getOnCreateTime());
254+
++currentTime;
255+
trace.onActivityStarted(activity1);
256+
Assert.assertNotNull(trace.getOnStartTime());
257+
++currentTime;
258+
trace.onActivityResumed(activity1);
259+
Assert.assertNotNull(trace.getOnResumeTime());
260+
fakeExecutorService.runAll();
261+
// There should be a trace sent since the delay between the main thread and onActivityCreated
262+
// is limited.
263+
verify(transportManager, times(1))
264+
.log(
265+
traceArgumentCaptor.capture(),
266+
ArgumentMatchers.nullable(ApplicationProcessState.class));
267+
}
268+
269+
@Test
270+
public void testStartFromBackground_moreThan50ms() {
271+
FakeScheduledExecutorService fakeExecutorService = new FakeScheduledExecutorService();
272+
Timer fakeTimer = spy(new Timer(currentTime));
273+
AppStartTrace trace =
274+
new AppStartTrace(transportManager, clock, configResolver, fakeExecutorService);
275+
trace.registerActivityLifecycleCallbacks(appContext);
276+
trace.setMainThreadRunnableTime(fakeTimer);
277+
278+
// See AppStartTrace.MAX_BACKGROUND_RUNNABLE_DELAY.
279+
when(fakeTimer.getDurationMicros()).thenReturn(TimeUnit.MILLISECONDS.toMicros(50) + 1);
246280
trace.onActivityCreated(activity1, bundle);
247281
Assert.assertNull(trace.getOnCreateTime());
248282
++currentTime;
@@ -252,6 +286,7 @@ public void testStartFromBackground() {
252286
trace.onActivityResumed(activity1);
253287
Assert.assertNull(trace.getOnResumeTime());
254288
// There should be no trace sent.
289+
fakeExecutorService.runAll();
255290
verify(transportManager, times(0))
256291
.log(
257292
traceArgumentCaptor.capture(),

0 commit comments

Comments
 (0)