#input-event #android-ndk #android #applications #events #activity #game-activity

android-activity

在 Android 上使用 NativeActivity 或 GameActivity 构建 Rust 应用程序的粘合层

16 个不稳定版本 (5 个破坏性更新)

0.6.0 2024 年 4 月 26 日
0.5.2 2024 年 1 月 30 日
0.5.1 2023 年 12 月 20 日
0.5.0 2023 年 10 月 16 日
0.1.1 2022 年 7 月 4 日

#20GUI

Download history 32110/week @ 2024-05-03 39651/week @ 2024-05-10 35089/week @ 2024-05-17 37088/week @ 2024-05-24 44874/week @ 2024-05-31 51985/week @ 2024-06-07 52941/week @ 2024-06-14 53775/week @ 2024-06-21 47445/week @ 2024-06-28 45518/week @ 2024-07-05 45803/week @ 2024-07-12 49113/week @ 2024-07-19 50438/week @ 2024-07-26 44067/week @ 2024-08-02 51055/week @ 2024-08-09 40423/week @ 2024-08-16

194,721 每月下载量
用于 1,481 个 crate (8 直接)

MIT/Apache

1.5MB
42K SLoC

android-activity

ci crates.io Docs MSRV

概述

android-activity 提供了一个构建 Android 上的原生 Rust 应用程序的 "粘合层",支持多个 Activity 基类。它与 C/C++ 应用程序的 android_native_app_glue.c 类似,是 ndk-glue crate 的替代品。

android-activity 通过 Android Activity 类的 onCreate 方法将您的 crate 作为 cdylib 库加载;在 Java 主线程之外运行 android_main() 函数,并在 Java 和您的本地线程之间传递事件(如生命周期事件和输入事件)。

目前它支持 NativeActivityGameActivity(来自 Android 游戏开发套件),并且还支持一个面向 Rust 应用程序需求的第一方 RustActivity 基类。

示例

Cargo.toml

[dependencies]
log = "0.4"
android_logger = "0.13"
android-activity = { version = "0.5", features = [ "native-activity" ] }

[lib]
crate_type = ["cdylib"]

注意:您需要指定 "native-activity" 功能或 "game-activity" 功能以标识您的应用程序基于哪个 Activity 基类

lib.rs

use android_activity::{AndroidApp, InputStatus, MainEvent, PollEvent};

#[no_mangle]
fn android_main(app: AndroidApp) {
    android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Info));

    loop {
        app.poll_events(Some(std::time::Duration::from_millis(500)) /* timeout */, |event| {
            match event {
                PollEvent::Wake => { log::info!("Early wake up"); },
                PollEvent::Timeout => { log::info!("Hello, World!"); },
                PollEvent::Main(main_event) => {
                    log::info!("Main event: {:?}", main_event);
                    match main_event {
                        MainEvent::Destroy => { return; }
                        _ => {}
                    }
                },
                _ => {}
            }

            app.input_events(|event| {
                log::info!("Input Event: {event:?}");
                InputStatus::Unhandled
            });
        });
    }
}
rustup target add aarch64-linux-android
cargo install cargo-apk
cargo apk run
adb logcat example:V *:S

完整示例

请参阅 这个示例集合(基于 GameActivityNativeActivity)。

每个示例都是一个独立的项目,也可以用作启动新项目的方便模板。

基于中间件框架(如Winit和Egui)的示例旨在演示如何编写可在Android和其他系统上运行的便携式代码。

我应该使用NativeActivity还是GameActivity?

要了解更多关于随Android提供的NativeActivity类的信息,请参阅这里

要了解更多关于Android Game Developer's Kit中的GameActivity类以及与NativeActivity的比较,请参阅这里

一般来说,如果您不确定,由于可能不需要编译/链接任何Java或Kotlin代码,NativeActivity可能是一个更好的起点。

预计GameActivity的后端将随着时间的推移获得更复杂的输入处理功能(例如,支持通过屏幕键盘或游戏控制器进行输入),并且只有GameActivity是基于您可能希望在某些情况下帮助实现设备兼容性的AppCompatActivity子类。

即使您最初出于方便使用NativeActivity,大多数中等复杂的应用程序最终可能需要定义自己的Activity子类(无论是从NativeActivityGameActivity继承),这将需要编译至少一小段Java或Kotlin代码。这通常是由于Android的设计导致的,它通过Activity类引导大量事件,这些事件只能通过重载一些相关Activity方法来处理。

从ndk-glue切换到android-activity

基于Winit的应用程序

首先;如果您有一个基于Winit的应用程序,并且明确依赖于ndk-glue,则您的应用程序需要在Winit 0.28版本中删除对ndk-glue的依赖,因为Winit 0.28版本将基于android-activity(由于glue crates的性质,它们不能与替代glue crates兼容)。

基于Winit的应用程序可以遵循Android README中的指导来获取如何切换的建议。大多数基于Winit的应用程序应努力删除对特定glue crate的任何明确依赖(因此不是直接依赖于ndk-glueandroid-activity,而是依赖于Winit来拉取正确的glue crate)。然后,主要实际变化将是添加一个#[no_mangle]android_main(app: AndroidApp)入口点。

有关更多详细信息,请参阅Android README,并请参阅这里的基于Winit的示例

中间件crate(即不是应用程序)

如果您有一个被认为是中间件库的crate(例如,使用JNI来支持对蓝牙或Android的相机API的访问),则该crate几乎肯定应该删除对特定glue crate的任何依赖,因为这会强制实施严格的兼容性约束,这意味着crate只能由使用该特定glue crate版本的应用程序使用。

中间件库可以改用 ndk-context 包作为使用 JNI 而不必对应用程序的整体架构做出任何假设的手段。这样,中间件包就可以与替代粘合包(包括 ndk-glueandroid-activity)一起工作,以及与嵌入式用例(即可能仅嵌入由 Rust 编写的动态库以实现某些原生函数的非原生应用程序)一起工作。

其他非 Winit 基础的应用程序

将简单独立应用程序从 ndk-glue 切换到 android-activity(仍然基于 NativeActivity)的步骤应该是

  1. 从你的 Cargo.toml 中移除 ndk-glue
  2. 添加对 android-activity 的依赖,例如 android-activity = { version="0.5", features = [ "native-activity" ] }
  3. 可选地添加对 android_logger = "0.13.0"
  4. 更新 main 入口点,如下所示
use android_activity::AndroidApp;

#[no_mangle]
fn android_main(app: AndroidApp) {
    android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Info));
}

有关如何轮询事件的更多详细信息,请参阅此最小化的 NativeActivity 主循环

由于需要传递 AndroidApp 参数,该参数与传统 main() 函数不兼容,因此没有 #[ndk_glue::main] 替代方案。具有 Android 特定入口点还可以提供初始化 Android 日志和处理其他 Android 特定细节(如根据 app 参数构建事件循环)的位置。

设计摘要 / android-activity 的动机

在着手开发 android-activity 之前,发现可用于在 Android 上构建独立 Rust 应用程序的现有粘合包存在一些技术限制,这些限制是该包旨在解决的。

  1. 支持替代 Activity 类:以前的粘合包基于 NativeActivity,它们的 API 不支持替代方案。特别是,对与 GameActivity 类及其 GameTextInput 库结合使用的兴趣,这可以方便地支持屏幕键盘。这也允许基于标准的 AppCompatActivity 基础类构建应用程序,这是使用 NativeActivity 所不可能的。最后,还感兴趣于为支持第一方 RustActivity 开辟道路,该活动可以根据 Android 上 Rust 应用程序的需求进行最佳调整。
  2. 封装 IPC 和原生线程与 JVM 线程之间的同步:例如,使用 ndk-glue,应用程序本身需要通过遵循锁定约定来避免原生线程和 Java 线程之间的竞态条件,并且对于也需要同步的其他请求(如状态保存)支持尚不明确。
  3. 避免静态全局状态:考虑到支持具有多个原生活动的应用程序的可能性,人们对一个不依赖于全局静态来跟踪顶级状态的 API 感兴趣。不是有全局状态获取器,而是 android-activity 将一个显式的 app: AndroidApp 参数传递给封装单个 Activity 状态的入口点。

MSRV

我们的目标是(至少)支持过去三个月内发布的 Rust 的稳定版本。Rust 的发布周期为 6 周,这意味着我们将支持过去三个稳定版本。例如,当 Rust 1.69 发布时,我们将限制我们的 rust-version 为 1.67。

我们只会在我们依赖新功能或依赖项提高了其 MSRV 时提升 rust-version,并且不会过于贪婪。换句话说,我们只会将 MSRV 设置为所需的最低版本。

MSRV 更新不被视为固有的 semver 破坏(除非在公共 API 中公开了新功能),因此 rust-version 的更改可能在补丁版本中发生。

依赖项

~5–16MB
~202K SLoC