25
25
import android .content .Intent ;
26
26
import android .content .IntentFilter ;
27
27
import android .os .Bundle ;
28
+ import androidx .annotation .NonNull ;
29
+ import androidx .lifecycle .DefaultLifecycleObserver ;
30
+ import androidx .lifecycle .LifecycleOwner ;
31
+ import androidx .lifecycle .ProcessLifecycleOwner ;
28
32
import io .sentry .Breadcrumb ;
29
33
import io .sentry .Hint ;
30
34
import io .sentry .IScopes ;
33
37
import io .sentry .SentryLevel ;
34
38
import io .sentry .SentryOptions ;
35
39
import io .sentry .android .core .internal .util .AndroidCurrentDateProvider ;
40
+ import io .sentry .android .core .internal .util .AndroidThreadChecker ;
36
41
import io .sentry .android .core .internal .util .Debouncer ;
37
42
import io .sentry .util .AutoClosableReentrantLock ;
38
43
import io .sentry .util .Objects ;
@@ -51,29 +56,46 @@ public final class SystemEventsBreadcrumbsIntegration implements Integration, Cl
51
56
52
57
private final @ NotNull Context context ;
53
58
54
- @ TestOnly @ Nullable SystemEventsBroadcastReceiver receiver ;
59
+ @ TestOnly @ Nullable volatile SystemEventsBroadcastReceiver receiver ;
60
+
61
+ @ TestOnly @ Nullable volatile ReceiverLifecycleHandler lifecycleHandler ;
62
+
63
+ private final @ NotNull MainLooperHandler handler ;
55
64
56
65
private @ Nullable SentryAndroidOptions options ;
57
66
67
+ private @ Nullable IScopes scopes ;
68
+
58
69
private final @ NotNull String [] actions ;
59
- private boolean isClosed = false ;
60
- private final @ NotNull AutoClosableReentrantLock startLock = new AutoClosableReentrantLock ();
70
+ private volatile boolean isClosed = false ;
71
+ private volatile boolean isStopped = false ;
72
+ private volatile IntentFilter filter = null ;
73
+ private final @ NotNull AutoClosableReentrantLock receiverLock = new AutoClosableReentrantLock ();
61
74
62
75
public SystemEventsBreadcrumbsIntegration (final @ NotNull Context context ) {
63
76
this (context , getDefaultActionsInternal ());
64
77
}
65
78
66
79
private SystemEventsBreadcrumbsIntegration (
67
80
final @ NotNull Context context , final @ NotNull String [] actions ) {
81
+ this (context , actions , new MainLooperHandler ());
82
+ }
83
+
84
+ SystemEventsBreadcrumbsIntegration (
85
+ final @ NotNull Context context ,
86
+ final @ NotNull String [] actions ,
87
+ final @ NotNull MainLooperHandler handler ) {
68
88
this .context = ContextUtils .getApplicationContext (context );
69
89
this .actions = actions ;
90
+ this .handler = handler ;
70
91
}
71
92
72
93
public SystemEventsBreadcrumbsIntegration (
73
94
final @ NotNull Context context , final @ NotNull List <String > actions ) {
74
95
this .context = ContextUtils .getApplicationContext (context );
75
96
this .actions = new String [actions .size ()];
76
97
actions .toArray (this .actions );
98
+ this .handler = new MainLooperHandler ();
77
99
}
78
100
79
101
@ Override
@@ -83,6 +105,7 @@ public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions
83
105
Objects .requireNonNull (
84
106
(options instanceof SentryAndroidOptions ) ? (SentryAndroidOptions ) options : null ,
85
107
"SentryAndroidOptions is required" );
108
+ this .scopes = scopes ;
86
109
87
110
this .options
88
111
.getLogger ()
@@ -92,46 +115,170 @@ public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions
92
115
this .options .isEnableSystemEventBreadcrumbs ());
93
116
94
117
if (this .options .isEnableSystemEventBreadcrumbs ()) {
118
+ addLifecycleObserver (this .options );
119
+ registerReceiver (this .scopes , this .options , /* reportAsNewIntegration = */ true );
120
+ }
121
+ }
95
122
96
- try {
97
- options
98
- .getExecutorService ()
99
- .submit (
100
- () -> {
101
- try (final @ NotNull ISentryLifecycleToken ignored = startLock .acquire ()) {
102
- if (!isClosed ) {
103
- startSystemEventsReceiver (scopes , (SentryAndroidOptions ) options );
123
+ private void registerReceiver (
124
+ final @ NotNull IScopes scopes ,
125
+ final @ NotNull SentryAndroidOptions options ,
126
+ final boolean reportAsNewIntegration ) {
127
+
128
+ if (!options .isEnableSystemEventBreadcrumbs ()) {
129
+ return ;
130
+ }
131
+
132
+ try (final @ NotNull ISentryLifecycleToken ignored = receiverLock .acquire ()) {
133
+ if (isClosed || isStopped || receiver != null ) {
134
+ return ;
135
+ }
136
+ }
137
+
138
+ try {
139
+ options
140
+ .getExecutorService ()
141
+ .submit (
142
+ () -> {
143
+ try (final @ NotNull ISentryLifecycleToken ignored = receiverLock .acquire ()) {
144
+ if (isClosed || isStopped || receiver != null ) {
145
+ return ;
146
+ }
147
+
148
+ receiver = new SystemEventsBroadcastReceiver (scopes , options );
149
+ if (filter == null ) {
150
+ filter = new IntentFilter ();
151
+ for (String item : actions ) {
152
+ filter .addAction (item );
104
153
}
105
154
}
106
- });
107
- } catch (Throwable e ) {
108
- options
109
- .getLogger ()
110
- .log (
111
- SentryLevel .DEBUG ,
112
- "Failed to start SystemEventsBreadcrumbsIntegration on executor thread." ,
113
- e );
114
- }
155
+ try {
156
+ // registerReceiver can throw SecurityException but it's not documented in the
157
+ // official docs
158
+ ContextUtils .registerReceiver (context , options , receiver , filter );
159
+ if (reportAsNewIntegration ) {
160
+ options
161
+ .getLogger ()
162
+ .log (SentryLevel .DEBUG , "SystemEventsBreadcrumbsIntegration installed." );
163
+ addIntegrationToSdkVersion ("SystemEventsBreadcrumbs" );
164
+ }
165
+ } catch (Throwable e ) {
166
+ options .setEnableSystemEventBreadcrumbs (false );
167
+ options
168
+ .getLogger ()
169
+ .log (
170
+ SentryLevel .ERROR ,
171
+ "Failed to initialize SystemEventsBreadcrumbsIntegration." ,
172
+ e );
173
+ }
174
+ }
175
+ });
176
+ } catch (Throwable e ) {
177
+ options
178
+ .getLogger ()
179
+ .log (
180
+ SentryLevel .WARNING ,
181
+ "Failed to start SystemEventsBreadcrumbsIntegration on executor thread." );
115
182
}
116
183
}
117
184
118
- private void startSystemEventsReceiver (
119
- final @ NotNull IScopes scopes , final @ NotNull SentryAndroidOptions options ) {
120
- receiver = new SystemEventsBroadcastReceiver ( scopes , options );
121
- final IntentFilter filter = new IntentFilter () ;
122
- for ( String item : actions ) {
123
- filter . addAction ( item ) ;
185
+ private void unregisterReceiver () {
186
+ final @ Nullable SystemEventsBroadcastReceiver receiverRef ;
187
+ try ( final @ NotNull ISentryLifecycleToken ignored = receiverLock . acquire ()) {
188
+ isStopped = true ;
189
+ receiverRef = receiver ;
190
+ receiver = null ;
124
191
}
192
+
193
+ if (receiverRef != null ) {
194
+ context .unregisterReceiver (receiverRef );
195
+ }
196
+ }
197
+
198
+ // TODO: this duplicates a lot of AppLifecycleIntegration. We should register once on init
199
+ // and multiplex to different listeners rather.
200
+ private void addLifecycleObserver (final @ NotNull SentryAndroidOptions options ) {
125
201
try {
126
- // registerReceiver can throw SecurityException but it's not documented in the official docs
127
- ContextUtils .registerReceiver (context , options , receiver , filter );
128
- options .getLogger ().log (SentryLevel .DEBUG , "SystemEventsBreadcrumbsIntegration installed." );
129
- addIntegrationToSdkVersion ("SystemEventsBreadcrumbs" );
202
+ Class .forName ("androidx.lifecycle.DefaultLifecycleObserver" );
203
+ Class .forName ("androidx.lifecycle.ProcessLifecycleOwner" );
204
+ if (AndroidThreadChecker .getInstance ().isMainThread ()) {
205
+ addObserverInternal (options );
206
+ } else {
207
+ // some versions of the androidx lifecycle-process require this to be executed on the main
208
+ // thread.
209
+ handler .post (() -> addObserverInternal (options ));
210
+ }
211
+ } catch (ClassNotFoundException e ) {
212
+ options
213
+ .getLogger ()
214
+ .log (
215
+ SentryLevel .WARNING ,
216
+ "androidx.lifecycle is not available, SystemEventsBreadcrumbsIntegration won't be able"
217
+ + " to register/unregister an internal BroadcastReceiver. This may result in an"
218
+ + " increased ANR rate on Android 14 and above." );
219
+ } catch (Throwable e ) {
220
+ options
221
+ .getLogger ()
222
+ .log (
223
+ SentryLevel .ERROR ,
224
+ "SystemEventsBreadcrumbsIntegration could not register lifecycle observer" ,
225
+ e );
226
+ }
227
+ }
228
+
229
+ private void addObserverInternal (final @ NotNull SentryAndroidOptions options ) {
230
+ lifecycleHandler = new ReceiverLifecycleHandler ();
231
+
232
+ try {
233
+ ProcessLifecycleOwner .get ().getLifecycle ().addObserver (lifecycleHandler );
130
234
} catch (Throwable e ) {
131
- options .setEnableSystemEventBreadcrumbs (false );
235
+ // This is to handle a potential 'AbstractMethodError' gracefully. The error is triggered in
236
+ // connection with conflicting dependencies of the androidx.lifecycle.
237
+ // //See the issue here: https://github.com/getsentry/sentry-java/pull/2228
238
+ lifecycleHandler = null ;
132
239
options
133
240
.getLogger ()
134
- .log (SentryLevel .ERROR , "Failed to initialize SystemEventsBreadcrumbsIntegration." , e );
241
+ .log (
242
+ SentryLevel .ERROR ,
243
+ "SystemEventsBreadcrumbsIntegration failed to get Lifecycle and could not install lifecycle observer." ,
244
+ e );
245
+ }
246
+ }
247
+
248
+ private void removeLifecycleObserver () {
249
+ if (lifecycleHandler != null ) {
250
+ if (AndroidThreadChecker .getInstance ().isMainThread ()) {
251
+ removeObserverInternal ();
252
+ } else {
253
+ // some versions of the androidx lifecycle-process require this to be executed on the main
254
+ // thread.
255
+ // avoid method refs on Android due to some issues with older AGP setups
256
+ // noinspection Convert2MethodRef
257
+ handler .post (() -> removeObserverInternal ());
258
+ }
259
+ }
260
+ }
261
+
262
+ private void removeObserverInternal () {
263
+ final @ Nullable ReceiverLifecycleHandler watcherRef = lifecycleHandler ;
264
+ if (watcherRef != null ) {
265
+ ProcessLifecycleOwner .get ().getLifecycle ().removeObserver (watcherRef );
266
+ }
267
+ lifecycleHandler = null ;
268
+ }
269
+
270
+ @ Override
271
+ public void close () throws IOException {
272
+ try (final @ NotNull ISentryLifecycleToken ignored = receiverLock .acquire ()) {
273
+ isClosed = true ;
274
+ filter = null ;
275
+ }
276
+
277
+ removeLifecycleObserver ();
278
+ unregisterReceiver ();
279
+
280
+ if (options != null ) {
281
+ options .getLogger ().log (SentryLevel .DEBUG , "SystemEventsBreadcrumbsIntegration remove." );
135
282
}
136
283
}
137
284
@@ -164,18 +311,23 @@ private void startSystemEventsReceiver(
164
311
return actions ;
165
312
}
166
313
167
- @ Override
168
- public void close () throws IOException {
169
- try (final @ NotNull ISentryLifecycleToken ignored = startLock .acquire ()) {
170
- isClosed = true ;
171
- }
172
- if (receiver != null ) {
173
- context .unregisterReceiver (receiver );
174
- receiver = null ;
314
+ final class ReceiverLifecycleHandler implements DefaultLifecycleObserver {
315
+ @ Override
316
+ public void onStart (@ NonNull LifecycleOwner owner ) {
317
+ if (scopes == null || options == null ) {
318
+ return ;
319
+ }
175
320
176
- if ( options != null ) {
177
- options . getLogger (). log ( SentryLevel . DEBUG , "SystemEventsBreadcrumbsIntegration remove." ) ;
321
+ try ( final @ NotNull ISentryLifecycleToken ignored = receiverLock . acquire () ) {
322
+ isStopped = false ;
178
323
}
324
+
325
+ registerReceiver (scopes , options , /* reportAsNewIntegration = */ false );
326
+ }
327
+
328
+ @ Override
329
+ public void onStop (@ NonNull LifecycleOwner owner ) {
330
+ unregisterReceiver ();
179
331
}
180
332
}
181
333
0 commit comments