Skip to content

Commit f65c333

Browse files
Use manual foreground detection to trigger network reconnect (#2763)
1 parent 563781c commit f65c333

File tree

5 files changed

+106
-28
lines changed

5 files changed

+106
-28
lines changed

firebase-firestore/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
# Unreleased
2+
- [changed] Increases the aggressiveness of network retires when an app's
3+
foreground status changes.
4+
5+
# 23.0.1
26
- [changed] The SDK now tries to immediately establish a connection to the
37
backend when the app enters the foreground.
48

firebase-firestore/src/androidTest/java/com/google/firebase/firestore/remote/RemoteStoreTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,16 +81,16 @@ public ImmutableSortedSet<DocumentKey> getRemoteKeysForTarget(int targetId) {
8181
RemoteStore remoteStore =
8282
new RemoteStore(callback, localStore, datastore, testQueue, connectivityMonitor);
8383

84-
waitFor(testQueue.enqueue(() -> remoteStore.forceEnableNetwork()));
84+
waitFor(testQueue.enqueue(remoteStore::forceEnableNetwork));
8585
drain(testQueue);
8686
networkChangeSemaphore.drainPermits();
8787

8888
connectivityMonitor.goOffline();
8989
waitFor(networkChangeSemaphore);
9090
drain(testQueue);
91-
92-
waitFor(testQueue.enqueue(() -> remoteStore.forceEnableNetwork()));
9391
networkChangeSemaphore.drainPermits();
92+
93+
waitFor(testQueue.enqueue(remoteStore::forceEnableNetwork));
9494
connectivityMonitor.goOnline();
9595
waitFor(networkChangeSemaphore);
9696
}

firebase-firestore/src/main/java/com/google/firebase/firestore/remote/AndroidConnectivityMonitor.java

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,35 @@
1717
import static com.google.firebase.firestore.util.Assert.hardAssert;
1818

1919
import android.annotation.TargetApi;
20+
import android.app.Activity;
21+
import android.app.Application;
2022
import android.content.BroadcastReceiver;
23+
import android.content.ComponentCallbacks2;
2124
import android.content.Context;
2225
import android.content.Intent;
2326
import android.content.IntentFilter;
27+
import android.content.res.Configuration;
2428
import android.net.ConnectivityManager;
2529
import android.net.Network;
2630
import android.os.Build;
31+
import android.os.Bundle;
32+
import androidx.annotation.NonNull;
2733
import androidx.annotation.Nullable;
28-
import com.google.android.gms.common.api.internal.BackgroundDetector;
2934
import com.google.firebase.firestore.util.Consumer;
35+
import com.google.firebase.firestore.util.Logger;
3036
import java.util.ArrayList;
3137
import java.util.List;
38+
import java.util.concurrent.atomic.AtomicBoolean;
3239

3340
/**
3441
* Android implementation of ConnectivityMonitor. Parallel implementations exist for N+ and pre-N.
3542
*
3643
* <p>Implementation note: Most of the code here was shamelessly stolen from
3744
* https://github.com/grpc/grpc-java/blob/master/android/src/main/java/io/grpc/android/AndroidChannelBuilder.java
3845
*/
39-
public final class AndroidConnectivityMonitor
40-
implements ConnectivityMonitor, BackgroundDetector.BackgroundStateChangeListener {
46+
public final class AndroidConnectivityMonitor implements ConnectivityMonitor {
47+
48+
private static final String LOG_TAG = "AndroidConnectivityMonitor";
4149

4250
private final Context context;
4351
@Nullable private final ConnectivityManager connectivityManager;
@@ -78,34 +86,80 @@ private void configureNetworkMonitoring() {
7886
final DefaultNetworkCallback defaultNetworkCallback = new DefaultNetworkCallback();
7987
connectivityManager.registerDefaultNetworkCallback(defaultNetworkCallback);
8088
unregisterRunnable =
81-
new Runnable() {
82-
@Override
83-
public void run() {
84-
connectivityManager.unregisterNetworkCallback(defaultNetworkCallback);
85-
}
86-
};
89+
() -> connectivityManager.unregisterNetworkCallback(defaultNetworkCallback);
8790
} else {
8891
NetworkReceiver networkReceiver = new NetworkReceiver();
8992
@SuppressWarnings("deprecation")
9093
IntentFilter networkIntentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
9194
context.registerReceiver(networkReceiver, networkIntentFilter);
92-
unregisterRunnable =
93-
new Runnable() {
94-
@Override
95-
public void run() {
96-
context.unregisterReceiver(networkReceiver);
97-
}
98-
};
95+
unregisterRunnable = () -> context.unregisterReceiver(networkReceiver);
9996
}
10097
}
10198

10299
private void configureBackgroundStateListener() {
103-
BackgroundDetector.getInstance().addListener(this);
100+
Application application = (Application) context.getApplicationContext();
101+
final AtomicBoolean inBackground = new AtomicBoolean();
102+
103+
// Manually register an ActivityLifecycleCallback. Android's BackgroundDetector only notifies
104+
// when it is certain that the app transitioned from background to foreground. Instead, we
105+
// want to be notified whenever there is a slight chance that this transition happened.
106+
application.registerActivityLifecycleCallbacks(
107+
new Application.ActivityLifecycleCallbacks() {
108+
@Override
109+
public void onActivityCreated(@NonNull Activity activity, Bundle savedInstanceState) {
110+
if (inBackground.compareAndSet(true, false)) {
111+
raiseForegroundNotification();
112+
}
113+
}
114+
115+
@Override
116+
public void onActivityStarted(@NonNull Activity activity) {
117+
if (inBackground.compareAndSet(true, false)) {
118+
raiseForegroundNotification();
119+
}
120+
}
121+
122+
@Override
123+
public void onActivityResumed(@NonNull Activity activity) {
124+
if (inBackground.compareAndSet(true, false)) {
125+
raiseForegroundNotification();
126+
}
127+
}
128+
129+
@Override
130+
public void onActivityPaused(@NonNull Activity activity) {}
131+
132+
@Override
133+
public void onActivityStopped(@NonNull Activity activity) {}
134+
135+
@Override
136+
public void onActivitySaveInstanceState(
137+
@NonNull Activity activity, @NonNull Bundle outState) {}
138+
139+
@Override
140+
public void onActivityDestroyed(@NonNull Activity activity) {}
141+
});
142+
143+
application.registerComponentCallbacks(
144+
new ComponentCallbacks2() {
145+
@Override
146+
public void onTrimMemory(int level) {
147+
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
148+
inBackground.set(true);
149+
}
150+
}
151+
152+
@Override
153+
public void onConfigurationChanged(@NonNull Configuration newConfig) {}
154+
155+
@Override
156+
public void onLowMemory() {}
157+
});
104158
}
105159

106-
@Override
107-
public void onBackgroundStateChanged(boolean background) {
108-
if (!background && isConnected()) {
160+
public void raiseForegroundNotification() {
161+
Logger.debug(LOG_TAG, "App has entered the foreground.");
162+
if (isConnected()) {
109163
raiseCallbacks(/* connected= */ true);
110164
}
111165
}

firebase-firestore/src/main/java/com/google/firebase/firestore/remote/OnlineStateTracker.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ interface OnlineStateCallback {
8888
shouldWarnClientIsOffline = true;
8989
}
9090

91+
/** Returns the current online state. */
92+
OnlineState getState() {
93+
return state;
94+
}
95+
9196
/**
9297
* Called by RemoteStore when a watch stream is started (including on each backoff attempt).
9398
*

firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -214,14 +214,29 @@ public void onClose(Status status) {
214214
// Porting Note: Unlike iOS, `restartNetwork()` is called even when the network
215215
// becomes unreachable as we don't have any other way to tear down our streams.
216216

217+
// We only invoke restartNetwork() when the network status differs from our online
218+
// state. This prevents frequent reconnects as the callback is invoked whenever
219+
// the app reaches the foreground.
220+
if (networkStatus.equals(NetworkStatus.REACHABLE)
221+
&& onlineStateTracker.getState().equals(OnlineState.ONLINE)) {
222+
return;
223+
}
224+
225+
if (networkStatus.equals(NetworkStatus.UNREACHABLE)
226+
&& onlineStateTracker.getState().equals(OnlineState.OFFLINE)) {
227+
return;
228+
}
229+
217230
// If the network has been explicitly disabled, make sure we don't accidentally
218231
// re-enable it.
219-
if (canUseNetwork()) {
220-
// Tear down and re-create our network streams. This will ensure the backoffs are
221-
// reset.
222-
Logger.debug(LOG_TAG, "Restarting streams for network reachability change.");
223-
restartNetwork();
232+
if (!canUseNetwork()) {
233+
return;
224234
}
235+
236+
// Tear down and re-create our network streams. This will ensure the backoffs are
237+
// reset.
238+
Logger.debug(LOG_TAG, "Restarting streams for network reachability change.");
239+
restartNetwork();
225240
});
226241
});
227242
}

0 commit comments

Comments
 (0)