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,50 @@ 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+ ( ( AndroidNativeBridgeManager ) nativeBridges ) . StartOboeBridge ( Scheduler , onLatencyMeasured ) ;
259+ }
260+
261+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
262+ private void stopOboeBridge ( )
263+ {
264+ ( nativeBridges as AndroidNativeBridgeManager ) ? . StopOboeBridge ( ) ;
265+ }
266+
267+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
268+ private void startVulkanProbe ( )
269+ {
270+ nativeBridges ??= new AndroidNativeBridgeManager ( ) ;
271+ ( ( AndroidNativeBridgeManager ) nativeBridges ) . StartVulkanProbe ( ) ;
272+ }
273+
274+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
275+ private void stopVulkanProbe ( )
276+ {
277+ ( nativeBridges as AndroidNativeBridgeManager ) ? . StopVulkanProbe ( ) ;
278+ }
279+
280+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
281+ private double getMeasuredAudioLatencyFromBridge ( )
282+ {
283+ return ( nativeBridges as AndroidNativeBridgeManager ) ? . GetMeasuredAudioLatencyMs ( ) ?? - 1 ;
284+ }
285+
286+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
287+ private void disposeNativeBridges ( )
288+ {
289+ ( nativeBridges as AndroidNativeBridgeManager ) ? . Dispose ( ) ;
290+ nativeBridges = null ;
248291 }
249292
250293 protected override void ScreenChanged ( IOsuScreen ? current , IOsuScreen ? newScreen )
@@ -303,9 +346,7 @@ public override void SetHost(GameHost host)
303346 protected override void Dispose ( bool isDisposing )
304347 {
305348 base . Dispose ( isDisposing ) ;
306-
307- nativeBridges ? . Dispose ( ) ;
308- nativeBridges = null ;
349+ disposeNativeBridges ( ) ;
309350 }
310351
311352 private class AndroidBatteryInfo : BatteryInfo
0 commit comments