ITADN
rust-mobile/android-activity
rust-mobile/android-activity · 文件 下载 ZIP
文件最后提交记录最后更新时间
README.md
以下内容由 AI 翻译,如有问题请点此提交 issue 反馈

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 Game Development Kit),并且也有兴趣支持一个第一方 RustActivity 基类,该基类可能更好地满足 Rust 应用程序的需求。

快速开始

Cargo.toml:

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

[lib]
crate-type = ["cdylib"]

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

lib.rs:

use std::sync::OnceLock;
use android_activity::{AndroidApp, InputStatus, MainEvent, PollEvent};

// - Called on a dedicated Activity main loop thread, spawned after `android_on_create` returns
// - May be called multiple times if your Activity is destroyed and recreated.
// - Note: this symbol has a "Rust" ABI (default), not "C" ABI.
#[unsafe(no_mangle)]
fn android_main(app: AndroidApp) {

    // `android_main` is tied to your `Activity` lifecycle, not your application lifecycle
    // and so it may be called multiple times if your Activity is destroyed and recreated.
    //
    // Use a `OnceLock` or similar to ensure that you don't attempt to initialize global state
    // multiple times.
    static APP_ONCE: OnceLock<()> = OnceLock::new();
    APP_ONCE.get_or_init(|| {
        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 {
                        // Once you receive a `Destroy` event, your `AndroidApp` will no longer
                        // be associated with any `Activity` and it's methods will effectively be no-ops.
                        //
                        // You should return from `android_main` and if your `Activity` gets recreated then
                        // a new `AndroidApp` will be passed to a new invocation of `android_main`.
                        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

注意:虽然 cargo apk 对于这个快速入门示例很方便,但通常建议您在 Android 应用程序中使用更标准的基于 Gradle 的构建系统,并使用类似 cargo ndk 的工具将您的 Rust 代码构建为 cdylib,然后通过 Gradle 进行打包。

完整示例

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

每个示例都是一个独立的 Android Studio 项目,可以作为启动新项目的便捷模板。

对于基于中间件框架(Winit 或 Egui)的示例,它们还旨在展示如何编写可在 Android 和其他系统上运行的可移植代码。

可选的 android_on_create 入口点

android-activity 还支持一个可选的 android_on_create 入口点, 该入口点会在 android_main() 被调用之前,从 Activity.onCreate() 回调中被调用。

android_on_createandroid_main 线程生成之前,从 Java 主线程 / UI 线程中被调用。

考虑到许多 Android SDK API(例如 android.view.View)必须从主线程访问,android_on_create 可以是一个执行需要在 Java 主线程上完成的设置工作的好地方。

例如:

use std::sync::OnceLock;
use jni::{JavaVM, objects::JObject};

#[unsafe(no_mangle)]
fn android_on_create(state: &android_activity::OnCreateState) {

    // `android_on_create` is tied to your `Activity` lifecycle, not your application lifecycle
    // and so it may be called multiple times if your activity is destroyed and recreated.
    //
    // Use a `OnceLock` or similar to ensure that you don't attempt to initialize global state
    // multiple times.
    static APP_ONCE: OnceLock<()> = OnceLock::new();
    APP_ONCE.get_or_init(|| {
        // Initialize logging...
    });
    let vm = unsafe { JavaVM::from_raw(state.vm_as_ptr().cast()) };
    let activity = state.activity_as_ptr() as jni::sys::jobject;
    // Do some other setup work on the Java main thread before `android_main` starts running
}

(注:还有一种 AndroidApp::run_on_java_main_thread() 方法, 它为某些用例提供了在 Java 主线程上运行代码的另一种方式)

我应该使用 NativeActivity 还是 GameActivity?

要了解随 Android 一起发布的 NativeActivity 类的更多信息,请参见 此处

要了解作为 Android Game Developer's Kit 一部分的 GameActivity 类的更多信息,以及查看与 NativeActivity 的对比,请参见 此处

一般来说,如果不确定,NativeActivity 可能更方便作为起点, 因为您可能不需要编译/链接任何 Java 或 Kotlin 代码,但 GameActivity 可能是更合适的长期选择,因为它基于 AppCompatActivity 并且内置了对输入方法(例如 屏幕键盘)的支持。

NativeActivity

  • 适用于:简单应用、快速原型设计、有限的文本输入支持
  • 设置:只需添加功能标志
  • 局限性:没有内置的输入方法支持(只能接收来自软键盘的物理按键 事件,通常仅允许基本的 ASCII 输入)

NativeActivity 类的独特优势在于它是作为 Android 操作系统的一部分发布的,因此您可以在无需编译或链接任何 Java 或 Kotlin 代码的情况下使用它。

NativeActivity 在技术上是构建纯 Rust 原生 Android 应用程序的唯一方式,完全不需要任何 Java 或 Kotlin 代码。

NativeActivity 最显著的局限性是它没有 内置的输入方法(例如屏幕键盘)支持,因此对于需要支持文本输入的应用程序, 它通常不是一个好的选择。

由于某些软键盘会为基本 ASCII 输入发送物理按键事件, 因此 NativeActivity 可以启用基本文本输入以用于原型设计,但这 不太可能足以满足生产级应用的需求。

对于高级用例,可以结合 NativeActivity 提供自定义的 InputConnection 支持,但目前 android-activity 并未开箱即用地提供此功能。

GameActivity

  • 适用于:需要文本输入、现代 AndroidX 功能的应用
  • 配置要求:
    • 添加 gradle 依赖:androidx.games:games-activity:4.4.0
    • 在 Cargo.toml 中启用 game-activity 功能
    • 重要:请勿启用 prefab 支持 详情见此处
  • 提供:IME 支持、AppCompatActivity 功能

GameActivity 通过 GameTextInput 库内置了对输入方法的支持,因此是那些需要支持文本 输入的应用程序的更好选择。

GameActivity 允许您更新与软键盘相关的 ImeOptions 和操作, 以及接收 IME span 更新以跟踪用户的文本输入状态。

GameActivity 基于 AppCompatActivity 类,这是一个标准的 Jetpack / AndroidX 类,提供了许多内置功能,以帮助 在不同 Android 版本和设备之间实现兼容性。

游戏活动库版本

android-activity currently supports the GameActivity 4.4.0 Jetpack library and is backwards compatible with the previous 4.0.0 stable release. We can't guarantee that the next 4.x stable release will be compatible, but it's fairly likely that it will be.

您的 Android 包应依赖于来自 Google Maven 仓库的 androidx.games:games-activity:4.4.0

有关如何将 GameActivity 库添加到您的项目的更多详细信息,请参阅上游 GameActivity 入门 指南

不要编译和链接上游 GameActivity 'prefab'(C++ 胶水)层

重要:请勿遵循上游说明来为 GameActivity 启用原生 prefab 支持,因为这会将上游 C++ 胶水 层作为构建的一部分进行编译和链接。上游胶水层与 android-activity 不直接兼容,后者提供了自己的原生胶水层,并与 Rust 集成。

也就是说,您无需通过您的 build.gradle 文件启用 prefabs:

buildFeatures {
  prefab true
}

并且不要在你的 CMakeLists.txt 文件中添加如下代码片段:

find_package(game-activity REQUIRED CONFIG)
target_link_libraries(${PROJECT_NAME} PUBLIC log android
game-activity::game-activity_static)

计划实现 Activity 子类

仅通过 Rust / JNI 代码无法对 Activity 进行子类化。

请记住,Android 的设计通过 Activity 类分发许多事件, 这些事件只能通过重载某些相关的 Activity 方法来处理,因此 如果你想要处理这些事件,则需要实现一个 Activity 子类并重载相关的方法。

大多数中等复杂度的应用程序最终都需要定义自己的 Activity 子类(无论是子类化 NativeActivity 还是 GameActivity), 这将需要编译至少少量的 Java 或 Kotlin 代码。

归根结底,Android 的应用编程模型从根本上 基于运行 Java/Kotlin 代码的 Java 虚拟机,这些代码可以选择性地调用 本地代码(而不是反过来)。

设计摘要 / android-activity 背后的动机

在着手开发 android-activity 之前,发现用于 在 Android 上构建独立 Rust 应用程序的现有胶水 crate 存在许多 技术限制,而本 crate 旨在解决这些问题:

  1. Support alternative Activity classes: Prior glue crates were based on NativeActivity and their API precluded supporting alternatives. In particular there was an interest in the GameActivity class in conjunction with its GameTextInput library that can facilitate onscreen keyboard support. This also allows building applications based on the standard AppCompatActivity base class which isn't possible with NativeActivity. Finally there was interest in paving the way towards supporting a first-party RustActivity that could be best tailored towards the needs of Rust applications on Android.
  2. Encapsulate IPC + synchronization between the native thread and the JVM thread: For example with ndk-glue the application itself needs to avoid race conditions between the native and Java thread by following a locking convention) and it wasn't clear how this would extend to support other requests (like state saving) that also require synchronization.
  3. Avoid static global state: Keeping in mind the possibility of supporting applications with multiple native activities there was interest in having an API that didn't rely on global statics to track top-level state. Instead of having global getters for state then android-activity passes an explicit app: AndroidApp argument to the entry point that encapsulates the state connected with a single Activity.

使用 android-activity 编写应用程序是可能的,该应用程序可以 优雅地处理 Activity 的重复 创建 -> 运行 -> 销毁 周期, 这得益于其对全局状态的避免。理论上,你甚至可以同时运行 多个 Activity 实例(但由于 NativeActivityGameActivity 是为全屏游戏设计的,这些游戏只需要单个 Activity,因此这不是一个常见的用例)。

MSRV

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

我们只会在依赖新特性或某个依赖项提高了其 MSRV 时,才会提升 rust-version,并且我们不会贪心。 换句话说,我们只会将 MSRV 设置为所需的_最低_版本。

MSRV 的更新不被视为本质上破坏 semver(除非在公共 API 中暴露了新特性),因此 rust-version 变更可能 发生在补丁版本中。

Game Activity Library 版本策略

android-activity 的任何单个版本都将支持特定版本的 Game Activity Jetpack / AndroidX 库(如上所述)。

Game Activity 库所需的版本不构成我们 Rust semver 契约的一部分,因为它不影响 android-activity 的公共 Rust API。

这意味着 android-activity 的新补丁版本可能会更新 GameActivity 所需的版本,这可能要求用户更新他们 打包应用程序的方式。

这与 MSRV 更新的工作方式类似,新的工具链要求可能 会影响您构建应用程序的方式,但该变更与 crate 的公共 API 是正交的。