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
This commit is contained in:
Robert Bragg
2026-03-02 16:28:50 +00:00
parent ae24c96dcc
commit 4ff35807fb
3 changed files with 55 additions and 35 deletions
+11 -13
View File
@@ -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)");
}
}
}
+33 -11
View File
@@ -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`.
/// 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
/// 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<F>(&self, timeout: Option<Duration>, callback: F)
where
F: FnMut(PollEvent<'_>),
+11 -11
View File
@@ -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)");
}
}
}