2525import android .content .Intent ;
2626import android .content .IntentFilter ;
2727import android .os .Bundle ;
28+ import androidx .annotation .NonNull ;
29+ import androidx .lifecycle .DefaultLifecycleObserver ;
30+ import androidx .lifecycle .LifecycleOwner ;
31+ import androidx .lifecycle .ProcessLifecycleOwner ;
2832import io .sentry .Breadcrumb ;
2933import io .sentry .Hint ;
3034import io .sentry .IScopes ;
3337import io .sentry .SentryLevel ;
3438import io .sentry .SentryOptions ;
3539import io .sentry .android .core .internal .util .AndroidCurrentDateProvider ;
40+ import io .sentry .android .core .internal .util .AndroidThreadChecker ;
3641import io .sentry .android .core .internal .util .Debouncer ;
3742import io .sentry .util .AutoClosableReentrantLock ;
3843import io .sentry .util .Objects ;
@@ -51,29 +56,46 @@ public final class SystemEventsBreadcrumbsIntegration implements Integration, Cl
5156
5257 private final @ NotNull Context context ;
5358
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 ;
5564
5665 private @ Nullable SentryAndroidOptions options ;
5766
67+ private @ Nullable IScopes scopes ;
68+
5869 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 ();
6174
6275 public SystemEventsBreadcrumbsIntegration (final @ NotNull Context context ) {
6376 this (context , getDefaultActionsInternal ());
6477 }
6578
6679 private SystemEventsBreadcrumbsIntegration (
6780 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 ) {
6888 this .context = ContextUtils .getApplicationContext (context );
6989 this .actions = actions ;
90+ this .handler = handler ;
7091 }
7192
7293 public SystemEventsBreadcrumbsIntegration (
7394 final @ NotNull Context context , final @ NotNull List <String > actions ) {
7495 this .context = ContextUtils .getApplicationContext (context );
7596 this .actions = new String [actions .size ()];
7697 actions .toArray (this .actions );
98+ this .handler = new MainLooperHandler ();
7799 }
78100
79101 @ Override
@@ -83,6 +105,7 @@ public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions
83105 Objects .requireNonNull (
84106 (options instanceof SentryAndroidOptions ) ? (SentryAndroidOptions ) options : null ,
85107 "SentryAndroidOptions is required" );
108+ this .scopes = scopes ;
86109
87110 this .options
88111 .getLogger ()
@@ -92,46 +115,170 @@ public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions
92115 this .options .isEnableSystemEventBreadcrumbs ());
93116
94117 if (this .options .isEnableSystemEventBreadcrumbs ()) {
118+ addLifecycleObserver (this .options );
119+ registerReceiver (this .scopes , this .options , /* reportAsNewIntegration = */ true );
120+ }
121+ }
95122
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 );
104153 }
105154 }
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." );
115182 }
116183 }
117184
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 ;
124191 }
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 ) {
125201 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 );
130234 } 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 ;
132239 options
133240 .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." );
135282 }
136283 }
137284
@@ -164,18 +311,23 @@ private void startSystemEventsReceiver(
164311 return actions ;
165312 }
166313
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+ }
175320
176- if ( options != null ) {
177- options . getLogger (). log ( SentryLevel . DEBUG , "SystemEventsBreadcrumbsIntegration remove." ) ;
321+ try ( final @ NotNull ISentryLifecycleToken ignored = receiverLock . acquire () ) {
322+ isStopped = false ;
178323 }
324+
325+ registerReceiver (scopes , options , /* reportAsNewIntegration = */ false );
326+ }
327+
328+ @ Override
329+ public void onStop (@ NonNull LifecycleOwner owner ) {
330+ unregisterReceiver ();
179331 }
180332 }
181333
0 commit comments