Fix Android startup crash: lock activity to SensorLandscape in manifest - #228
Merged
Merged
Conversation
…untime orientation write on phones The activity launched in the device's sensor orientation (often portrait on a phone), then OnCreate assigned RequestedOrientation = SensorLandscape, which triggered an immediate orientation change. The SurfaceView's underlying ANativeWindow was destroyed and recreated while SDL's draw thread was concurrently initialising the Vulkan swapchain → vkCreateAndroidSurfaceKHR raced on a stale ANativeWindow and the process crashed a few seconds into startup with the user-visible pattern: rotation animation → black screen → crash. Declaring ScreenOrientation in the [Activity] attribute bakes the orientation into the manifest so Android creates the activity in landscape from the start. The runtime re-assignment is now applied only on tablets (where FullUser is intentionally more permissive than the manifest default); on phones it would be redundant and on some OEMs can still nudge the SurfaceView into a brief recreate cycle. Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/03b83d23-052b-4cd1-9469-9b208e7669d5 Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com>
Copilot created this pull request from a session on behalf of
winnerspiros
April 21, 2026 21:39
View session
…use of startup crash) The fork's Release-only PropertyGroup enabled PublishTrimmed + TrimMode=partial + RunAOTCompilation + AndroidEnableProfiledAot, with SuppressTrimAnalysisWarnings=true masking the build-time signal. Only Microsoft.CSharp and ppy.Veldrid.SPIRV were rooted via TrimmerRootAssembly — osu.Game, the rulesets, Realm, Newtonsoft.Json, AutoMapper, Sentry, OsuTK and other reflection-heavy assemblies were not. A trimmed method/type or an un-AOT'd profiled-AOT call site throws TypeLoadException / MissingMethodException early in OsuGame construction, before the file logger is open; the process dies on the SDLThread / .NET ThreadPool thread with only a tombstone, so the user gets no osu.log and Logcat shows a bare native crash. Visually: splash → screen rotates into landscape → black for a few seconds → crash, exactly as reported. Upstream ppy/osu's osu.Android.props enables NEITHER trimming NOR AOT — we now match that baseline. The remaining release-only knobs (AndroidEnableAssemblyCompression, DebugType=none, DebugSymbols=false) and the Microsoft.CSharp PackageReference / both TrimmerRootAssembly entries are all removed since they only existed to support the trim/AOT pipeline. Untouched: AndroidEnableMarshalMethods=false (still needed), the FixRuntimePackAssetTypes target (still needed for the Linux libbass.so leak), and the ppy.osu.Framework.NativeLibs ExcludeAssets="all" exclusion (still needed for the same). The earlier orientation-in-manifest tweak in OsuGameActivity.cs is kept as defensive hardening but its comment is updated to be honest that it is not the actual fix. Re-enabling trimming/AOT in the future requires: rooting every reflection-using assembly explicitly, and running with SuppressTrimAnalysisWarnings=false to surface the gaps before shipping. Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/dbc925d8-a57f-47c5-827b-5141f9eaa0d6 Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com>
1. OsuGameActivity.OnCreate: wrap Microsoft.Maui.ApplicationModel.Platform.Init in try/catch. MAUI Essentials pulls in workload-version-sensitive initialisation that can throw TypeLoadException/MissingMethodException on the UI thread before the managed logger is up if the build-machine workload drifts from the device runtime — users would see only a native tombstone with no osu.log, matching the "rotate → black → crash" pattern. 2. OsuGameActivity.OnCreate: wrap the SurfaceHolder.AddCallback registration that is posted onto the UI thread message queue. A later race with activity teardown can make the Post'd lambda execute after the SurfaceView is gone, throwing an unhandled exception on the UI thread. 3. OsuGameAndroid.LoadComplete: the outer try/catch around the thread-affinity setup only covers the Scheduler.Add *call* (which enqueues the lambda), not the lambda body, which runs later on the update thread. Null-guard Host.DrawThread and Host.InputThread (via ?. chaining) and wrap the lambda body in its own try/catch so an NRE (if Host or its threads are null during a teardown race) can't escape as an unhandled update-thread exception and kill the framework. All three are defensive — none of them replace the AOT/trimming fix from the previous commit, but together they close three real "silent crash with no logs" paths that the fork introduced on top of upstream. Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/dbc925d8-a57f-47c5-827b-5141f9eaa0d6 Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com>
winnerspiros
approved these changes
Apr 21, 2026
There was a problem hiding this comment.
Pull request overview
Disables Android Release trimming/profiled-AOT settings and adds Android startup hardening (manifest-declared landscape orientation + defensive exception guards) to prevent early, no-log startup crashes.
Changes:
- Remove Release-time trimming/AOT configuration (and related rooting/package items) from
osu.Android.props. - Declare
ScreenOrientation=SensorLandscapeinOsuGameActivitymanifest attribute and avoid redundant runtime orientation writes on phones. - Add defensive try/catch + null-guards around MAUI
Platform.Init, surface callback registration, and deferred thread-affinity scheduling.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| osu.Android/OsuGameAndroid.cs | Hardens deferred scheduling for render/input thread affinity pinning to avoid update-thread unhandled exceptions. |
| osu.Android/OsuGameActivity.cs | Locks initial orientation via manifest attribute; adds defensive guards around MAUI init and SurfaceHolder callback registration. |
| osu.Android.props | Removes Release trimming/profiled-AOT configuration and related rooting, keeping only safe release packaging knobs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+230
to
233
| try | ||
| { | ||
| try | ||
| Host?.DrawThread?.Scheduler.Add(() => | ||
| { |
| // NRE from Host.DrawThread/Host.InputThread being null (or a Host | ||
| // teardown race during startup) can't escape as an unhandled update- | ||
| // thread exception and kill the framework. | ||
| Debug.WriteLine($"[osu!] Failed to enqueue thread-affinity pinning for render/input threads: {e.Message}"); |
| } | ||
| catch (Exception e) | ||
| { | ||
| Debug.WriteLine($"[osu!] MAUI Platform.Init failed (non-fatal): {e.Message}"); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PublishTrimmed+TrimMode=partial+SuppressTrimAnalysisWarnings+RunAOTCompilation+AndroidEnableProfiledAoton top of upstream. Upstreamppy/osuships with none of these. Trim- or profiled-AOT-inducedTypeLoadException/MissingMethodExceptionearly inOsuGameconstruction crashes before the file logger is up → noosu.log, only a tombstone — exactly the "rotate → black → crash, no logs" pattern.PublishTrimmed,TrimMode,SuppressTrimAnalysisWarnings,RunAOTCompilation,AndroidEnableProfiledAot,AndroidStripILAfterAOT(match upstream)TrimmerRootAssembly+Microsoft.CSharpitemsMicrosoft.Maui.ApplicationModel.Platform.Initwith try/catch (workload-version drift is a real silent-crash path)SurfaceHolder.AddCallbacklambda (race with teardown)Host.DrawThread/Host.InputThreadin the scheduled thread-affinity lambda, and wrap its body in try/catch (the outer try/catch only covered theScheduler.Addenqueue, not the deferred lambda body; an NRE there would be unhandled on the update thread)AndroidEnableAssemblyCompression,DebugType=none,AndroidEnableMarshalMethods=false, theFixRuntimePackAssetTypestarget, and theppy.osu.Framework.NativeLibsExcludeAssets="all"exclusion (all unrelated to the crash, all still needed)