18
18
import android .app .Application ;
19
19
import android .app .Application .ActivityLifecycleCallbacks ;
20
20
import android .content .Context ;
21
+ import android .os .Build ;
21
22
import android .os .Bundle ;
23
+ import android .os .Process ;
24
+ import android .view .View ;
22
25
import androidx .annotation .Keep ;
23
26
import androidx .annotation .NonNull ;
24
27
import androidx .annotation .Nullable ;
25
28
import com .google .android .gms .common .util .VisibleForTesting ;
29
+ import com .google .firebase .perf .config .ConfigResolver ;
26
30
import com .google .firebase .perf .logging .AndroidLogger ;
27
31
import com .google .firebase .perf .provider .FirebasePerfProvider ;
28
32
import com .google .firebase .perf .session .PerfSession ;
29
33
import com .google .firebase .perf .session .SessionManager ;
30
34
import com .google .firebase .perf .transport .TransportManager ;
31
35
import com .google .firebase .perf .util .Clock ;
32
36
import com .google .firebase .perf .util .Constants ;
37
+ import com .google .firebase .perf .util .FirstDrawDoneListener ;
33
38
import com .google .firebase .perf .util .Timer ;
34
39
import com .google .firebase .perf .v1 .ApplicationProcessState ;
35
40
import com .google .firebase .perf .v1 .TraceMetric ;
@@ -69,6 +74,7 @@ public class AppStartTrace implements ActivityLifecycleCallbacks {
69
74
private boolean isRegisteredForLifecycleCallbacks = false ;
70
75
private final TransportManager transportManager ;
71
76
private final Clock clock ;
77
+ private final ConfigResolver configResolver ;
72
78
private Context appContext ;
73
79
/**
74
80
* The first time onCreate() of any activity is called, the activity is saved as launchActivity.
@@ -89,6 +95,7 @@ public class AppStartTrace implements ActivityLifecycleCallbacks {
89
95
private Timer onCreateTime = null ;
90
96
private Timer onStartTime = null ;
91
97
private Timer onResumeTime = null ;
98
+ private Timer firstDrawDone = null ;
92
99
93
100
private PerfSession startSession ;
94
101
private boolean isStartedFromBackground = false ;
@@ -134,12 +141,13 @@ static AppStartTrace getInstance(TransportManager transportManager, Clock clock)
134
141
new AppStartTrace (
135
142
transportManager ,
136
143
clock ,
144
+ ConfigResolver .getInstance (),
137
145
new ThreadPoolExecutor (
138
146
CORE_POOL_SIZE ,
139
147
MAX_POOL_SIZE ,
140
148
/* keepAliveTime= */ MAX_LATENCY_BEFORE_UI_INIT + 10 ,
141
149
TimeUnit .SECONDS ,
142
- new LinkedBlockingQueue <>(1 )));
150
+ new LinkedBlockingQueue <>()));
143
151
}
144
152
}
145
153
}
@@ -149,9 +157,11 @@ static AppStartTrace getInstance(TransportManager transportManager, Clock clock)
149
157
AppStartTrace (
150
158
@ NonNull TransportManager transportManager ,
151
159
@ NonNull Clock clock ,
160
+ @ NonNull ConfigResolver configResolver ,
152
161
@ NonNull ExecutorService executorService ) {
153
162
this .transportManager = transportManager ;
154
163
this .clock = clock ;
164
+ this .configResolver = configResolver ;
155
165
this .executorService = executorService ;
156
166
}
157
167
@@ -178,6 +188,33 @@ public synchronized void unregisterActivityLifecycleCallbacks() {
178
188
isRegisteredForLifecycleCallbacks = false ;
179
189
}
180
190
191
+ /**
192
+ * Gets the timetamp that marks the beginning of app start, currently defined as the beginning of
193
+ * BIND_APPLICATION. Fallback to class-load time of {@link FirebasePerfProvider} when API < 24.
194
+ *
195
+ * @return {@link Timer} at the beginning of app start by Fireperf definition.
196
+ */
197
+ private static Timer getStartTimer () {
198
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .N ) {
199
+ return Timer .ofElapsedRealtime (Process .getStartElapsedRealtime ());
200
+ }
201
+ return FirebasePerfProvider .getAppStartTime ();
202
+ }
203
+
204
+ private void recordFirstDrawDone () {
205
+ if (firstDrawDone != null ) {
206
+ return ;
207
+ }
208
+ this .firstDrawDone = clock .getTime ();
209
+ executorService .execute (
210
+ () -> this .logColdStart (getStartTimer (), this .firstDrawDone , this .startSession ));
211
+
212
+ if (isRegisteredForLifecycleCallbacks ) {
213
+ // After AppStart trace is queued to be logged, we can unregister this callback.
214
+ unregisterActivityLifecycleCallbacks ();
215
+ }
216
+ }
217
+
181
218
@ Override
182
219
public synchronized void onActivityCreated (Activity activity , Bundle savedInstanceState ) {
183
220
if (isStartedFromBackground || onCreateTime != null // An activity already called onCreate()
@@ -206,9 +243,18 @@ public synchronized void onActivityStarted(Activity activity) {
206
243
207
244
@ Override
208
245
public synchronized void onActivityResumed (Activity activity ) {
209
- if (isStartedFromBackground
210
- || onResumeTime != null // An activity already called onResume()
211
- || isTooLateToInitUI ) {
246
+ if (isStartedFromBackground || isTooLateToInitUI ) {
247
+ return ;
248
+ }
249
+
250
+ // Shadow-launch experiment of new app start time
251
+ final boolean isExperimentTTIDEnabled = configResolver .getIsExperimentTTIDEnabled ();
252
+ if (isExperimentTTIDEnabled ) {
253
+ View rootView = activity .findViewById (android .R .id .content );
254
+ FirstDrawDoneListener .registerForNextDraw (rootView , this ::recordFirstDrawDone );
255
+ }
256
+
257
+ if (onResumeTime != null ) { // An activity already called onResume()
212
258
return ;
213
259
}
214
260
@@ -228,12 +274,30 @@ public synchronized void onActivityResumed(Activity activity) {
228
274
// Log the app start trace in a non-main thread.
229
275
executorService .execute (this ::logAppStartTrace );
230
276
231
- if (isRegisteredForLifecycleCallbacks ) {
277
+ if (! isExperimentTTIDEnabled && isRegisteredForLifecycleCallbacks ) {
232
278
// After AppStart trace is logged, we can unregister this callback.
233
279
unregisterActivityLifecycleCallbacks ();
234
280
}
235
281
}
236
282
283
+ private void logColdStart (Timer start , Timer end , PerfSession session ) {
284
+ TraceMetric .Builder metric =
285
+ TraceMetric .newBuilder ()
286
+ .setName ("_experiment_app_start_ttid" )
287
+ .setClientStartTimeUs (start .getMicros ())
288
+ .setDurationUs (start .getDurationMicros (end ));
289
+
290
+ TraceMetric .Builder subtrace =
291
+ TraceMetric .newBuilder ()
292
+ .setName ("_experiment_classLoadTime" )
293
+ .setClientStartTimeUs (FirebasePerfProvider .getAppStartTime ().getMicros ())
294
+ .setDurationUs (FirebasePerfProvider .getAppStartTime ().getDurationMicros (end ));
295
+
296
+ metric .addSubtraces (subtrace ).addPerfSessions (this .startSession .build ());
297
+
298
+ transportManager .log (metric .build (), ApplicationProcessState .FOREGROUND_BACKGROUND );
299
+ }
300
+
237
301
private void logAppStartTrace () {
238
302
TraceMetric .Builder metric =
239
303
TraceMetric .newBuilder ()
0 commit comments