33
44using System ;
55using System . Linq ;
6+ using System . Runtime . CompilerServices ;
67using Android . App ;
78using Android . Content . PM ;
89using Android . Views ;
@@ -38,10 +39,15 @@ public partial class OsuGameAndroid : OsuGame
3839 private readonly BindableDouble audioOffset = new BindableDouble ( ) ;
3940
4041 /// <summary>
41- /// Native bridge manager — kept as a separate type so OboeAudioBridge / VulkanProbe
42- /// types are never loaded during OsuGameAndroid class initialisation.
42+ /// Boxed reference to the native bridge manager.
43+ /// Declared as <c>object?</c> so that the runtime never resolves the concrete
44+ /// AndroidNativeBridgeManager type (and its P/Invoke field types) during
45+ /// OsuGameAndroid class initialisation — which would trigger
46+ /// NativeLibrary.TryLoad before the framework is ready and crash on some
47+ /// Samsung devices.
48+ /// All access goes through [NoInlining] helpers below.
4349 /// </summary>
44- private AndroidNativeBridgeManager ? nativeBridges ;
50+ private object ? nativeBridges ;
4551
4652 public OsuGameAndroid ( OsuGameActivity activity )
4753 : base ( null )
@@ -122,8 +128,7 @@ protected override void LoadComplete()
122128 {
123129 if ( e . NewValue )
124130 {
125- nativeBridges ??= new AndroidNativeBridgeManager ( ) ;
126- nativeBridges . StartOboeBridge ( Scheduler , latency =>
131+ startOboeBridge ( latency =>
127132 {
128133 // Only auto-suggest when the user hasn't already configured a manual offset.
129134 if ( Math . Abs ( audioOffset . Value ) >= 0.01 )
@@ -136,7 +141,7 @@ protected override void LoadComplete()
136141 }
137142 else
138143 {
139- nativeBridges ? . StopOboeBridge ( ) ;
144+ stopOboeBridge ( ) ;
140145 }
141146 }
142147 catch ( Exception ex )
@@ -150,14 +155,9 @@ protected override void LoadComplete()
150155 try
151156 {
152157 if ( e . NewValue )
153- {
154- nativeBridges ??= new AndroidNativeBridgeManager ( ) ;
155- nativeBridges . StartVulkanProbe ( ) ;
156- }
158+ startVulkanProbe ( ) ;
157159 else
158- {
159- nativeBridges ? . StopVulkanProbe ( ) ;
160- }
160+ stopVulkanProbe ( ) ;
161161 }
162162 catch ( Exception ex )
163163 {
@@ -244,7 +244,54 @@ private void selectHighestRefreshRate()
244244 /// </summary>
245245 public double GetMeasuredAudioLatencyMs ( )
246246 {
247- return nativeBridges ? . GetMeasuredAudioLatencyMs ( ) ?? - 1 ;
247+ return getMeasuredAudioLatencyFromBridge ( ) ;
248+ }
249+
250+ // ── Native bridge helpers ──────────────────────────────────────────
251+ // Every method below is [NoInlining] so that AndroidNativeBridgeManager
252+ // (and its P/Invoke field types) are never resolved until explicitly called.
253+
254+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
255+ private void startOboeBridge ( Action < double > onLatencyMeasured )
256+ {
257+ nativeBridges ??= new AndroidNativeBridgeManager ( ) ;
258+
259+ if ( nativeBridges is AndroidNativeBridgeManager mgr )
260+ mgr . StartOboeBridge ( Scheduler , onLatencyMeasured ) ;
261+ }
262+
263+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
264+ private void stopOboeBridge ( )
265+ {
266+ ( nativeBridges as AndroidNativeBridgeManager ) ? . StopOboeBridge ( ) ;
267+ }
268+
269+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
270+ private void startVulkanProbe ( )
271+ {
272+ nativeBridges ??= new AndroidNativeBridgeManager ( ) ;
273+
274+ if ( nativeBridges is AndroidNativeBridgeManager mgr )
275+ mgr . StartVulkanProbe ( ) ;
276+ }
277+
278+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
279+ private void stopVulkanProbe ( )
280+ {
281+ ( nativeBridges as AndroidNativeBridgeManager ) ? . StopVulkanProbe ( ) ;
282+ }
283+
284+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
285+ private double getMeasuredAudioLatencyFromBridge ( )
286+ {
287+ return ( nativeBridges as AndroidNativeBridgeManager ) ? . GetMeasuredAudioLatencyMs ( ) ?? - 1 ;
288+ }
289+
290+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
291+ private void disposeNativeBridges ( )
292+ {
293+ ( nativeBridges as AndroidNativeBridgeManager ) ? . Dispose ( ) ;
294+ nativeBridges = null ;
248295 }
249296
250297 protected override void ScreenChanged ( IOsuScreen ? current , IOsuScreen ? newScreen )
@@ -303,9 +350,7 @@ public override void SetHost(GameHost host)
303350 protected override void Dispose ( bool isDisposing )
304351 {
305352 base . Dispose ( isDisposing ) ;
306-
307- nativeBridges ? . Dispose ( ) ;
308- nativeBridges = null ;
353+ disposeNativeBridges ( ) ;
309354 }
310355
311356 private class AndroidBatteryInfo : BatteryInfo
0 commit comments