From 9a488fba761899776a3aaab4bad45f5dc5a37c98 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Wed, 10 Jul 2024 15:16:26 +0200 Subject: [PATCH] android: Forward `suspended()` and `resumed()` events and patch up platform-specific documentation Key them off of `onStop()` and `onStart()` which seems to match the other backends most closely. These [Android Activity lifecycle] events denote when the application is visible on-screen, and recommend that any heavy lifting for startup and shutdown happens here, as the application may be demoted to the background and later shut down entirely unless the user navigates back to it. [Android Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle --- src/application.rs | 86 +++++++++++++++++++------------- src/changelog/unreleased.md | 6 +-- src/event.rs | 36 ++++++------- src/event_loop.rs | 2 +- src/platform_impl/android/mod.rs | 8 +-- 5 files changed, 78 insertions(+), 60 deletions(-) diff --git a/src/application.rs b/src/application.rs index 2dc224c941..2dbdad8f59 100644 --- a/src/application.rs +++ b/src/application.rs @@ -41,11 +41,21 @@ pub trait ApplicationHandler { /// [`pageshow`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pageshow_event /// [`bfcache`]: https://web.dev/bfcache/ /// + /// ### Android + /// + /// On Android, the [`resumed()`] method is called when the `Activity` is (again, if after a + /// prior [`suspended()`]) being displayed to the user. This is a good place to begin drawing + /// visual elements, running animations, etc. It is driven by Android's [`onStart()`] method. + /// + /// [`onStart()`]: https://developer.android.com/reference/android/app/Activity#onStart() + /// /// ### Others /// - /// **Android / macOS / Orbital / Wayland / Windows / X11:** Unsupported. + /// **macOS / Orbital / Wayland / Windows / X11:** Unsupported. /// - /// [`resumed()`]: Self::resumed + /// [`resumed()`]: Self::resumed() + /// [`suspended()`]: Self::suspended() + /// [`exiting()`]: Self::exiting() fn resumed(&mut self, event_loop: &dyn ActiveEventLoop) { let _ = event_loop; } @@ -71,26 +81,24 @@ pub trait ApplicationHandler { /// /// ### Android /// - /// On Android, the [`can_create_surfaces()`] method is called when a new [`SurfaceView`] has - /// been created. This is expected to closely correlate with the [`onResume`] lifecycle - /// event but there may technically be a discrepancy. - /// - /// [`onResume`]: https://developer.android.com/reference/android/app/Activity#onResume() + /// On Android, the [`can_create_surfaces()`] method is called when a new [`NativeWindow`] + /// (native [`Surface`]) is created which backs the application window. This is expected to + /// closely correlate with the [`onStart`] lifecycle event which typically results in a surface + /// to be created after the app becomes visible. /// - /// Applications that need to run on Android must wait until they have been "resumed" before + /// Applications that need to run on Android must wait until they have received a surface before /// they will be able to create a render surface (such as an `EGLSurface`, [`VkSurfaceKHR`] - /// or [`wgpu::Surface`]) which depend on having a [`SurfaceView`]. Applications must also - /// assume that if they are [suspended], then their render surfaces are invalid and should - /// be dropped. + /// or [`wgpu::Surface`]) which depend on having a [`NativeWindow`]. Applications must handle + /// [`destroy_surfaces()`], where their render surfaces are invalid and should be dropped. /// - /// [suspended]: Self::destroy_surfaces - /// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView - /// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle + /// [`NativeWindow`]: https://developer.android.com/ndk/reference/group/a-native-window + /// [`Surface`]: https://developer.android.com/reference/android/view/Surface + /// [`onStart`]: https://developer.android.com/reference/android/app/Activity#onStart() /// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html /// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html /// - /// [`can_create_surfaces()`]: Self::can_create_surfaces - /// [`destroy_surfaces()`]: Self::destroy_surfaces + /// [`can_create_surfaces()`]: Self::can_create_surfaces() + /// [`destroy_surfaces()`]: Self::destroy_surfaces() fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop); /// Called after a wake up is requested using [`EventLoopProxy::wake_up()`]. @@ -235,18 +243,30 @@ pub trait ApplicationHandler { /// ### Web /// /// On Web, the [`suspended()`] method is called in response to a [`pagehide`] event if the - /// page is being restored from the [`bfcache`] (back/forward cache) - an in-memory cache that + /// page is being moved/stored in the [`bfcache`] (back/forward cache) - an in-memory cache that /// stores a complete snapshot of a page (including the JavaScript heap) as the user is /// navigating away. /// /// [`pagehide`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event /// [`bfcache`]: https://web.dev/bfcache/ /// + /// ### Android + /// + /// On Android, the [`suspended()`] method is called when the `Activity` is no longer visible to + /// the user. This is a good place to stop refreshing UI, running animations and other visual + /// things. It is driven by Android's [`onStop()`] method. + /// + /// After this event the application either receives [`resumed()`] again, or [`exiting()`]. + /// + /// [`onStop()`]: https://developer.android.com/reference/android/app/Activity#onStop() + /// /// ### Others /// - /// **Android / macOS / Orbital / Wayland / Windows / X11:** Unsupported. + /// **macOS / Orbital / Wayland / Windows / X11:** Unsupported. /// - /// [`suspended()`]: Self::suspended + /// [`resumed()`]: Self::resumed() + /// [`suspended()`]: Self::suspended() + /// [`exiting()`]: Self::exiting() fn suspended(&mut self, event_loop: &dyn ActiveEventLoop) { let _ = event_loop; } @@ -259,24 +279,22 @@ pub trait ApplicationHandler { /// /// ### Android /// - /// On Android, the [`destroy_surfaces()`] method is called when the application's associated - /// [`SurfaceView`] is destroyed. This is expected to closely correlate with the [`onPause`] - /// lifecycle event but there may technically be a discrepancy. - /// - /// [`onPause`]: https://developer.android.com/reference/android/app/Activity#onPause() + /// On Android, the [`destroy_surfaces()`] method is called when the application's + /// [`NativeWindow`] (native [`Surface`]) is destroyed. This is expected to closely correlate + /// with the [`onStop`] lifecycle event which typically results in the surface to be destroyed + /// after the app becomes invisible. /// - /// Applications that need to run on Android should assume their [`SurfaceView`] has been + /// Applications that need to run on Android should assume their [`NativeWindow`] has been /// destroyed, which indirectly invalidates any existing render surfaces that may have been /// created outside of Winit (such as an `EGLSurface`, [`VkSurfaceKHR`] or [`wgpu::Surface`]). /// - /// After being [suspended] on Android applications must drop all render surfaces before - /// the event callback completes, which may be re-created when the application is next - /// [resumed]. + /// When receiving [`destroy_surfaces()`] Android applications must drop all render surfaces + /// before the event callback completes, which may be re-created when the application next + /// receives [`can_create_surfaces()`]. /// - /// [suspended]: Self::destroy_surfaces - /// [resumed]: Self::can_create_surfaces - /// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView - /// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle + /// [`NativeWindow`]: https://developer.android.com/ndk/reference/group/a-native-window + /// [`Surface`]: https://developer.android.com/reference/android/view/Surface + /// [`onStop`]: https://developer.android.com/reference/android/app/Activity#onStop() /// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html /// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html /// @@ -284,8 +302,8 @@ pub trait ApplicationHandler { /// /// - **iOS / macOS / Orbital / Wayland / Web / Windows / X11:** Unsupported. /// - /// [`can_create_surfaces()`]: Self::can_create_surfaces - /// [`destroy_surfaces()`]: Self::destroy_surfaces + /// [`can_create_surfaces()`]: Self::can_create_surfaces() + /// [`destroy_surfaces()`]: Self::destroy_surfaces() fn destroy_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { let _ = event_loop; } diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 2a89a131cd..88e8d71fce 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -84,14 +84,14 @@ changelog entry. - Changed `EventLoopProxy::send_event` to `EventLoopProxy::wake_up`, it now only wakes up the loop. - On X11, implement smooth resizing through the sync extension API. -- `ApplicationHandler::create|destroy_surfaces()` was split off from +- `ApplicationHandler::can_create|destroy_surfaces()` was split off from `ApplicationHandler::resumed/suspended()`. `ApplicationHandler::can_create_surfaces()` should, for portability reasons to Android, be the only place to create render surfaces. - `ApplicationHandler::resumed/suspended()` are now only emitted by iOS and Web - and now signify actually resuming/suspending the application. + `ApplicationHandler::resumed/suspended()` are now only emitted by iOS, Web + and Android, and now signify actually resuming/suspending the application. - Rename `platform::web::*ExtWebSys` to `*ExtWeb`. - Change signature of `EventLoop::run_app`, `EventLoopExtPumpEvents::pump_app_events` and `EventLoopExtRunOnDemand::run_app_on_demand` to accept a `impl ApplicationHandler` directly, diff --git a/src/event.rs b/src/event.rs index 3742596d5d..d6f35be472 100644 --- a/src/event.rs +++ b/src/event.rs @@ -63,51 +63,51 @@ use crate::window::{ActivationToken, Theme, WindowId}; #[allow(dead_code)] #[derive(Debug, Clone, PartialEq)] pub(crate) enum Event { - /// See [`ApplicationHandler::new_events`] for details. + /// See [`ApplicationHandler::new_events()`] for details. /// - /// [`ApplicationHandler::new_events`]: crate::application::ApplicationHandler::new_events + /// [`ApplicationHandler::new_events()`]: crate::application::ApplicationHandler::new_events() NewEvents(StartCause), - /// See [`ApplicationHandler::window_event`] for details. + /// See [`ApplicationHandler::window_event()`] for details. /// - /// [`ApplicationHandler::window_event`]: crate::application::ApplicationHandler::window_event + /// [`ApplicationHandler::window_event()`]: crate::application::ApplicationHandler::window_event() #[allow(clippy::enum_variant_names)] WindowEvent { window_id: WindowId, event: WindowEvent }, - /// See [`ApplicationHandler::device_event`] for details. + /// See [`ApplicationHandler::device_event()`] for details. /// - /// [`ApplicationHandler::device_event`]: crate::application::ApplicationHandler::device_event + /// [`ApplicationHandler::device_event()`]: crate::application::ApplicationHandler::device_event() #[allow(clippy::enum_variant_names)] DeviceEvent { device_id: Option, event: DeviceEvent }, - /// See [`ApplicationHandler::suspended`] for details. + /// See [`ApplicationHandler::suspended()`] for details. /// - /// [`ApplicationHandler::suspended`]: crate::application::ApplicationHandler::suspended + /// [`ApplicationHandler::suspended()`]: crate::application::ApplicationHandler::suspended() Suspended, - /// See [`ApplicationHandler::can_create_surfaces`] for details. + /// See [`ApplicationHandler::can_create_surfaces()`] for details. /// - /// [`ApplicationHandler::can_create_surfaces`]: crate::application::ApplicationHandler::can_create_surfaces + /// [`ApplicationHandler::can_create_surfaces()`]: crate::application::ApplicationHandler::can_create_surfaces() CreateSurfaces, - /// See [`ApplicationHandler::resumed`] for details. + /// See [`ApplicationHandler::resumed()`] for details. /// - /// [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed + /// [`ApplicationHandler::resumed()`]: crate::application::ApplicationHandler::resumed() Resumed, - /// See [`ApplicationHandler::about_to_wait`] for details. + /// See [`ApplicationHandler::about_to_wait()`] for details. /// - /// [`ApplicationHandler::about_to_wait`]: crate::application::ApplicationHandler::about_to_wait + /// [`ApplicationHandler::about_to_wait()`]: crate::application::ApplicationHandler::about_to_wait() AboutToWait, - /// See [`ApplicationHandler::exiting`] for details. + /// See [`ApplicationHandler::exiting()`] for details. /// - /// [`ApplicationHandler::exiting`]: crate::application::ApplicationHandler::exiting + /// [`ApplicationHandler::exiting()`]: crate::application::ApplicationHandler::exiting() LoopExiting, - /// See [`ApplicationHandler::memory_warning`] for details. + /// See [`ApplicationHandler::memory_warning()`] for details. /// - /// [`ApplicationHandler::memory_warning`]: crate::application::ApplicationHandler::memory_warning + /// [`ApplicationHandler::memory_warning()`]: crate::application::ApplicationHandler::memory_warning() MemoryWarning, /// User requested a wake up. diff --git a/src/event_loop.rs b/src/event_loop.rs index edcac345f8..155a1262de 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -420,7 +420,7 @@ impl rwh_06::HasDisplayHandle for dyn ActiveEventLoop + '_ { /// A proxy for the underlying display handle. /// -/// The purpose of this type is to provide a cheaply clonable handle to the underlying +/// The purpose of this type is to provide a cheaply cloneable handle to the underlying /// display handle. This is often used by graphics APIs to connect to the underlying APIs. /// It is difficult to keep a handle to the [`EventLoop`] type or the [`ActiveEventLoop`] /// type. In contrast, this type involves no lifetimes and can be persisted for as long as diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index a93dc56dd5..9f3a9eec7e 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -217,11 +217,11 @@ impl EventLoop { app.memory_warning(&self.window_target); }, MainEvent::Start => { - // XXX: how to forward this state to applications? - warn!("TODO: forward onStart notification to application"); + app.resumed(self.window_target()); }, MainEvent::Resume { .. } => { debug!("App Resumed - is running"); + // TODO: This is incorrect - will be solved in https://github.com/rust-windowing/winit/pull/3897 self.running = true; }, MainEvent::SaveState { .. } => { @@ -231,11 +231,11 @@ impl EventLoop { }, MainEvent::Pause => { debug!("App Paused - stopped running"); + // TODO: This is incorrect - will be solved in https://github.com/rust-windowing/winit/pull/3897 self.running = false; }, MainEvent::Stop => { - // XXX: how to forward this state to applications? - warn!("TODO: forward onStop notification to application"); + app.suspended(self.window_target()); }, MainEvent::Destroy => { // XXX: maybe exit mainloop to drop things before being