From f08d7b3493aae4d34018e6ef78c3336022acc99d Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Mon, 2 Mar 2026 16:28:50 +0000 Subject: [PATCH] Use ALooper_pollOnce instead of pollAll The `ALooper_pollAll` API is deprecated and considering that `poll_events` never promised the behaviour of `pollAll` we simply change the implementation to use `ALoooper_pollOnce` and assume the caller is going to anyway be calling `poll_events` within its own loop. Note: `pollOnce` can still deliver multiple callback events, and the "once" effectively just refers to only calling `epoll_wait` at most once. Considering winit for example, this should have no effect since winit will be calling `poll_events` in a loop with no assumption about how concurrent events could potentially be batched. Fixes: #170 --- android-activity/src/game_activity/mod.rs | 24 +++++------ android-activity/src/lib.rs | 46 +++++++++++++++------ android-activity/src/native_activity/mod.rs | 22 +++++----- 3 files changed, 56 insertions(+), 36 deletions(-) diff --git a/android-activity/src/game_activity/mod.rs b/android-activity/src/game_activity/mod.rs index 53889cf..1b589c6 100644 --- a/android-activity/src/game_activity/mod.rs +++ b/android-activity/src/game_activity/mod.rs @@ -15,8 +15,7 @@ use log::{error, trace}; use jni::sys::*; -use ndk_sys::ALooper_wake; -use ndk_sys::{ALooper, ALooper_pollAll}; +use ndk_sys::{ALooper, ALooper_pollOnce, ALooper_wake}; use ndk::asset::AssetManager; use ndk::configuration::Configuration; @@ -346,8 +345,8 @@ impl AndroidAppInner { } else { -1 }; - trace!("Calling ALooper_pollAll, timeout = {timeout_milliseconds}"); - let id = ALooper_pollAll( + trace!("Calling ALooper_pollOnce, timeout = {timeout_milliseconds}"); + let id = ALooper_pollOnce( timeout_milliseconds, &mut fd, &mut events, @@ -355,8 +354,7 @@ impl AndroidAppInner { ); match id { ffi::ALOOPER_POLL_WAKE => { - trace!("ALooper_pollAll returned POLL_WAKE"); - + trace!("ALooper_pollOnce returned POLL_WAKE"); if ffi::android_app_input_available_wake_up(native_app.as_ptr()) { log::debug!("Notifying Input Available"); callback(PollEvent::Main(MainEvent::InputAvailable)); @@ -365,23 +363,23 @@ impl AndroidAppInner { callback(PollEvent::Wake); } ffi::ALOOPER_POLL_CALLBACK => { - // ALooper_pollAll is documented to handle all callback sources internally so it should + // ALooper_pollOnce is documented to handle all callback sources internally so it should // never return a _CALLBACK source id... - error!("Spurious ALOOPER_POLL_CALLBACK from ALopper_pollAll() (ignored)"); + error!("Spurious ALOOPER_POLL_CALLBACK from ALooper_pollOnce() (ignored)"); } ffi::ALOOPER_POLL_TIMEOUT => { - trace!("ALooper_pollAll returned POLL_TIMEOUT"); + trace!("ALooper_pollOnce returned POLL_TIMEOUT"); callback(PollEvent::Timeout); } ffi::ALOOPER_POLL_ERROR => { // If we have an IO error with our pipe to the main Java thread that's surely // not something we can recover from - panic!("ALooper_pollAll returned POLL_ERROR"); + panic!("ALooper_pollOnce returned POLL_ERROR"); } id if id >= 0 => { match id as ffi::NativeAppGlueLooperId { ffi::NativeAppGlueLooperId_LOOPER_ID_MAIN => { - trace!("ALooper_pollAll returned ID_MAIN"); + trace!("ALooper_pollOnce returned ID_MAIN"); let source: *mut ffi::android_poll_source = source.cast(); if !source.is_null() { let cmd_i = ffi::android_app_read_cmd(native_app.as_ptr()); @@ -485,7 +483,7 @@ impl AndroidAppInner { trace!("Calling android_app_post_exec_cmd({cmd_i})"); ffi::android_app_post_exec_cmd(native_app.as_ptr(), cmd_i); } else { - panic!("ALooper_pollAll returned ID_MAIN event with NULL android_poll_source!"); + panic!("ALooper_pollOnce returned ID_MAIN event with NULL android_poll_source!"); } } _ => { @@ -494,7 +492,7 @@ impl AndroidAppInner { } } _ => { - error!("Spurious ALooper_pollAll return value {id} (ignored)"); + error!("Spurious ALooper_pollOnce return value {id} (ignored)"); } } } diff --git a/android-activity/src/lib.rs b/android-activity/src/lib.rs index f9298fa..3393562 100644 --- a/android-activity/src/lib.rs +++ b/android-activity/src/lib.rs @@ -607,24 +607,46 @@ impl AndroidApp { self.inner.read().unwrap().activity_as_ptr() } - /// Polls for any events associated with this [AndroidApp] and processes those events - /// (such as lifecycle events) via the given `callback`. - /// - /// It's important to use this API for polling, and not call [`ALooper_pollAll`] directly since - /// some events require pre- and post-processing either side of the callback. For correct - /// behavior events should be handled immediately, before returning from the callback and - /// not simply queued for batch processing later. For example the existing [`NativeWindow`] - /// is accessible during a [`MainEvent::TerminateWindow`] callback and will be - /// set to `None` once the callback returns, and this is also synchronized with the Java - /// main thread. The [`MainEvent::SaveState`] event is also synchronized with the + /// Polls for any events associated with this [AndroidApp] and processes + /// those events (such as lifecycle events) via the given `callback`. + /// + /// It's important to use this API for polling, and not call + /// [`ALooper_pollAll`] or [`ALooper_pollOnce`] directly since some events + /// require pre- and post-processing either side of the callback. For + /// correct behavior events should be handled immediately, before returning + /// from the callback and not simply queued for batch processing later. For + /// example the existing [`NativeWindow`] is accessible during a + /// [`MainEvent::TerminateWindow`] callback and will be set to `None` once + /// the callback returns, and this is also synchronized with the Java main + /// thread. The [`MainEvent::SaveState`] event is also synchronized with the /// Java main thread. /// + /// Internally this is based on [`ALooper_pollOnce`] and will only poll + /// file descriptors once per invocation. + /// + /// # Wake Events + /// + /// Note that although there is an explicit [PollEvent::Wake] that _can_ + /// indicate that the main loop was explicitly woken up (E.g. via + /// [`AndroidAppWaker::wake`]) it's possible that there will be + /// more-specific events that will be delivered after a wake up. + /// + /// In other words you should only expect to explicitly see + /// [`PollEvent::Wake`] events after an early wake up if there were no + /// other, more-specific, events that could be delivered after the wake up. + /// + /// Again, said another way - it's possible that _any_ event could + /// effectively be delivered after an early wake up so don't assume there is + /// a 1:1 relationship between invoking a wake up via + /// [`AndroidAppWaker::wake`] and the delivery of [PollEvent::Wake]. + /// /// # Panics /// - /// This must only be called from your `android_main()` thread and it may panic if called - /// from another thread. + /// This must only be called from your `android_main()` thread and it may + /// panic if called from another thread. /// /// [`ALooper_pollAll`]: ndk::looper::ThreadLooper::poll_all + /// [`ALooper_pollOnce`]: ndk::looper::ThreadLooper::poll_once pub fn poll_events(&self, timeout: Option, callback: F) where F: FnMut(PollEvent<'_>), diff --git a/android-activity/src/native_activity/mod.rs b/android-activity/src/native_activity/mod.rs index 74f2925..0a2ef1c 100644 --- a/android-activity/src/native_activity/mod.rs +++ b/android-activity/src/native_activity/mod.rs @@ -194,42 +194,42 @@ impl AndroidAppInner { -1 }; - trace!("Calling ALooper_pollAll, timeout = {timeout_milliseconds}"); + trace!("Calling ALooper_pollOnce, timeout = {timeout_milliseconds}"); assert_eq!( ndk_sys::ALooper_forThread(), self.looper.ptr, "Application tried to poll events from non-main thread" ); - let id = ndk_sys::ALooper_pollAll( + let id = ndk_sys::ALooper_pollOnce( timeout_milliseconds, &mut fd, &mut events, &mut source as *mut *mut c_void, ); - trace!("pollAll id = {id}"); + trace!("pollOnce id = {id}"); match id { ndk_sys::ALOOPER_POLL_WAKE => { - trace!("ALooper_pollAll returned POLL_WAKE"); + trace!("ALooper_pollOnce returned POLL_WAKE"); callback(PollEvent::Wake); } ndk_sys::ALOOPER_POLL_CALLBACK => { - // ALooper_pollAll is documented to handle all callback sources internally so it should + // ALooper_pollOnce is documented to handle all callback sources internally so it should // never return a _CALLBACK source id... - error!("Spurious ALOOPER_POLL_CALLBACK from ALopper_pollAll() (ignored)"); + error!("Spurious ALOOPER_POLL_CALLBACK from ALooper_pollOnce() (ignored)"); } ndk_sys::ALOOPER_POLL_TIMEOUT => { - trace!("ALooper_pollAll returned POLL_TIMEOUT"); + trace!("ALooper_pollOnce returned POLL_TIMEOUT"); callback(PollEvent::Timeout); } ndk_sys::ALOOPER_POLL_ERROR => { // If we have an IO error with our pipe to the main Java thread that's surely // not something we can recover from - panic!("ALooper_pollAll returned POLL_ERROR"); + panic!("ALooper_pollOnce returned POLL_ERROR"); } id if id >= 0 => { match id { LOOPER_ID_MAIN => { - trace!("ALooper_pollAll returned ID_MAIN"); + trace!("ALooper_pollOnce returned ID_MAIN"); if let Some(ipc_cmd) = self.native_activity.read_cmd() { let main_cmd = match ipc_cmd { // We don't forward info about the AInputQueue to apps since it's @@ -283,7 +283,7 @@ impl AndroidAppInner { } } LOOPER_ID_INPUT => { - trace!("ALooper_pollAll returned ID_INPUT"); + trace!("ALooper_pollOnce returned ID_INPUT"); // To avoid spamming the application with event loop iterations notifying them of // input events then we only send one `InputAvailable` per iteration of input @@ -298,7 +298,7 @@ impl AndroidAppInner { } } _ => { - error!("Spurious ALooper_pollAll return value {id} (ignored)"); + error!("Spurious ALooper_pollOnce return value {id} (ignored)"); } } }