Skip to content

Android resume panics after the render surface is recreated #25682

Description

@uwu-420

Bevy version and features

a716a99b01d7137d8d294631319f00073664a553 from 2026-09-02. The checkout reports version 0.20.0-dev.

I used the official examples/mobile crate. It uses the default Bevy features and android-game-activity.

Relevant system information

  • Samsung SM-F721B
  • Android 16 with API level 36
  • Vulkan with Adreno 730
  • Winit 0.30.13
  • Android Games Activity 4.4.0
  • android-activity 0.6.1
  • Rust 1.98.1

What you did

  1. Build and install the official examples/mobile Android example.
  2. Start the example.
  3. Press the Home button.
  4. Open the example again.

I changed only targetSdk from 33 to 35 for this device test.

What went wrong

Expected result

Bevy creates a new render surface and continues to run.

Actual result

The render thread panics during the first resume.

thread 'Render thread' panicked at crates/bevy_ecs/src/error/handler.rs:128:1
Encountered an error in command `<Enable the debug feature to see the name>`: Entity despawned: The entity with ID 305v0 is invalid; its index now has generation 1.

Additional information

Cause

The Android suspend path removes RawHandleWrapper from the main window entity. extract_windows handles that removal by despawning the synchronized render entity. The main entity keeps SyncToRenderWorld and its SubEntity<RenderApp> mapping. The mapping therefore still points to the despawned entity.

Resume adds a new RawHandleWrapper. Window extraction then queues commands for the stale render entity and the command application panics.

This behavior appears to come from #25005.

Relevant source

Candidate correction

I kept the synchronized render entity and removed only its window render components when RawHandleWrapper was removed.

 for removed_window in removed.read() {
     if let Ok(render_entity) = mapper.get(removed_window) {
-        commands.entity(render_entity.entity()).despawn();
+        #[cfg(target_os = "android")]
+        commands
+            .entity(render_entity.entity())
+            .remove::<(ExtractedWindow, RawHandleWrapper, SurfaceData)>();
+        #[cfg(not(target_os = "android"))]
+        commands.entity(render_entity.entity()).despawn();
     }
 }

The official example then completed three Home and resume cycles on the same device. This result only validates the Android suspend and resume path. The window closing path and other platforms still need review.

Related to #9057.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions