20
20
import android .content .Context ;
21
21
import android .os .Bundle ;
22
22
import androidx .annotation .NonNull ;
23
- import androidx .core .app .FrameMetricsAggregator ;
24
23
import androidx .fragment .app .FragmentActivity ;
25
24
import com .google .android .gms .common .util .VisibleForTesting ;
26
25
import com .google .firebase .perf .config .ConfigResolver ;
27
26
import com .google .firebase .perf .logging .AndroidLogger ;
28
- import com .google .firebase .perf .metrics .FrameMetricsCalculator ;
27
+ import com .google .firebase .perf .metrics .FrameMetricsCalculator . PerfFrameMetrics ;
29
28
import com .google .firebase .perf .metrics .Trace ;
30
29
import com .google .firebase .perf .session .SessionManager ;
31
30
import com .google .firebase .perf .transport .TransportManager ;
32
31
import com .google .firebase .perf .util .Clock ;
33
32
import com .google .firebase .perf .util .Constants ;
34
33
import com .google .firebase .perf .util .Constants .CounterNames ;
34
+ import com .google .firebase .perf .util .Optional ;
35
35
import com .google .firebase .perf .util .ScreenTraceUtil ;
36
36
import com .google .firebase .perf .util .Timer ;
37
37
import com .google .firebase .perf .v1 .ApplicationProcessState ;
49
49
public class AppStateMonitor implements ActivityLifecycleCallbacks {
50
50
51
51
private static final AndroidLogger logger = AndroidLogger .getInstance ();
52
- private static final String FRAME_METRICS_AGGREGATOR_CLASSNAME =
53
- "androidx.core.app.FrameMetricsAggregator" ;
54
52
55
53
private static volatile AppStateMonitor instance ;
56
- private static boolean hasFrameMetricsAggregator = false ;
57
54
58
55
private final WeakHashMap <Activity , Boolean > activityToResumedMap = new WeakHashMap <>();
56
+ private final WeakHashMap <Activity , FrameMetricsRecorder > activityToRecorderMap =
57
+ new WeakHashMap <>();
58
+
59
+ // Map for holding the fragment state monitor to remove receiving the fragment state callbacks
60
+ private final WeakHashMap <Activity , FragmentStateMonitor > activityToFragmentStateMonitorMap =
61
+ new WeakHashMap <>();
59
62
private final WeakHashMap <Activity , Trace > activityToScreenTraceMap = new WeakHashMap <>();
60
63
private final Map <String , Long > metricToCountMap = new HashMap <>();
61
64
private final Set <WeakReference <AppStateCallback >> appStateSubscribers = new HashSet <>();
@@ -67,8 +70,7 @@ public class AppStateMonitor implements ActivityLifecycleCallbacks {
67
70
private final TransportManager transportManager ;
68
71
private final ConfigResolver configResolver ;
69
72
private final Clock clock ;
70
-
71
- private FrameMetricsAggregator frameMetricsAggregator ;
73
+ private final boolean screenPerformanceRecordingSupported ;
72
74
73
75
private Timer resumeTime ; // The time app comes to foreground
74
76
private Timer stopTime ; // The time app goes to background
@@ -94,18 +96,19 @@ public static AppStateMonitor getInstance() {
94
96
transportManager ,
95
97
clock ,
96
98
ConfigResolver .getInstance (),
97
- hasFrameMetricsAggregatorClass () ? new FrameMetricsAggregator () : null );
99
+ isScreenPerformanceRecordingSupported () );
98
100
}
99
101
102
+ @ VisibleForTesting
100
103
AppStateMonitor (
101
104
TransportManager transportManager ,
102
105
Clock clock ,
103
106
ConfigResolver configResolver ,
104
- FrameMetricsAggregator frameMetricsAggregator ) {
107
+ boolean screenPerformanceRecordingSupported ) {
105
108
this .transportManager = transportManager ;
106
109
this .clock = clock ;
107
110
this .configResolver = configResolver ;
108
- this .frameMetricsAggregator = frameMetricsAggregator ;
111
+ this .screenPerformanceRecordingSupported = screenPerformanceRecordingSupported ;
109
112
}
110
113
111
114
public synchronized void registerActivityLifecycleCallbacks (Context context ) {
@@ -149,28 +152,64 @@ public void incrementTsnsCount(int value) {
149
152
tsnsCount .addAndGet (value );
150
153
}
151
154
152
- @ Override
153
- public void onActivityCreated (Activity activity , Bundle savedInstanceState ) {
155
+ // Starts tracking the frame metrics for an activity.
156
+ private void startFrameMonitoring (Activity activity ) {
154
157
if (isScreenTraceSupported () && configResolver .isPerformanceMonitoringEnabled ()) {
158
+ FrameMetricsRecorder recorder = new FrameMetricsRecorder (activity );
159
+ activityToRecorderMap .put (activity , recorder );
155
160
if (activity instanceof FragmentActivity ) {
161
+ FragmentStateMonitor fragmentStateMonitor =
162
+ new FragmentStateMonitor (clock , transportManager , this , recorder );
163
+ activityToFragmentStateMonitorMap .put (activity , fragmentStateMonitor );
156
164
FragmentActivity fragmentActivity = (FragmentActivity ) activity ;
157
165
fragmentActivity
158
166
.getSupportFragmentManager ()
159
- .registerFragmentLifecycleCallbacks (
160
- new FragmentStateMonitor (clock , transportManager , this , frameMetricsAggregator ),
161
- true );
167
+ .registerFragmentLifecycleCallbacks (fragmentStateMonitor , true );
162
168
}
163
169
}
164
170
}
165
171
166
172
@ Override
167
- public void onActivityDestroyed (Activity activity ) {}
173
+ public void onActivityCreated (Activity activity , Bundle savedInstanceState ) {
174
+ startFrameMonitoring (activity );
175
+ }
168
176
169
177
@ Override
170
- public synchronized void onActivityStarted (Activity activity ) {}
178
+ public void onActivityDestroyed (Activity activity ) {
179
+ // Dereference FrameMetricsRecorder from the map because it holds an Activity reference
180
+ activityToRecorderMap .remove (activity );
181
+ // Dereference FragmentStateMonitor because it holds a FrameMetricsRecorder reference
182
+ if (activityToFragmentStateMonitorMap .containsKey (activity )) {
183
+ FragmentActivity fragmentActivity = (FragmentActivity ) activity ;
184
+ fragmentActivity
185
+ .getSupportFragmentManager ()
186
+ .unregisterFragmentLifecycleCallbacks (activityToFragmentStateMonitorMap .remove (activity ));
187
+ }
188
+ }
189
+
190
+ @ Override
191
+ public synchronized void onActivityStarted (Activity activity ) {
192
+ if (isScreenTraceSupported () && configResolver .isPerformanceMonitoringEnabled ()) {
193
+ if (!activityToRecorderMap .containsKey (activity )) {
194
+ // If performance monitoring is disabled at start and enabled at runtime, start monitoring
195
+ // the activity as the app comes to foreground.
196
+ startFrameMonitoring (activity );
197
+ }
198
+ // Starts recording frame metrics for this activity.
199
+ activityToRecorderMap .get (activity ).start ();
200
+ // Start the Trace
201
+ Trace screenTrace = new Trace (getScreenTraceName (activity ), transportManager , clock , this );
202
+ screenTrace .start ();
203
+ activityToScreenTraceMap .put (activity , screenTrace );
204
+ }
205
+ }
171
206
172
207
@ Override
173
208
public synchronized void onActivityStopped (Activity activity ) {
209
+ if (isScreenTraceSupported ()) {
210
+ sendScreenTrace (activity );
211
+ }
212
+
174
213
// Last activity has its onActivityStopped called, the app goes to background.
175
214
if (activityToResumedMap .containsKey (activity )) {
176
215
activityToResumedMap .remove (activity );
@@ -211,20 +250,6 @@ public synchronized void onActivityResumed(Activity activity) {
211
250
// current activity was paused then resumed without onStop, for example by an AlertDialog
212
251
activityToResumedMap .put (activity , true );
213
252
}
214
-
215
- // Screen trace is after session update so the sessionId is not added twice to the Trace
216
- if (isScreenTraceSupported () && configResolver .isPerformanceMonitoringEnabled ()) {
217
- // Starts recording frame metrics for this activity.
218
- /**
219
- * TODO: Only add activities that are hardware acceleration enabled so that calling {@link
220
- * FrameMetricsAggregator#remove(Activity)} will not throw exceptions.
221
- */
222
- frameMetricsAggregator .add (activity );
223
- // Start the Trace
224
- Trace screenTrace = new Trace (getScreenTraceName (activity ), transportManager , clock , this );
225
- screenTrace .start ();
226
- activityToScreenTraceMap .put (activity , screenTrace );
227
- }
228
253
}
229
254
230
255
/** Returns if this is the cold start of the app. */
@@ -315,54 +340,24 @@ public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
315
340
@ Override
316
341
public void onActivityPaused (Activity activity ) {}
317
342
318
- /** Stops screen trace right after onPause because of b/210055697 */
319
- @ Override
320
- public void onActivityPostPaused (@ NonNull Activity activity ) {
321
- if (isScreenTraceSupported ()) {
322
- sendScreenTrace (activity );
323
- }
324
- }
325
-
326
343
/**
327
- * Send screen trace. If hardware acceleration is not enabled, all frame metrics will be zero and
328
- * the trace will not be sent.
344
+ * Sends the screen trace for the provided activity.
329
345
*
330
346
* @param activity activity object.
331
347
*/
332
348
private void sendScreenTrace (Activity activity ) {
333
- if (!activityToScreenTraceMap .containsKey (activity )) {
334
- return ;
335
- }
336
349
Trace screenTrace = activityToScreenTraceMap .get (activity );
337
350
if (screenTrace == null ) {
338
351
return ;
339
352
}
340
353
activityToScreenTraceMap .remove (activity );
341
354
342
- int totalFrames = 0 ;
343
- int slowFrames = 0 ;
344
- int frozenFrames = 0 ;
345
- /**
346
- * Resets the metrics data and returns the currently-collected metrics. Note that {@link
347
- * FrameMetricsAggregator#reset()} will not stop recording for the activity. The reason of using
348
- * {@link FrameMetricsAggregator#reset()} is that {@link
349
- * FrameMetricsAggregator#remove(Activity)} will throw exceptions for hardware acceleration
350
- * disabled activities.
351
- */
352
- try {
353
- frameMetricsAggregator .remove (activity );
354
- } catch (IllegalArgumentException ignored ) {
355
- logger .debug ("View not hardware accelerated. Unable to collect screen trace." );
356
- }
357
- FrameMetricsCalculator .PerfFrameMetrics perfFrameMetrics =
358
- FrameMetricsCalculator .calculateFrameMetrics (frameMetricsAggregator .reset ());
359
- if (perfFrameMetrics .getTotalFrames () == 0
360
- && perfFrameMetrics .getSlowFrames () == 0
361
- && perfFrameMetrics .getFrozenFrames () == 0 ) {
362
- // All metrics are zero, no need to send screen trace.
355
+ Optional <PerfFrameMetrics > perfFrameMetrics = activityToRecorderMap .get (activity ).stop ();
356
+ if (!perfFrameMetrics .isAvailable ()) {
357
+ logger .warn ("Failed to record frame data for %s." , activity .getClass ().getSimpleName ());
363
358
return ;
364
359
}
365
- ScreenTraceUtil .addFrameCounters (screenTrace , perfFrameMetrics );
360
+ ScreenTraceUtil .addFrameCounters (screenTrace , perfFrameMetrics . get () );
366
361
// Stop and record trace
367
362
screenTrace .stop ();
368
363
}
@@ -407,23 +402,16 @@ private void sendSessionLog(String name, Timer startTime, Timer endTime) {
407
402
* @return true if supported, false if not.
408
403
*/
409
404
protected boolean isScreenTraceSupported () {
410
- return hasFrameMetricsAggregator ;
405
+ return screenPerformanceRecordingSupported ;
411
406
}
412
407
413
408
/**
414
409
* FrameMetricsAggregator first appears in Android Support Library 26.1.0. Before GMSCore SDK is
415
410
* updated to 26.1.0 (b/69954793), there will be ClassNotFoundException. This method is to check
416
411
* if FrameMetricsAggregator exists to avoid ClassNotFoundException.
417
412
*/
418
- private static boolean hasFrameMetricsAggregatorClass () {
419
- try {
420
- Class <?> initializerClass = Class .forName (FRAME_METRICS_AGGREGATOR_CLASSNAME );
421
- hasFrameMetricsAggregator = true ;
422
- return true ;
423
- } catch (ClassNotFoundException e ) {
424
- hasFrameMetricsAggregator = false ;
425
- return false ;
426
- }
413
+ private static boolean isScreenPerformanceRecordingSupported () {
414
+ return FrameMetricsRecorder .isFrameMetricsRecordingSupported ();
427
415
}
428
416
429
417
/** An interface to be implemented by subscribers which needs to receive app state update. */
0 commit comments