Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions osu.Android/Linker.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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. -->
Comment on lines +108 to +116
<assembly fullname="osu.Framework" preserve="all" />
<assembly fullname="osu.Framework.Android" preserve="all" />
<assembly fullname="ppy.Veldrid.SPIRV" preserve="all" />
Expand Down
21 changes: 8 additions & 13 deletions osu.Android/OsuGameActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Comment on lines +92 to +96
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors,
"osu.Framework.Android.AndroidGameActivity", "osu.Framework.Android")]
public OsuGameActivity()
{
game = new OsuGameAndroid(this);
Expand Down
Loading