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 .IHub ;
3135import io .sentry .Integration ;
3236import io .sentry .SentryLevel ;
3337import io .sentry .SentryOptions ;
3438import io .sentry .android .core .internal .util .AndroidCurrentDateProvider ;
39+ import io .sentry .android .core .internal .util .AndroidMainThreadChecker ;
3540import io .sentry .android .core .internal .util .Debouncer ;
3641import io .sentry .util .Objects ;
3742import io .sentry .util .StringUtils ;
@@ -49,29 +54,46 @@ public final class SystemEventsBreadcrumbsIntegration implements Integration, Cl
4954
5055 private final @ NotNull Context context ;
5156
52- @ TestOnly @ Nullable SystemEventsBroadcastReceiver receiver ;
57+ @ TestOnly @ Nullable volatile SystemEventsBroadcastReceiver receiver ;
58+
59+ @ TestOnly @ Nullable volatile ReceiverLifecycleHandler lifecycleHandler ;
60+
61+ private final @ NotNull MainLooperHandler handler ;
5362
5463 private @ Nullable SentryAndroidOptions options ;
5564
65+ private @ Nullable IHub hub ;
66+
5667 private final @ NotNull String [] actions ;
57- private boolean isClosed = false ;
58- private final @ NotNull Object startLock = new Object ();
68+ private volatile boolean isClosed = false ;
69+ private volatile boolean isStopped = false ;
70+ private volatile IntentFilter filter = null ;
71+ private final @ NotNull Object receiverLock = new Object ();
5972
6073 public SystemEventsBreadcrumbsIntegration (final @ NotNull Context context ) {
6174 this (context , getDefaultActionsInternal ());
6275 }
6376
6477 private SystemEventsBreadcrumbsIntegration (
6578 final @ NotNull Context context , final @ NotNull String [] actions ) {
79+ this (context , actions , new MainLooperHandler ());
80+ }
81+
82+ SystemEventsBreadcrumbsIntegration (
83+ final @ NotNull Context context ,
84+ final @ NotNull String [] actions ,
85+ final @ NotNull MainLooperHandler handler ) {
6686 this .context = ContextUtils .getApplicationContext (context );
6787 this .actions = actions ;
88+ this .handler = handler ;
6889 }
6990
7091 public SystemEventsBreadcrumbsIntegration (
7192 final @ NotNull Context context , final @ NotNull List <String > actions ) {
7293 this .context = ContextUtils .getApplicationContext (context );
7394 this .actions = new String [actions .size ()];
7495 actions .toArray (this .actions );
96+ this .handler = new MainLooperHandler ();
7597 }
7698
7799 @ Override
@@ -81,6 +103,7 @@ public void register(final @NotNull IHub hub, final @NotNull SentryOptions optio
81103 Objects .requireNonNull (
82104 (options instanceof SentryAndroidOptions ) ? (SentryAndroidOptions ) options : null ,
83105 "SentryAndroidOptions is required" );
106+ this .hub = hub ;
84107
85108 this .options
86109 .getLogger ()
@@ -90,46 +113,170 @@ public void register(final @NotNull IHub hub, final @NotNull SentryOptions optio
90113 this .options .isEnableSystemEventBreadcrumbs ());
91114
92115 if (this .options .isEnableSystemEventBreadcrumbs ()) {
116+ addLifecycleObserver (this .options );
117+ registerReceiver (this .hub , this .options , /* reportAsNewIntegration = */ true );
118+ }
119+ }
93120
94- try {
95- options
96- .getExecutorService ()
97- .submit (
98- () -> {
99- synchronized (startLock ) {
100- if (!isClosed ) {
101- startSystemEventsReceiver (hub , (SentryAndroidOptions ) options );
121+ private void registerReceiver (
122+ final @ NotNull IHub hub ,
123+ final @ NotNull SentryAndroidOptions options ,
124+ final boolean reportAsNewIntegration ) {
125+
126+ if (!options .isEnableSystemEventBreadcrumbs ()) {
127+ return ;
128+ }
129+
130+ synchronized (receiverLock ) {
131+ if (isClosed || isStopped || receiver != null ) {
132+ return ;
133+ }
134+ }
135+
136+ try {
137+ options
138+ .getExecutorService ()
139+ .submit (
140+ () -> {
141+ synchronized (receiverLock ) {
142+ if (isClosed || isStopped || receiver != null ) {
143+ return ;
144+ }
145+
146+ receiver = new SystemEventsBroadcastReceiver (hub , options );
147+ if (filter == null ) {
148+ filter = new IntentFilter ();
149+ for (String item : actions ) {
150+ filter .addAction (item );
102151 }
103152 }
104- });
105- } catch (Throwable e ) {
106- options
107- .getLogger ()
108- .log (
109- SentryLevel .DEBUG ,
110- "Failed to start SystemEventsBreadcrumbsIntegration on executor thread." ,
111- e );
112- }
153+ try {
154+ // registerReceiver can throw SecurityException but it's not documented in the
155+ // official docs
156+ ContextUtils .registerReceiver (context , options , receiver , filter );
157+ if (reportAsNewIntegration ) {
158+ options
159+ .getLogger ()
160+ .log (SentryLevel .DEBUG , "SystemEventsBreadcrumbsIntegration installed." );
161+ addIntegrationToSdkVersion ("SystemEventsBreadcrumbs" );
162+ }
163+ } catch (Throwable e ) {
164+ options .setEnableSystemEventBreadcrumbs (false );
165+ options
166+ .getLogger ()
167+ .log (
168+ SentryLevel .ERROR ,
169+ "Failed to initialize SystemEventsBreadcrumbsIntegration." ,
170+ e );
171+ }
172+ }
173+ });
174+ } catch (Throwable e ) {
175+ options
176+ .getLogger ()
177+ .log (
178+ SentryLevel .WARNING ,
179+ "Failed to start SystemEventsBreadcrumbsIntegration on executor thread." );
113180 }
114181 }
115182
116- private void startSystemEventsReceiver (
117- final @ NotNull IHub hub , final @ NotNull SentryAndroidOptions options ) {
118- receiver = new SystemEventsBroadcastReceiver ( hub , options );
119- final IntentFilter filter = new IntentFilter () ;
120- for ( String item : actions ) {
121- filter . addAction ( item ) ;
183+ private void unregisterReceiver () {
184+ final @ Nullable SystemEventsBroadcastReceiver receiverRef ;
185+ synchronized ( receiverLock ) {
186+ isStopped = true ;
187+ receiverRef = receiver ;
188+ receiver = null ;
122189 }
190+
191+ if (receiverRef != null ) {
192+ context .unregisterReceiver (receiverRef );
193+ }
194+ }
195+
196+ // TODO: this duplicates a lot of AppLifecycleIntegration. We should register once on init
197+ // and multiplex to different listeners rather.
198+ private void addLifecycleObserver (final @ NotNull SentryAndroidOptions options ) {
123199 try {
124- // registerReceiver can throw SecurityException but it's not documented in the official docs
125- ContextUtils .registerReceiver (context , options , receiver , filter );
126- options .getLogger ().log (SentryLevel .DEBUG , "SystemEventsBreadcrumbsIntegration installed." );
127- addIntegrationToSdkVersion ("SystemEventsBreadcrumbs" );
200+ Class .forName ("androidx.lifecycle.DefaultLifecycleObserver" );
201+ Class .forName ("androidx.lifecycle.ProcessLifecycleOwner" );
202+ if (AndroidMainThreadChecker .getInstance ().isMainThread ()) {
203+ addObserverInternal (options );
204+ } else {
205+ // some versions of the androidx lifecycle-process require this to be executed on the main
206+ // thread.
207+ handler .post (() -> addObserverInternal (options ));
208+ }
209+ } catch (ClassNotFoundException e ) {
210+ options
211+ .getLogger ()
212+ .log (
213+ SentryLevel .WARNING ,
214+ "androidx.lifecycle is not available, SystemEventsBreadcrumbsIntegration won't be able"
215+ + " to register/unregister an internal BroadcastReceiver. This may result in an"
216+ + " increased ANR rate on Android 14 and above." );
217+ } catch (Throwable e ) {
218+ options
219+ .getLogger ()
220+ .log (
221+ SentryLevel .ERROR ,
222+ "SystemEventsBreadcrumbsIntegration could not register lifecycle observer" ,
223+ e );
224+ }
225+ }
226+
227+ private void addObserverInternal (final @ NotNull SentryAndroidOptions options ) {
228+ lifecycleHandler = new ReceiverLifecycleHandler ();
229+
230+ try {
231+ ProcessLifecycleOwner .get ().getLifecycle ().addObserver (lifecycleHandler );
128232 } catch (Throwable e ) {
129- options .setEnableSystemEventBreadcrumbs (false );
233+ // This is to handle a potential 'AbstractMethodError' gracefully. The error is triggered in
234+ // connection with conflicting dependencies of the androidx.lifecycle.
235+ // //See the issue here: https://github.com/getsentry/sentry-java/pull/2228
236+ lifecycleHandler = null ;
130237 options
131238 .getLogger ()
132- .log (SentryLevel .ERROR , "Failed to initialize SystemEventsBreadcrumbsIntegration." , e );
239+ .log (
240+ SentryLevel .ERROR ,
241+ "SystemEventsBreadcrumbsIntegration failed to get Lifecycle and could not install lifecycle observer." ,
242+ e );
243+ }
244+ }
245+
246+ private void removeLifecycleObserver () {
247+ if (lifecycleHandler != null ) {
248+ if (AndroidMainThreadChecker .getInstance ().isMainThread ()) {
249+ removeObserverInternal ();
250+ } else {
251+ // some versions of the androidx lifecycle-process require this to be executed on the main
252+ // thread.
253+ // avoid method refs on Android due to some issues with older AGP setups
254+ // noinspection Convert2MethodRef
255+ handler .post (() -> removeObserverInternal ());
256+ }
257+ }
258+ }
259+
260+ private void removeObserverInternal () {
261+ final @ Nullable ReceiverLifecycleHandler watcherRef = lifecycleHandler ;
262+ if (watcherRef != null ) {
263+ ProcessLifecycleOwner .get ().getLifecycle ().removeObserver (watcherRef );
264+ }
265+ lifecycleHandler = null ;
266+ }
267+
268+ @ Override
269+ public void close () throws IOException {
270+ synchronized (receiverLock ) {
271+ isClosed = true ;
272+ filter = null ;
273+ }
274+
275+ removeLifecycleObserver ();
276+ unregisterReceiver ();
277+
278+ if (options != null ) {
279+ options .getLogger ().log (SentryLevel .DEBUG , "SystemEventsBreadcrumbsIntegration remove." );
133280 }
134281 }
135282
@@ -162,18 +309,23 @@ private void startSystemEventsReceiver(
162309 return actions ;
163310 }
164311
165- @ Override
166- public void close () throws IOException {
167- synchronized (startLock ) {
168- isClosed = true ;
169- }
170- if (receiver != null ) {
171- context .unregisterReceiver (receiver );
172- receiver = null ;
312+ final class ReceiverLifecycleHandler implements DefaultLifecycleObserver {
313+ @ Override
314+ public void onStart (@ NonNull LifecycleOwner owner ) {
315+ if (hub == null || options == null ) {
316+ return ;
317+ }
173318
174- if ( options != null ) {
175- options . getLogger (). log ( SentryLevel . DEBUG , "SystemEventsBreadcrumbsIntegration remove." ) ;
319+ synchronized ( receiverLock ) {
320+ isStopped = false ;
176321 }
322+
323+ registerReceiver (hub , options , /* reportAsNewIntegration = */ false );
324+ }
325+
326+ @ Override
327+ public void onStop (@ NonNull LifecycleOwner owner ) {
328+ unregisterReceiver ();
177329 }
178330 }
179331
0 commit comments