Skip to content
This repository was archived by the owner on Dec 20, 2022. It is now read-only.

Commit da74867

Browse files
authored
ndk-example: Demonstrate Looper usage without winit (#161)
* ndk-example: Demonstrate Looper usage without winit Originally proposed [here], this adds a simple looper example demonstrating how to use Android's `Looper` through the NDK bindings in similar fashion to `winit`. It uses `ndk_glue`'s event pipe, Android's `InputQueue` and a custom Unix pipe to transfer events to the Looper (thread). [here]: rust-mobile/ndk#111 (comment)
1 parent 27c073a commit da74867

File tree

2 files changed

+163
-0
lines changed

2 files changed

+163
-0
lines changed

examples/Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ publish = false
77

88
[target.'cfg(target_os = "android")'.dependencies]
99
jni = "0.18.0"
10+
libc = "0.2"
1011
log = "0.4.14"
1112
ndk = { path = "../ndk", features = ["trace"] }
1213
ndk-glue = { path = "../ndk-glue", features = ["logger"] }
@@ -19,6 +20,10 @@ crate-type = ["cdylib"]
1920
name = "jni_audio"
2021
crate-type = ["cdylib"]
2122

23+
[[example]]
24+
name = "looper"
25+
crate-type = ["cdylib"]
26+
2227
[package.metadata.android.sdk]
2328
min_sdk_version = 16
2429
target_sdk_version = 29

examples/examples/looper.rs

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
//! Demonstrates how to manage application lifetime using Android's `Looper`
2+
3+
use std::mem::MaybeUninit;
4+
use std::os::unix::prelude::RawFd;
5+
use std::time::Duration;
6+
7+
use log::info;
8+
use ndk::event::{InputEvent, Keycode};
9+
use ndk::looper::{FdEvent, Poll, ThreadLooper};
10+
11+
const U32_SIZE: usize = std::mem::size_of::<u32>();
12+
13+
#[cfg_attr(
14+
target_os = "android",
15+
ndk_glue::main(backtrace = "on", logger(level = "debug"))
16+
)]
17+
fn main() {
18+
// Retrieve the Looper that ndk_glue created for us on the current thread.
19+
// Android uses this to block on events and poll file descriptors with a single mechanism.
20+
let looper =
21+
ThreadLooper::for_thread().expect("ndk-glue did not attach thread looper before main()!");
22+
23+
// First free number after ndk_glue::NDK_GLUE_LOOPER_INPUT_QUEUE_IDENT. This might be fragile.
24+
const CUSTOM_EVENT_IDENT: i32 = ndk_glue::NDK_GLUE_LOOPER_INPUT_QUEUE_IDENT + 1;
25+
26+
fn create_pipe() -> [RawFd; 2] {
27+
let mut ends = MaybeUninit::<[RawFd; 2]>::uninit();
28+
assert_eq!(unsafe { libc::pipe(ends.as_mut_ptr().cast()) }, 0);
29+
unsafe { ends.assume_init() }
30+
}
31+
32+
// Create a Unix pipe to send custom events to the Looper. ndk-glue uses a similar mechanism to deliver
33+
// ANativeActivityCallbacks asynchronously to the Looper through NDK_GLUE_LOOPER_EVENT_PIPE_IDENT.
34+
let custom_event_pipe = create_pipe();
35+
let custom_callback_pipe = create_pipe();
36+
37+
// Attach the reading end of the pipe to the looper, so that it wakes up
38+
// whenever data is available for reading (FdEvent::INPUT)
39+
looper
40+
.as_foreign()
41+
.add_fd(
42+
custom_event_pipe[0],
43+
CUSTOM_EVENT_IDENT,
44+
FdEvent::INPUT,
45+
std::ptr::null_mut(),
46+
)
47+
.expect("Failed to add file descriptor to Looper");
48+
49+
// Attach the reading end of a pipe to a callback, too
50+
looper
51+
.as_foreign()
52+
.add_fd_with_callback(custom_callback_pipe[0], FdEvent::INPUT, |fd| {
53+
let mut recv = !0u32;
54+
assert_eq!(
55+
unsafe { libc::read(fd, &mut recv as *mut _ as *mut _, U32_SIZE) } as usize,
56+
U32_SIZE
57+
);
58+
info!("Read custom event from pipe, in callback: {}", recv);
59+
// Detach this handler by returning `false` once the count reaches 5
60+
recv < 5
61+
})
62+
.expect("Failed to add file descriptor to Looper");
63+
64+
std::thread::spawn(move || {
65+
// Send a "custom event" to the looper every second
66+
for i in 0.. {
67+
let i_addr = &i as *const _ as *const _;
68+
std::thread::sleep(Duration::from_secs(1));
69+
assert_eq!(
70+
unsafe { libc::write(custom_event_pipe[1], i_addr, U32_SIZE) },
71+
U32_SIZE as isize
72+
);
73+
assert_eq!(
74+
unsafe { libc::write(custom_callback_pipe[1], i_addr, U32_SIZE,) },
75+
U32_SIZE as isize
76+
);
77+
}
78+
});
79+
80+
let mut exit = false;
81+
82+
while !exit {
83+
// looper.poll_*_timeout(timeout) to not block indefinitely.
84+
// Pass a timeout of Duration::ZERO to never block.
85+
match looper.poll_all().unwrap() {
86+
Poll::Wake => { /* looper.as_foreign().wake() was called */ }
87+
Poll::Callback => {
88+
/* An event with a registered callback was received.
89+
* Only received when polling for single events with poll_once_*
90+
*/
91+
unreachable!()
92+
}
93+
Poll::Timeout => {
94+
/* Timed out as per poll_*_timeout */
95+
unreachable!()
96+
}
97+
Poll::Event {
98+
ident,
99+
fd,
100+
events: _,
101+
data: _,
102+
} => {
103+
info!("File descriptor event on identifier {}", ident);
104+
match ident {
105+
ndk_glue::NDK_GLUE_LOOPER_EVENT_PIPE_IDENT => {
106+
// One of the callbacks in ANativeActivityCallbacks is called, and delivered
107+
// to this application asynchronously by ndk_glue through a pipe.
108+
// These consist mostly of important lifecycle and window events! Graphics
109+
// applications will create and destroy their output surface/swapchain here.
110+
info!(
111+
"Event pipe yields: {:?}",
112+
ndk_glue::poll_events()
113+
.expect("Looper says event-pipe has data available!")
114+
)
115+
}
116+
ndk_glue::NDK_GLUE_LOOPER_INPUT_QUEUE_IDENT => {
117+
let input_queue = ndk_glue::input_queue();
118+
let input_queue = input_queue.as_ref().expect("Input queue not attached");
119+
assert!(input_queue.has_events().unwrap());
120+
// Consume as many events as possible
121+
while let Some(event) = input_queue.get_event() {
122+
// Pass the event by a possible IME (Input Method Editor, ie. an open keyboard) first
123+
if let Some(event) = input_queue.pre_dispatch(event) {
124+
info!("Input event {:?}", event);
125+
let mut handled = false;
126+
if let InputEvent::KeyEvent(key_event) = &event {
127+
if key_event.key_code() == Keycode::Back {
128+
// Gracefully stop the app when the user presses the back button
129+
exit = true;
130+
handled = true;
131+
}
132+
}
133+
// Let Android know that we did not consume the event
134+
// (Pass true here if you did)
135+
input_queue.finish_event(event, handled);
136+
}
137+
}
138+
}
139+
CUSTOM_EVENT_IDENT => {
140+
// Expect to receive 32-bit numbers to describe events,
141+
// as sent by the thread above
142+
let mut recv = !0u32;
143+
assert_eq!(
144+
unsafe { libc::read(fd, &mut recv as *mut _ as *mut _, U32_SIZE) }
145+
as usize,
146+
U32_SIZE
147+
);
148+
info!("Read custom event from pipe: {}", recv);
149+
}
150+
i => panic!("Unexpected event identifier {}", i),
151+
}
152+
}
153+
}
154+
}
155+
156+
// Stop the activity
157+
ndk_glue::native_activity().finish()
158+
}

0 commit comments

Comments
 (0)