Skip to content

Commit a256423

Browse files
committed
Android: rework backend to use android-activity crate
This updates the Android backend to use the android-activity crate instead of ndk-glue. This solves a few issues: 1. The backend is agnostic of the application's choice of Activity base class 2. Winit is no longer responsible for handling any Java synchronization details, since these are encapsulated by the design of android_activity 3. The backend no longer depends on global / static getters for state such as the native_window() which puts it in a better position to support running multiple activities within a single Android process. 4. Redraw requests are flagged, not queued, in a way that avoids taking priority over user events (resolves rust-windowing#2299) To make it possible for application crates to avoid explicitly depending on the `android-activity` crate (and avoid version conflicts) this re-exports the android-activity crate under: `winit::platform::android::activity::*` This also adds `android-native-activity` and `android-game-activity` features that set the corresponding android-activity features. Addresses: PR rust-windowing#1892 Addresses: PR rust-windowing#2307 Addresses: PR rust-windowing#2343 Addresses: rust-windowing#2293 Resolves: rust-windowing#2299
1 parent 92fdf5b commit a256423

File tree

7 files changed

+738
-417
lines changed

7 files changed

+738
-417
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
3434
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: x11 }
3535
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "wayland,wayland-dlopen" }
36-
- { target: aarch64-linux-android, os: ubuntu-latest, cmd: 'apk --' }
36+
- { target: aarch64-linux-android, os: ubuntu-latest, cmd: 'apk --', features: "android-native-activity" }
3737
- { target: x86_64-apple-darwin, os: macos-latest, }
3838
- { target: x86_64-apple-ios, os: macos-latest, }
3939
- { target: aarch64-apple-ios, os: macos-latest, }

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ And please only add new entries to the top of this list, right below the `# Unre
2626
- On macOS, added support for `WindowEvent::ThemeChanged`.
2727
- **Breaking:** Removed `WindowBuilderExtWindows::with_theme` and `WindowBuilderExtWayland::with_wayland_csd_theme` in favour of `WindowBuilder::with_theme`.
2828
- **Breaking:** Removed `WindowExtWindows::theme` in favour of `Window::theme`.
29+
- **Breaking:** On Android, switched to using [`android-activity`](https://github.com/rib/android-activity) crate as a glue layer instead of [`ndk-glue](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue). See [README.md#Android](https://github.com/rust-windowing/winit#Android) for more details. ([#2444](https://github.com/rust-windowing/winit/pull/2444))
2930

3031
# 0.27.4
3132

Cargo.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ wayland-dlopen = ["sctk/dlopen", "wayland-client/dlopen"]
4141
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
4242
wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
4343
wayland-csd-adwaita-notitle = ["sctk-adwaita"]
44+
android-native-activity = [ "android-activity/native-activity" ]
45+
android-game-activity = [ "android-activity/game-activity" ]
4446

4547
[dependencies]
4648
instant = { version = "0.1", features = ["wasm-bindgen"] }
@@ -59,7 +61,7 @@ simple_logger = { version = "2.1.0", default_features = false }
5961
[target.'cfg(target_os = "android")'.dependencies]
6062
# Coordinate the next winit release with android-ndk-rs: https://github.com/rust-windowing/winit/issues/1995
6163
ndk = "0.7.0"
62-
ndk-glue = "0.7.0"
64+
android-activity = "0.4.0-beta.1"
6365

6466
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
6567
objc2 = "=0.3.0-beta.3"

README.md

+73-16
Original file line numberDiff line numberDiff line change
@@ -99,36 +99,93 @@ book].
9999

100100
#### Android
101101

102-
This library makes use of the [ndk-rs](https://github.com/rust-windowing/android-ndk-rs) crates, refer to that repo for more documentation.
102+
The Android backend builds on (and exposes types from) the [`ndk`](https://docs.rs/ndk/0.7.0/ndk/) crate.
103103

104-
The `ndk-glue` version needs to match the version used by `winit`. Otherwise, the application will not start correctly as `ndk-glue`'s internal `NativeActivity` static is not the same due to version mismatch.
104+
Native Android applications need some form of "glue" crate that is responsible
105+
for defining the main entry point for your Rust application as well as tracking
106+
various life-cycle events and synchronizing with the main JVM thread.
105107

106-
`winit` compatibility table with `ndk-glue`:
108+
Winit uses the [android-activity](https://github.com/rib/android-activity) as a
109+
glue crate (prior to `0.28` it used
110+
[ndk-glue](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue).
107111

108-
| winit | ndk-glue |
109-
| :---: | :------------------: |
110-
| 0.24 | `ndk-glue = "0.2.0"` |
111-
| 0.25 | `ndk-glue = "0.3.0"` |
112-
| 0.26 | `ndk-glue = "0.5.0"` |
113-
| 0.27 | `ndk-glue = "0.7.0"` |
112+
The version of the glue crate that your application depends on _must_ match the
113+
version that Winit depends on because the glue crate is responsible for your
114+
application's main entrypoint. If Cargo resolves multiple versions they will
115+
clash.
116+
117+
`winit` glue compatibility table:
118+
119+
| winit | ndk-glue |
120+
| :---: | :--------------------------: |
121+
| 0.28 | `android-activity = "0.4"` |
122+
| 0.27 | `ndk-glue = "0.7"` |
123+
| 0.26 | `ndk-glue = "0.5"` |
124+
| 0.25 | `ndk-glue = "0.3"` |
125+
| 0.24 | `ndk-glue = "0.2"` |
126+
127+
The recommended way to avoid a conflict with the glue version is to avoid explicitly
128+
depending on the `android-activity` crate, and instead consume the API that
129+
is re-exported by Winit under `winit::platform::android::activity::*`
114130

115131
Running on an Android device needs a dynamic system library, add this to Cargo.toml:
116132

117133
```toml
118-
[[example]]
119-
name = "request_redraw_threaded"
134+
[lib]
135+
name = "main"
120136
crate-type = ["cdylib"]
121137
```
122138

123-
And add this to the example file to add the native activity glue:
139+
All Android applications are based on an `Activity` subclass and the
140+
`android-activity` crate is designed to support different choices for this base
141+
class. You application must specify the base class it needs via a feature flag:
142+
143+
| Base Class | Feature Flag | Notes |
144+
| :--------------: | :---------------: | :-----: |
145+
| `NativeActivity` | `android-native-activity` | Built-in to Android - making it possible to build some tests/demos without needing to compile any JVM code. Can give a false sense of convenience because it's often not really possible to avoid needing a build system that can compile some JVM code, to at least subclass `NativeActivity` |
146+
| [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`] which is a defacto standard `Activity` base class that helps support a wider range of Android versions. Will offer integration with [`GameTextInput`] library for soft keyboard support. Requires a build system that can compile Java and fetch Android dependencies from a Maven repository (with [Gradle] being the defacto standard build system for Android applications) |
147+
148+
[`GameActivity`]: https://developer.android.com/games/agdk/game-activity
149+
[`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input
150+
[`AndroidAppCompat`]: https://developer.android.com/reference/androidx/appcompat/app/AppCompatActivity
151+
[Gradle]: https://developer.android.com/studio/build
152+
153+
For example, add this to Cargo.toml:
154+
```toml
155+
winit = { version = "0.28", features = [ "android-game-activity" ] }
156+
157+
[target.'cfg(target_os = "android")'.dependencies]
158+
android_logger = "0.11.0"
159+
```
160+
161+
And, for example, define an entry point for your library like this:
124162
```rust
125-
#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))]
126-
fn main() {
127-
...
163+
#[cfg(target_os = "android")]
164+
use winit::platform::android::activity::AndroidApp;
165+
166+
#[cfg(target_os = "android")]
167+
#[no_mangle]
168+
fn android_main(app: AndroidApp) {
169+
use winit::platform::android::EventLoopBuilderExtAndroid;
170+
171+
android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Trace));
172+
173+
let event_loop = EventLoopBuilder::with_user_event()
174+
.with_android_app(app)
175+
.build();
176+
_main(event_loop);
128177
}
129178
```
130179

131-
And run the application with `cargo apk run --example request_redraw_threaded`
180+
For more details, refer to these `android-activity` [example applications](https://github.com/rib/android-activity/tree/main/examples).
181+
182+
##### Converting from `ndk-glue` to `android-activity`
183+
184+
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk` then the minimal changes would be:
185+
1. Remove `ndk-glue` from your `Cargo.toml`
186+
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.28", features = [ "android-native-activity" ] }`
187+
3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above).
188+
4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above).
132189

133190
#### MacOS
134191

src/event_loop.rs

+10
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,18 @@ impl<T> EventLoopBuilder<T> {
9797
/// `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`.
9898
/// If it is not set, winit will try to connect to a Wayland connection, and if that fails,
9999
/// will fall back on X11. If this variable is set with any other value, winit will panic.
100+
/// - **Android:** Must be configured with an `AndroidApp` from `android_main()` by calling
101+
/// [`.with_android_app(app)`] before calling `.build()`.
100102
///
101103
/// [`platform`]: crate::platform
104+
#[cfg_attr(
105+
target_os = "android",
106+
doc = "[`.with_android_app(app)`]: crate::platform::android::EventLoopBuilderExtAndroid::with_android_app"
107+
)]
108+
#[cfg_attr(
109+
not(target_os = "android"),
110+
doc = "[`.with_android_app(app)`]: #only-available-on-android"
111+
)]
102112
#[inline]
103113
pub fn build(&mut self) -> EventLoop<T> {
104114
static EVENT_LOOP_CREATED: OnceCell<()> = OnceCell::new();

src/platform/android.rs

+45-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use crate::{
2-
event_loop::{EventLoop, EventLoopWindowTarget},
2+
event_loop::{EventLoop, EventLoopBuilder, EventLoopWindowTarget},
33
window::{Window, WindowBuilder},
44
};
5-
use ndk::configuration::Configuration;
6-
use ndk_glue::Rect;
5+
6+
use android_activity::{AndroidApp, ConfigurationRef, Rect};
77

88
/// Additional methods on [`EventLoop`] that are specific to Android.
99
pub trait EventLoopExtAndroid {}
@@ -17,15 +17,15 @@ pub trait EventLoopWindowTargetExtAndroid {}
1717
pub trait WindowExtAndroid {
1818
fn content_rect(&self) -> Rect;
1919

20-
fn config(&self) -> Configuration;
20+
fn config(&self) -> ConfigurationRef;
2121
}
2222

2323
impl WindowExtAndroid for Window {
2424
fn content_rect(&self) -> Rect {
2525
self.window.content_rect()
2626
}
2727

28-
fn config(&self) -> Configuration {
28+
fn config(&self) -> ConfigurationRef {
2929
self.window.config()
3030
}
3131
}
@@ -36,3 +36,43 @@ impl<T> EventLoopWindowTargetExtAndroid for EventLoopWindowTarget<T> {}
3636
pub trait WindowBuilderExtAndroid {}
3737

3838
impl WindowBuilderExtAndroid for WindowBuilder {}
39+
40+
pub trait EventLoopBuilderExtAndroid {
41+
/// Associates the `AndroidApp` that was passed to `android_main()` with the event loop
42+
///
43+
/// This must be called on Android since the `AndroidApp` is not global state.
44+
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self;
45+
}
46+
47+
impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
48+
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self {
49+
self.platform_specific.android_app = Some(app);
50+
self
51+
}
52+
}
53+
54+
/// Re-export of the `android_activity` API
55+
///
56+
/// Winit re-exports the `android_activity` API for convenience so that most
57+
/// applications can rely on the Winit crate to resolve the required version of
58+
/// `android_activity` and avoid any chance of a conflict between Winit and the
59+
/// application crate.
60+
///
61+
/// Unlike most libraries there can only be a single implementation
62+
/// of the `android_activity` glue crate linked with an application because
63+
/// it is responsible for the application's `android_main()` entry point.
64+
///
65+
/// Since Winit depends on a specific version of `android_activity` the simplest
66+
/// way to avoid creating a conflict is for applications to avoid explicitly
67+
/// depending on the `android_activity` crate, and instead consume the API that
68+
/// is re-exported by Winit.
69+
///
70+
/// For compatibility applications should then import the `AndroidApp` type for
71+
/// their `android_main(app: AndroidApp)` function like:
72+
/// ```rust
73+
/// #[cfg(target_os="android")]
74+
/// use winit::platform::android::activity::AndroidApp;
75+
/// ```
76+
pub mod activity {
77+
pub use android_activity::*;
78+
}

0 commit comments

Comments
 (0)