Skip to content

Commit 25a760c

Browse files
authored
Merge d088df7 into cd268a3
2 parents cd268a3 + d088df7 commit 25a760c

17 files changed

+500
-109
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Breaking changes:
1616
- Automatic user interaction tracking: every click now starts a new automatic transaction ([#2891](https://github.com/getsentry/sentry-java/pull/2891))
1717
- Previously performing a click on the same UI widget twice would keep the existing transaction running, the new behavior now better aligns with other SDKs
1818
- Android only: If global hub mode is enabled, Sentry.getSpan() returns the root span instead of the latest span ([#2855](https://github.com/getsentry/sentry-java/pull/2855))
19+
- Android only: Observe network state to upload any unsent envelopes ([#2910](https://github.com/getsentry/sentry-java/pull/2910))
1920

2021
### Fixes
2122

sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
import io.sentry.DeduplicateMultithreadedEventProcessor;
99
import io.sentry.DefaultTransactionPerformanceCollector;
1010
import io.sentry.ILogger;
11+
import io.sentry.NoOpConnectionStatusProvider;
1112
import io.sentry.SendFireAndForgetEnvelopeSender;
1213
import io.sentry.SendFireAndForgetOutboxSender;
1314
import io.sentry.SentryLevel;
1415
import io.sentry.android.core.cache.AndroidEnvelopeCache;
1516
import io.sentry.android.core.internal.debugmeta.AssetsDebugMetaLoader;
1617
import io.sentry.android.core.internal.gestures.AndroidViewGestureTargetLocator;
1718
import io.sentry.android.core.internal.modules.AssetsModulesLoader;
19+
import io.sentry.android.core.internal.util.AndroidConnectionStatusProvider;
1820
import io.sentry.android.core.internal.util.AndroidMainThreadChecker;
1921
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
2022
import io.sentry.android.fragment.FragmentLifecycleIntegration;
@@ -130,14 +132,19 @@ static void initializeIntegrationsAndProcessors(
130132
options.setEnvelopeDiskCache(new AndroidEnvelopeCache(options));
131133
}
132134

135+
if (options.getConnectionStatusProvider() instanceof NoOpConnectionStatusProvider) {
136+
options.setConnectionStatusProvider(
137+
new AndroidConnectionStatusProvider(context, options.getLogger(), buildInfoProvider));
138+
}
139+
133140
options.addEventProcessor(new DeduplicateMultithreadedEventProcessor(options));
134141
options.addEventProcessor(
135142
new DefaultAndroidEventProcessor(context, buildInfoProvider, options));
136143
options.addEventProcessor(new PerformanceAndroidEventProcessor(options, activityFramesTracker));
137144
options.addEventProcessor(new ScreenshotEventProcessor(options, buildInfoProvider));
138145
options.addEventProcessor(new ViewHierarchyEventProcessor(options));
139146
options.addEventProcessor(new AnrV2EventProcessor(context, options, buildInfoProvider));
140-
options.setTransportGate(new AndroidTransportGate(context, options.getLogger()));
147+
options.setTransportGate(new AndroidTransportGate(options));
141148
final SentryFrameMetricsCollector frameMetricsCollector =
142149
new SentryFrameMetricsCollector(context, options, buildInfoProvider);
143150
options.setTransactionProfiler(

sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransportGate.java

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,26 @@
11
package io.sentry.android.core;
22

3-
import android.content.Context;
4-
import io.sentry.ILogger;
5-
import io.sentry.android.core.internal.util.ConnectivityChecker;
3+
import io.sentry.IConnectionStatusProvider;
4+
import io.sentry.SentryOptions;
65
import io.sentry.transport.ITransportGate;
76
import org.jetbrains.annotations.NotNull;
87
import org.jetbrains.annotations.TestOnly;
98

109
final class AndroidTransportGate implements ITransportGate {
1110

12-
private final Context context;
13-
private final @NotNull ILogger logger;
11+
private final @NotNull SentryOptions options;
1412

15-
AndroidTransportGate(final @NotNull Context context, final @NotNull ILogger logger) {
16-
this.context = context;
17-
this.logger = logger;
13+
AndroidTransportGate(final @NotNull SentryOptions options) {
14+
this.options = options;
1815
}
1916

2017
@Override
2118
public boolean isConnected() {
22-
return isConnected(ConnectivityChecker.getConnectionStatus(context, logger));
19+
return isConnected(options.getConnectionStatusProvider().getConnectionStatus());
2320
}
2421

2522
@TestOnly
26-
boolean isConnected(final @NotNull ConnectivityChecker.Status status) {
23+
boolean isConnected(final @NotNull IConnectionStatusProvider.ConnectionStatus status) {
2724
// let's assume its connected if there's no permission or something as we can't really know
2825
// whether is online or not.
2926
switch (status) {

sentry-android-core/src/main/java/io/sentry/android/core/DeviceInfoUtil.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import android.util.DisplayMetrics;
1717
import io.sentry.DateUtils;
1818
import io.sentry.SentryLevel;
19-
import io.sentry.android.core.internal.util.ConnectivityChecker;
2019
import io.sentry.android.core.internal.util.CpuInfoUtils;
2120
import io.sentry.android.core.internal.util.DeviceOrientations;
2221
import io.sentry.android.core.internal.util.RootChecker;
@@ -180,8 +179,8 @@ private void setDeviceIO(final @NotNull Device device, final boolean includeDyna
180179
}
181180

182181
Boolean connected;
183-
switch (ConnectivityChecker.getConnectionStatus(context, options.getLogger())) {
184-
case NOT_CONNECTED:
182+
switch (options.getConnectionStatusProvider().getConnectionStatus()) {
183+
case DISCONNECTED:
185184
connected = false;
186185
break;
187186
case CONNECTED:
@@ -223,8 +222,7 @@ private void setDeviceIO(final @NotNull Device device, final boolean includeDyna
223222

224223
if (device.getConnectionType() == null) {
225224
// wifi, ethernet or cellular, null if none
226-
device.setConnectionType(
227-
ConnectivityChecker.getConnectionType(context, options.getLogger(), buildInfoProvider));
225+
device.setConnectionType(options.getConnectionStatusProvider().getConnectionType());
228226
}
229227
}
230228

sentry-android-core/src/main/java/io/sentry/android/core/NetworkBreadcrumbsIntegration.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import io.sentry.SentryLevel;
1717
import io.sentry.SentryOptions;
1818
import io.sentry.TypeCheckHint;
19-
import io.sentry.android.core.internal.util.ConnectivityChecker;
19+
import io.sentry.android.core.internal.util.AndroidConnectionStatusProvider;
2020
import io.sentry.util.Objects;
2121
import java.io.Closeable;
2222
import java.io.IOException;
@@ -69,7 +69,7 @@ public void register(final @NotNull IHub hub, final @NotNull SentryOptions optio
6969

7070
networkCallback = new NetworkBreadcrumbsNetworkCallback(hub, buildInfoProvider);
7171
final boolean registered =
72-
ConnectivityChecker.registerNetworkCallback(
72+
AndroidConnectionStatusProvider.registerNetworkCallback(
7373
context, logger, buildInfoProvider, networkCallback);
7474

7575
// The specific error is logged in the ConnectivityChecker method
@@ -86,7 +86,7 @@ public void register(final @NotNull IHub hub, final @NotNull SentryOptions optio
8686
@Override
8787
public void close() throws IOException {
8888
if (networkCallback != null) {
89-
ConnectivityChecker.unregisterNetworkCallback(
89+
AndroidConnectionStatusProvider.unregisterNetworkCallback(
9090
context, logger, buildInfoProvider, networkCallback);
9191
logger.log(SentryLevel.DEBUG, "NetworkBreadcrumbsIntegration remove.");
9292
}
@@ -208,7 +208,7 @@ static class NetworkBreadcrumbConnectionDetail {
208208
this.signalStrength = strength > -100 ? strength : 0;
209209
this.isVpn = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
210210
String connectionType =
211-
ConnectivityChecker.getConnectionType(networkCapabilities, buildInfoProvider);
211+
AndroidConnectionStatusProvider.getConnectionType(networkCapabilities, buildInfoProvider);
212212
this.type = connectionType != null ? connectionType : "";
213213
}
214214

sentry-android-core/src/main/java/io/sentry/android/core/SendCachedEnvelopeIntegration.java

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,33 @@
11
package io.sentry.android.core;
22

3+
import io.sentry.IConnectionStatusProvider;
34
import io.sentry.IHub;
45
import io.sentry.Integration;
56
import io.sentry.SendCachedEnvelopeFireAndForgetIntegration;
67
import io.sentry.SentryLevel;
78
import io.sentry.SentryOptions;
89
import io.sentry.util.LazyEvaluator;
910
import io.sentry.util.Objects;
11+
import java.io.Closeable;
12+
import java.io.IOException;
1013
import java.util.concurrent.Future;
1114
import java.util.concurrent.RejectedExecutionException;
1215
import java.util.concurrent.TimeUnit;
1316
import java.util.concurrent.TimeoutException;
17+
import java.util.concurrent.atomic.AtomicBoolean;
1418
import org.jetbrains.annotations.NotNull;
19+
import org.jetbrains.annotations.Nullable;
1520

16-
final class SendCachedEnvelopeIntegration implements Integration {
21+
final class SendCachedEnvelopeIntegration
22+
implements Integration, IConnectionStatusProvider.IConnectionStatusObserver, Closeable {
1723

1824
private final @NotNull SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetFactory
1925
factory;
2026
private final @NotNull LazyEvaluator<Boolean> startupCrashMarkerEvaluator;
27+
private final AtomicBoolean startupCrashHandled = new AtomicBoolean(false);
28+
private @Nullable IConnectionStatusProvider connectionStatusProvider;
29+
private @Nullable IHub hub;
30+
private @Nullable SentryAndroidOptions options;
2131

2232
public SendCachedEnvelopeIntegration(
2333
final @NotNull SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForgetFactory factory,
@@ -29,7 +39,8 @@ public SendCachedEnvelopeIntegration(
2939
@Override
3040
public void register(@NotNull IHub hub, @NotNull SentryOptions options) {
3141
Objects.requireNonNull(hub, "Hub is required");
32-
final SentryAndroidOptions androidOptions =
42+
this.hub = hub;
43+
this.options =
3344
Objects.requireNonNull(
3445
(options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null,
3546
"SentryAndroidOptions is required");
@@ -40,51 +51,74 @@ public void register(@NotNull IHub hub, @NotNull SentryOptions options) {
4051
return;
4152
}
4253

54+
connectionStatusProvider = options.getConnectionStatusProvider();
55+
connectionStatusProvider.addConnectionStatusObserver(this);
56+
57+
sendCachedEnvelopes(hub, this.options);
58+
}
59+
60+
@Override
61+
public void close() throws IOException {
62+
if (connectionStatusProvider != null) {
63+
connectionStatusProvider.removeConnectionStatusObserver(this);
64+
}
65+
}
66+
67+
@Override
68+
public void onConnectionStatusChanged(IConnectionStatusProvider.ConnectionStatus status) {
69+
if (hub != null && options != null) {
70+
sendCachedEnvelopes(hub, options);
71+
}
72+
}
73+
74+
private synchronized void sendCachedEnvelopes(
75+
final @NotNull IHub hub, final @NotNull SentryAndroidOptions options) {
76+
4377
final SendCachedEnvelopeFireAndForgetIntegration.SendFireAndForget sender =
44-
factory.create(hub, androidOptions);
78+
factory.create(hub, options);
4579

4680
if (sender == null) {
47-
androidOptions.getLogger().log(SentryLevel.ERROR, "SendFireAndForget factory is null.");
81+
options.getLogger().log(SentryLevel.ERROR, "SendFireAndForget factory is null.");
4882
return;
4983
}
50-
5184
try {
52-
Future<?> future =
53-
androidOptions
85+
final Future<?> future =
86+
options
5487
.getExecutorService()
5588
.submit(
5689
() -> {
5790
try {
5891
sender.send();
5992
} catch (Throwable e) {
60-
androidOptions
93+
options
6194
.getLogger()
6295
.log(SentryLevel.ERROR, "Failed trying to send cached events.", e);
6396
}
6497
});
6598

66-
if (startupCrashMarkerEvaluator.getValue()) {
67-
androidOptions
68-
.getLogger()
69-
.log(SentryLevel.DEBUG, "Startup Crash marker exists, blocking flush.");
99+
// startupCrashMarkerEvaluator remains true on subsequent runs, let's ensure we only block on
100+
// the very first execution (=app start)
101+
if (startupCrashMarkerEvaluator.getValue()
102+
&& startupCrashHandled.compareAndSet(false, true)) {
103+
options.getLogger().log(SentryLevel.DEBUG, "Startup Crash marker exists, blocking flush.");
70104
try {
71-
future.get(androidOptions.getStartupCrashFlushTimeoutMillis(), TimeUnit.MILLISECONDS);
105+
future.get(options.getStartupCrashFlushTimeoutMillis(), TimeUnit.MILLISECONDS);
72106
} catch (TimeoutException e) {
73-
androidOptions
107+
options
74108
.getLogger()
75109
.log(SentryLevel.DEBUG, "Synchronous send timed out, continuing in the background.");
76110
}
77111
}
78-
androidOptions.getLogger().log(SentryLevel.DEBUG, "SendCachedEnvelopeIntegration installed.");
112+
options.getLogger().log(SentryLevel.DEBUG, "SendCachedEnvelopeIntegration installed.");
79113
} catch (RejectedExecutionException e) {
80-
androidOptions
114+
options
81115
.getLogger()
82116
.log(
83117
SentryLevel.ERROR,
84118
"Failed to call the executor. Cached events will not be sent. Did you call Sentry.close()?",
85119
e);
86120
} catch (Throwable e) {
87-
androidOptions
121+
options
88122
.getLogger()
89123
.log(SentryLevel.ERROR, "Failed to call the executor. Cached events will not be sent", e);
90124
}

0 commit comments

Comments
 (0)