diff --git a/osu.Android/Linker.xml b/osu.Android/Linker.xml index 6e1c6b2038b5..3325da1c3c4f 100644 --- a/osu.Android/Linker.xml +++ b/osu.Android/Linker.xml @@ -104,13 +104,16 @@ "to silence the warnings" allows ILLink to freely trim JNI-only constructors from AndroidGameActivity, crashing the app on launch. - ADDITIONAL DEFENCE (v2026.508.227+): OsuGameActivity now also declares its own - explicit (IntPtr, JniHandleOwnership) constructor that chains to base. Because - osu.Android is unambiguously preserve="all" and is always resolvable at - descriptor-processing time, this makes the JNI-activation constructor available - in the concrete type regardless of whether the IL2007-deferred rule for - osu.Framework.Android is honoured. The v2026.508.226 field crash confirmed - that the deferred rule alone is not always sufficient. --> + ADDITIONAL DEFENCE (v2026.508.228+): OsuGameActivity carries a [DynamicDependency] + attribute that explicitly roots AndroidGameActivity's constructors from the + osu.Android assembly. Because osu.Android is unambiguously preserve="all" and is + always resolvable at descriptor-processing time, ILLink processes this root + reference before it can trim the JNI-activation constructor, regardless of whether + the IL2007-deferred rule for osu.Framework.Android is honoured. This supersedes + the v2026.508.227 approach (explicit OsuGameActivity(IntPtr, JniHandleOwnership) + constructor) which did not compile because AndroidGameActivity does not expose an + explicit (IntPtr, JniHandleOwnership) constructor — only its SDLActivity grandparent + does, and C# base() invocations resolve only against the direct parent class. --> diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index 8689971de8ec..54f3f8beb9b3 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -15,6 +15,7 @@ using System.Threading; using System.Threading.Tasks; using System; +using System.Diagnostics.CodeAnalysis; using Uri = Android.Net.Uri; using ManagedBass; // Required for Bass.AndroidAAudio + Bass.DevicePeriod startup init (FLAG_BASS_AAUDIO_ENABLED path in OnCreate) using osu.Android.Input; @@ -88,19 +89,13 @@ protected override osu.Framework.Game CreateGame() return game; } - // JNI activation constructor. Android's TypeManager.Activate resolves this - // constructor via reflection when recreating the managed wrapper from a JNI - // handle (e.g. after process restore or across configuration changes). Without - // an explicit declaration here the linker might only find the inherited version - // in osu.Framework.Android — an assembly that ILLink cannot resolve during - // descriptor processing (IL2007), making the preserve="all" rule unreliable for - // inherited members. Declaring it in osu.Android (which is unambiguously - // preserve="all") guarantees the constructor survives trimming. - protected OsuGameActivity(IntPtr handle, JniHandleOwnership transfer) - : base(handle, transfer) - { - } - + // Preserve AndroidGameActivity's (IntPtr, JniHandleOwnership) JNI-activation constructor. + // The [assembly: preserve="all"] descriptor for osu.Framework.Android in Linker.xml is deferred + // (IL2007) because the framework assembly is not yet in ILLink's search path at descriptor time. + // Placing [DynamicDependency] on this constructor (which lives in osu.Android — always resolvable) + // roots the AndroidGameActivity constructor in ILLink's walk before it can be trimmed away. + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors, + "osu.Framework.Android.AndroidGameActivity", "osu.Framework.Android")] public OsuGameActivity() { game = new OsuGameAndroid(this);