Skip to content

Make FirebasePerformance initialization lazy. #2518

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,14 @@ public List<Component<?>> getComponents() {
.add(Dependency.required(FirebaseInstallationsApi.class))
.add(Dependency.requiredProvider(TransportFactory.class))
.factory(FirebasePerfRegistrar::providesFirebasePerformance)
// Since the SDK is eager(auto starts at app start), we use "lazy" dependency for some
// components that are not required during initialization so as not to force initialize
// them at app startup (refer
// https://github.com/google/guice/wiki/InjectingProviders#providers-for-lazy-loading).
.eagerInDefaultApp()
.build(),
/**
* Fireperf SDK is lazily by {@link FirebasePerformanceInitializer} during {@link
* com.google.firebase.perf.application.AppStateMonitor#onActivityResumed(Activity)}. we use
* "lazy" dependency for some components that are not required during initialization so as
* not to force initialize them at app startup (refer
* https://github.com/google/guice/wiki/InjectingProviders#providers-for-lazy-loading)*
*/
LibraryVersionComponent.create("fire-perf", BuildConfig.VERSION_NAME));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.firebase.perf;

import com.google.firebase.perf.application.AppStateMonitor;

/**
* FirebasePerformanceInitializer to initialize FirebasePerformance during app cold start
*
* @hide
*/
/** @hide */
public final class FirebasePerformanceInitializer implements AppStateMonitor.AppColdStartCallback {

/** @hide */
@Override
public void onAppColdStart() {
// Initialize FirebasePerformance when app cold starts.
FirebasePerformance.getInstance();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,9 @@ public static AppStateMonitor getInstance() {

private ApplicationProcessState mCurrentState = ApplicationProcessState.BACKGROUND;

private Set<WeakReference<AppStateCallback>> mClients =
private Set<WeakReference<AppStateCallback>> appStateSubscribers =
new HashSet<WeakReference<AppStateCallback>>();
private Set<AppColdStartCallback> appColdStartSubscribers = new HashSet<AppColdStartCallback>();

private boolean hasFrameMetricsAggregator = false;
private FrameMetricsAggregator mFrameMetricsAggregator;
Expand Down Expand Up @@ -207,6 +208,7 @@ public synchronized void onActivityResumed(Activity activity) {
updateAppState(ApplicationProcessState.FOREGROUND);
if (mIsColdStart) {
// case 1: app startup.
sendAppColdStartUpdate();
mIsColdStart = false;
} else {
// case 2: app switch from background to foreground.
Expand Down Expand Up @@ -234,36 +236,50 @@ public ApplicationProcessState getAppState() {
}

/**
* Register a client to receive app state update.
* Register a subscriber to receive app state update.
*
* @param client an AppStateCallback instance.
* @hide
*/
/** @hide */
public void registerForAppState(WeakReference<AppStateCallback> client) {
synchronized (mClients) {
mClients.add(client);
public void registerForAppState(WeakReference<AppStateCallback> subscriber) {
synchronized (appStateSubscribers) {
appStateSubscribers.add(subscriber);
}
}

/**
* Unregister the client to stop receiving app state update.
* Unregister the subscriber to stop receiving app state update.
*
* @param client an AppStateCallback instance.
* @param subscriber an AppStateCallback instance.
* @hide
*/
/** @hide */
public void unregisterForAppState(WeakReference<AppStateCallback> client) {
synchronized (mClients) {
mClients.remove(client);
public void unregisterForAppState(WeakReference<AppStateCallback> subscriber) {
synchronized (appStateSubscribers) {
appStateSubscribers.remove(subscriber);
}
}

/** Send update state update to registered clients. */
/**
* Register a subscriber to receive app cold start update.
*
* @param subscriber the {@link AppColdStartCallback} instance.
* @hide
*/
/** @hide */
public void registerForAppColdStart(AppColdStartCallback subscriber) {
synchronized (appStateSubscribers) {
appColdStartSubscribers.add(subscriber);
}
}

/** Send update state update to registered subscribers. */
private void updateAppState(ApplicationProcessState newState) {
mCurrentState = newState;
synchronized (mClients) {
for (Iterator<WeakReference<AppStateCallback>> i = mClients.iterator(); i.hasNext(); ) {
synchronized (appStateSubscribers) {
for (Iterator<WeakReference<AppStateCallback>> i = appStateSubscribers.iterator();
i.hasNext(); ) {
AppStateCallback callback = i.next().get();
if (callback != null) {
callback.onUpdateAppState(mCurrentState);
Expand All @@ -276,6 +292,18 @@ private void updateAppState(ApplicationProcessState newState) {
}
}

/** Send cold start update to registered subscribers. */
private void sendAppColdStartUpdate() {
synchronized (appStateSubscribers) {
for (Iterator<AppColdStartCallback> i = appColdStartSubscribers.iterator(); i.hasNext(); ) {
AppColdStartCallback callback = i.next();
if (callback != null) {
callback.onAppColdStart();
}
}
}
}

/**
* Return app is in foreground or not.
*
Expand Down Expand Up @@ -425,7 +453,7 @@ private boolean hasFrameMetricsAggregatorClass() {
}

/**
* An interface to be implemented by clients which needs to receive app state update.
* An interface to be implemented by subscribers which needs to receive app state update.
*
* @hide
*/
Expand All @@ -436,6 +464,15 @@ public static interface AppStateCallback {
public void onUpdateAppState(ApplicationProcessState newState);
}

/**
* An interface to be implemented by subscribers which needs to receive app cold start update.
*
* @hide
*/
public static interface AppColdStartCallback {
public void onAppColdStart();
}

/**
* Screen trace name is prefix "_st_" concatenates with Activity's class name.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import androidx.annotation.Nullable;
import com.google.android.gms.common.internal.Preconditions;
import com.google.android.gms.common.util.VisibleForTesting;
import com.google.firebase.perf.FirebasePerformanceInitializer;
import com.google.firebase.perf.application.AppStateMonitor;
import com.google.firebase.perf.config.ConfigResolver;
import com.google.firebase.perf.metrics.AppStartTrace;
Expand Down Expand Up @@ -61,7 +62,10 @@ public void attachInfo(Context context, ProviderInfo info) {
ConfigResolver configResolver = ConfigResolver.getInstance();
configResolver.setContentProviderContext(getContext());

AppStateMonitor.getInstance().registerActivityLifecycleCallbacks(getContext());
AppStateMonitor appStateMonitor = AppStateMonitor.getInstance();
appStateMonitor.registerActivityLifecycleCallbacks(getContext());
appStateMonitor.registerForAppColdStart(new FirebasePerformanceInitializer());

AppStartTrace appStartTrace = AppStartTrace.getInstance();
appStartTrace.registerActivityLifecycleCallbacks(getContext());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ public void testGetComponents() {
Dependency.required(FirebaseInstallationsApi.class),
Dependency.requiredProvider(TransportFactory.class));

assertThat(firebasePerfComponent.isEagerInDefaultApp()).isTrue();
assertThat(firebasePerfComponent.isLazy()).isTrue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import android.content.Context;
import android.os.Bundle;
import android.view.WindowManager.LayoutParams;
import com.google.firebase.perf.FirebasePerformanceInitializer;
import com.google.firebase.perf.FirebasePerformanceTestBase;
import com.google.firebase.perf.config.ConfigResolver;
import com.google.firebase.perf.config.DeviceCacheManager;
Expand Down Expand Up @@ -675,51 +676,106 @@ public void backgroundTrace_perfMonDeactivated_traceCreated() {
}

@Test
public void activityStateChanges_singleClient_callbackIsCalled() {
public void activityStateChanges_singleSubscriber_callbackIsCalled() {
AppStateMonitor monitor = new AppStateMonitor(transportManager, mClock);
Map<Integer, ApplicationProcessState> clientState = new HashMap<>();
Map<Integer, ApplicationProcessState> subscriberState = new HashMap<>();

final int client1 = 1;
final int subscriber1 = 1;
monitor.registerForAppState(
new WeakReference<>(newState -> clientState.put(client1, newState)));
new WeakReference<>(newState -> subscriberState.put(subscriber1, newState)));

// Activity comes to Foreground
monitor.onActivityResumed(activity1);
assertThat(clientState.get(client1)).isEqualTo(ApplicationProcessState.FOREGROUND);
assertThat(subscriberState.get(subscriber1)).isEqualTo(ApplicationProcessState.FOREGROUND);

// Activity goes to Background
monitor.onActivityStopped(activity1);
assertThat(clientState.get(client1)).isEqualTo(ApplicationProcessState.BACKGROUND);
assertThat(subscriberState.get(subscriber1)).isEqualTo(ApplicationProcessState.BACKGROUND);
}

@Test
public void activityStateChanges_multipleClients_callbackCalledOnEachClient() {
public void activityStateChanges_multipleSubscribers_callbackCalledOnEachSubscriber() {
AppStateMonitor monitor = new AppStateMonitor(transportManager, mClock);
Map<Integer, ApplicationProcessState> clientState = new HashMap<>();
Map<Integer, ApplicationProcessState> subscriberState = new HashMap<>();

final int client1 = 1;
final int subscriber1 = 1;
monitor.registerForAppState(
new WeakReference<>(newState -> clientState.put(client1, newState)));
new WeakReference<>(newState -> subscriberState.put(subscriber1, newState)));

final int client2 = 2;
final int subscriber2 = 2;
monitor.registerForAppState(
new WeakReference<>(newState -> clientState.put(client2, newState)));
new WeakReference<>(newState -> subscriberState.put(subscriber2, newState)));

final int client3 = 3;
final int subscriber3 = 3;
monitor.registerForAppState(
new WeakReference<>(newState -> clientState.put(client3, newState)));
new WeakReference<>(newState -> subscriberState.put(subscriber3, newState)));

// Activity comes to Foreground
monitor.onActivityResumed(activity1);
assertThat(clientState.get(client1)).isEqualTo(ApplicationProcessState.FOREGROUND);
assertThat(clientState.get(client2)).isEqualTo(ApplicationProcessState.FOREGROUND);
assertThat(clientState.get(client3)).isEqualTo(ApplicationProcessState.FOREGROUND);
assertThat(subscriberState.get(subscriber1)).isEqualTo(ApplicationProcessState.FOREGROUND);
assertThat(subscriberState.get(subscriber2)).isEqualTo(ApplicationProcessState.FOREGROUND);
assertThat(subscriberState.get(subscriber3)).isEqualTo(ApplicationProcessState.FOREGROUND);

// Activity goes to Background
monitor.onActivityStopped(activity1);
assertThat(clientState.get(client1)).isEqualTo(ApplicationProcessState.BACKGROUND);
assertThat(clientState.get(client2)).isEqualTo(ApplicationProcessState.BACKGROUND);
assertThat(clientState.get(client3)).isEqualTo(ApplicationProcessState.BACKGROUND);
assertThat(subscriberState.get(subscriber1)).isEqualTo(ApplicationProcessState.BACKGROUND);
assertThat(subscriberState.get(subscriber2)).isEqualTo(ApplicationProcessState.BACKGROUND);
assertThat(subscriberState.get(subscriber3)).isEqualTo(ApplicationProcessState.BACKGROUND);
}

@Test
public void appColdStart_singleSubscriber_callbackIsCalled() {
AppStateMonitor monitor = new AppStateMonitor(transportManager, mClock);
FirebasePerformanceInitializer mockInitializer = mock(FirebasePerformanceInitializer.class);
monitor.registerForAppColdStart(mockInitializer);

// Activity comes to Foreground
monitor.onActivityResumed(activity1);
verify(mockInitializer, times(1)).onAppColdStart();
}

@Test
public void appHotStart_singleSubscriber_callbackIsNotCalled() {
AppStateMonitor monitor = new AppStateMonitor(transportManager, mClock);
FirebasePerformanceInitializer mockInitializer = mock(FirebasePerformanceInitializer.class);
monitor.registerForAppColdStart(mockInitializer);

// Activity comes to Foreground
monitor.onActivityResumed(activity1);
verify(mockInitializer, times(1)).onAppColdStart();

// Activity goes to Background
monitor.onActivityStopped(activity1);

// Activity comes to Foreground
monitor.onActivityResumed(activity1);
verify(mockInitializer, times(1)).onAppColdStart();
}

@Test
public void appColdStart_multipleSubscriber_callbackIsCalled() {
AppStateMonitor monitor = new AppStateMonitor(transportManager, mClock);
FirebasePerformanceInitializer mockInitializer1 = mock(FirebasePerformanceInitializer.class);
FirebasePerformanceInitializer mockInitializer2 = mock(FirebasePerformanceInitializer.class);
monitor.registerForAppColdStart(mockInitializer1);
monitor.registerForAppColdStart(mockInitializer2);

// Activity comes to Foreground
monitor.onActivityResumed(activity1);
verify(mockInitializer1, times(1)).onAppColdStart();
verify(mockInitializer2, times(1)).onAppColdStart();
}

@Test
public void appColdStart_singleSubscriberRegistersForMultipleTimes_oneCallbackIsCalled() {
AppStateMonitor monitor = new AppStateMonitor(transportManager, mClock);
FirebasePerformanceInitializer mockInitializer1 = mock(FirebasePerformanceInitializer.class);
monitor.registerForAppColdStart(mockInitializer1);
monitor.registerForAppColdStart(mockInitializer1);

// Activity comes to Foreground
monitor.onActivityResumed(activity1);
verify(mockInitializer1, times(1)).onAppColdStart();
}

private static Activity createFakeActivity(boolean isHardwareAccelerated) {
Expand Down