Skip to content

Commit 3751fac

Browse files
authored
Merge a1fe411 into d8cb105
2 parents d8cb105 + a1fe411 commit 3751fac

File tree

3 files changed

+85
-9
lines changed

3 files changed

+85
-9
lines changed

firebase-perf/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Unreleased
22
* [fixed] Fixed an ANR on app launch. [#4831]
3+
* [fixed] Fixed app start traces on API 34+. [#5920]
34

45
# 22.0.0
56
* [changed] **Breaking Change**: Updated minSdkVersion to API level 23 or higher.

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

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ 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+
private static final long MAX_BACKGROUND_RUNNABLE_DELAY = TimeUnit.MILLISECONDS.toMicros(100);
81+
7882
// Core pool size 0 allows threads to shut down if they're idle
7983
private static final int CORE_POOL_SIZE = 0;
8084
private static final int MAX_POOL_SIZE = 1; // Only need single thread
@@ -111,6 +115,8 @@ public class AppStartTrace implements ActivityLifecycleCallbacks, LifecycleObser
111115
private final @Nullable Timer processStartTime;
112116
private final @Nullable Timer firebaseClassLoadTime;
113117
private Timer onCreateTime = null;
118+
119+
private Timer mainThreadRunnableTime = null;
114120
private Timer onStartTime = null;
115121
private Timer onResumeTime = null;
116122
private Timer firstForegroundTime = null;
@@ -319,8 +325,44 @@ private void recordOnDrawFrontOfQueue() {
319325
logExperimentTrace(this.experimentTtid);
320326
}
321327

328+
/**
329+
* Sets the `isStartedFromBackground` flag to `true` if the `mainThreadRunnableTime` was set
330+
* from the `StartFromBackgroundRunnable`.
331+
* <p>
332+
* If it's prior to API 34, it's always set to true if `mainThreadRunnableTime` was set.
333+
* <p>
334+
* If it's on or after API 34, and it was called less than `MAX_BACKGROUND_RUNNABLE_DELAY`
335+
* before `onActivityCreated`, the
336+
* assumption is that it was called immediately before the activity lifecycle callbacks in a
337+
* foreground start.
338+
* See https://github.com/firebase/firebase-android-sdk/issues/5920.
339+
*/
340+
private void resolveIsStartedFromBackground() {
341+
// If the mainThreadRunnableTime is null, either the runnable hasn't run, or this check has
342+
// already been made.
343+
if (mainThreadRunnableTime == null) {
344+
return;
345+
}
346+
347+
// If the `mainThreadRunnableTime` was set prior to API 34, it's always assumed that's it's
348+
// a background start.
349+
// Otherwise it's assumed to be a background start if the runnable was set more than
350+
// `MAX_BACKGROUND_RUNNABLE_DELAY`
351+
// before the first `onActivityCreated` call.
352+
// TODO(b/339891952): Investigate removing the API check, and setting a more precise delay.
353+
if ((Build.VERSION.SDK_INT < 34)
354+
|| (mainThreadRunnableTime.getDurationMicros() > MAX_BACKGROUND_RUNNABLE_DELAY)) {
355+
isStartedFromBackground = true;
356+
}
357+
358+
// Set this to null to prevent additional checks.
359+
mainThreadRunnableTime = null;
360+
}
361+
322362
@Override
323363
public synchronized void onActivityCreated(Activity activity, Bundle savedInstanceState) {
364+
resolveIsStartedFromBackground();
365+
324366
if (isStartedFromBackground || onCreateTime != null // An activity already called onCreate()
325367
) {
326368
return;
@@ -559,9 +601,9 @@ public static boolean isScreenOn(Context appContext) {
559601
/**
560602
* We use StartFromBackgroundRunnable to detect if app is started from background or foreground.
561603
* 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.
604+
* to main UI thread from FirebasePerfEarly. If `onActivityCreate` has never been called, we
605+
* record the timestamp - which allows `onActivityCreate` to determine whether it was a background
606+
* app start or not.
565607
*/
566608
public static class StartFromBackgroundRunnable implements Runnable {
567609
private final AppStartTrace trace;
@@ -572,9 +614,9 @@ public StartFromBackgroundRunnable(final AppStartTrace trace) {
572614

573615
@Override
574616
public void run() {
575-
// if no activity has ever been created.
617+
// Only set the `mainThreadRunnableTime` if `onActivityCreate` has never been called.
576618
if (trace.onCreateTime == null) {
577-
trace.isStartedFromBackground = true;
619+
trace.mainThreadRunnableTime = new Timer();
578620
}
579621
}
580622
}
@@ -614,7 +656,7 @@ Timer getOnResumeTime() {
614656
}
615657

616658
@VisibleForTesting
617-
void setIsStartFromBackground() {
618-
isStartedFromBackground = true;
659+
void setMainThreadRunnableTime(Timer timer) {
660+
mainThreadRunnableTime = timer;
619661
}
620662
}

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

Lines changed: 35 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,42 @@ public void testDelayedAppStart() {
238239
}
239240

240241
@Test
241-
public void testStartFromBackground() {
242+
public void testStartFromBackground_within100ms() {
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+
when(fakeTimer.getDurationMicros()).thenReturn(99L);
251+
trace.onActivityCreated(activity1, bundle);
252+
Assert.assertNotNull(trace.getOnCreateTime());
253+
++currentTime;
254+
trace.onActivityStarted(activity1);
255+
Assert.assertNotNull(trace.getOnStartTime());
256+
++currentTime;
257+
trace.onActivityResumed(activity1);
258+
Assert.assertNotNull(trace.getOnResumeTime());
259+
fakeExecutorService.runAll();
260+
// There should be a trace sent since the delay between the main thread and onActivityCreated
261+
// is limited.
262+
verify(transportManager, times(1))
263+
.log(
264+
traceArgumentCaptor.capture(),
265+
ArgumentMatchers.nullable(ApplicationProcessState.class));
266+
}
267+
268+
@Test
269+
public void testStartFromBackground_moreThan100ms() {
270+
FakeScheduledExecutorService fakeExecutorService = new FakeScheduledExecutorService();
271+
Timer fakeTimer = spy(new Timer(currentTime));
272+
AppStartTrace trace =
273+
new AppStartTrace(transportManager, clock, configResolver, fakeExecutorService);
274+
trace.registerActivityLifecycleCallbacks(appContext);
275+
trace.setMainThreadRunnableTime(fakeTimer);
276+
277+
when(fakeTimer.getDurationMicros()).thenReturn(TimeUnit.MILLISECONDS.toMicros(100) + 1);
246278
trace.onActivityCreated(activity1, bundle);
247279
Assert.assertNull(trace.getOnCreateTime());
248280
++currentTime;
@@ -252,6 +284,7 @@ public void testStartFromBackground() {
252284
trace.onActivityResumed(activity1);
253285
Assert.assertNull(trace.getOnResumeTime());
254286
// There should be no trace sent.
287+
fakeExecutorService.runAll();
255288
verify(transportManager, times(0))
256289
.log(
257290
traceArgumentCaptor.capture(),

0 commit comments

Comments
 (0)