egui Android 开发入门:从环境搭建到 hello_android 示例的完整构建运行指南
【免费下载链接】eguiegui: an easy-to-use immediate mode GUI in Rust that runs on both web and native项目地址: https://gitcode.com/GitHub_Trending/eg/egui
导读
本文以 egui 仓库中的官方 hello_android 示例 为骨架,完整讲解如何把基于 eframe 的 Rust 即时模式 GUI 应用编译成 Android APK 并在真机/模拟器上运行。你将掌握 Android 交叉编译工具链的搭建、环境变量的配置、cargo-apk的安装与使用,以及桌面端与 Android 端共用同一套代码的工程结构,读完即可把现有 egui 应用移植到 Android 平台。
示例概览:一个应用,两个平台
hello_android是 eframe 官方提供的 Android 最小可运行示例。它的独特之处在于:同一个 crate 同时面向桌面(native)与 Android(移动端)两个目标平台编译,而应用逻辑本身完全复用。
从 examples/hello_android/Cargo.toml 可以看到其关键设计:
[lib] # cdylib is required for Android, lib is required for desktop crate-type = ["cdylib", "lib"]cdylib:Android 需要以动态库(.so)形式打包进 APK,由系统 Java 层通过 JNI 加载;lib:桌面端(Linux/macOS/Windows)以常规 Rust 库形式链接进可执行文件。
两者共存,保证了cargo apk run(Android)与cargo run(桌面)都能直接使用同一个工程。
平台相关的两个入口
示例的源码分为两个文件:
- examples/hello_android/src/main.rs:桌面入口,调用
eframe::run_native创建原生窗口; - examples/hello_android/src/lib.rs:同时包含桌面入口与 Android 入口,其中 Android 入口通过
#[cfg(target_os = "android")]条件编译隔离:
#[cfg(target_os = "android")] #[unsafe(no_mangle)] fn android_main(app: winit::platform::android::activity::AndroidApp) { // Log to android output android_logger::init_once( android_logger::Config::default().with_max_level(log::LevelFilter::Info), ); let options = eframe::NativeOptions { android_app: Some(app), ..Default::default() }; eframe::run_native( "My egui App", options, Box::new(|cc| Ok(Box::new(MyApp::new(cc)))), ) .unwrap() }要点解析:
android_main是android-activity(通过 winit 暴露)约定的入口函数,必须#[unsafe(no_mangle)]导出符号,供 Android 原生层调用;- 通过
android_logger把 Rust 的log输出桥接到 Androidlogcat,级别设为Info,便于调试; NativeOptions中的android_app字段(见 crates/eframe/src/epi.rs 中#[cfg(target_os = "android")]的声明)是 Android 平台下eframe::run_native必需的运行时上下文,缺失会直接报错。
Android 特性开关
hello_android在依赖声明中启用了两个关键特性(见 examples/hello_android/Cargo.toml):
eframe = { workspace = true, default-features = false, features = [ "default_fonts", "glow", "android-native-activity", ] }glow:选用 OpenGL ES 渲染后端(Android 移动 GPU 兼容性最好);android-native-activity:选择android-activity的native-activity后端。
这两个特性从eframe一路透传到egui-winit与winit:
- crates/eframe/Cargo.toml 中
android-native-activity = ["egui-winit/android-native-activity"]; - crates/egui-winit/Cargo.toml 中
android-native-activity = ["winit/android-native-activity"]。
eframe还提供另一个备选后端android-game-activity(对应egui-winit/android-game-activity)。从 crates/eframe/src/lib.rs 的编译期约束可以看出,若同时开启accesskit辅助功能特性,则必须使用android-game-activity后端,否则编译会直接失败(compile_error!)。因此选型时需要注意:需要无障碍支持的应用应选择android-game-activity。
桌面端前置条件:交叉编译工具链
Android 应用需要 Rust 交叉编译目标与 Android SDK/NDK。以下按官方 README 的顺序展开,并补充必要说明。
1. 添加 Rust Android 编译目标
rustup target add armv7-linux-androideabi aarch64-linux-androidaarch64-linux-android:64 位 ARM 目标,覆盖当今绝大多数主流手机(arm64-v8a);armv7-linux-androideabi:32 位 ARM 目标,覆盖较老的 32 位设备(armeabi-v7a)。
这两个目标与 examples/hello_android/Cargo.toml 中[package.metadata.android]声明的build_targets一一对应:
[package.metadata.android] build_targets = ["armv7-linux-androideabi", "aarch64-linux-android"]若机器上只有 64 位设备,也可以只添加aarch64-linux-android并相应调整build_targets,以缩短构建时间。
2. 设置环境变量(每次构建前必须执行)
export ANDROID_HOME="$HOME/tools/android" export ANDROID_NDK_ROOT="${ANDROID_HOME}/ndk/29.0.14206865" export PATH="$PATH:${ANDROID_NDK_ROOT}:${ANDROID_HOME}/build-tools/${BUILDTOOLS_VERSION}:${ANDROID_HOME}/cmdline-tools/bin"官方特别强调:这些变量是cargo apk每次运行都需要的。其中:
ANDROID_HOME:Android SDK 根目录,本例为$HOME/tools/android;ANDROID_NDK_ROOT:指向具体版本的 NDK 目录(ndk/29.0.14206865);PATH:追加 NDK、SDK build-tools(版本号用${BUILDTOOLS_VERSION}占位,与第 4 步安装的版本保持一致)以及 cmdline-tools 的bin目录。
建议把这些export写入~/.bashrc或~/.profile,避免每次开新终端重复配置。若你安装了 Android Studio,其 SDK 默认位于$HOME/Android/Sdk,将ANDROID_HOME指向该目录同样可行,关键是 SDK、NDK、build-tools 版本要与下面的安装步骤一致。
3. 安装 Android 命令行工具
mkdir -p "${ANDROID_HOME}/cmdline-tools" curl -sLo /tmp/clt.zip https://dl.google.com/android/repository/commandlinetools-linux-14742923_latest.zip unzip -d "${ANDROID_HOME}" /tmp/clt.zip将 Google 官方的 commandline-tools 压缩包下载并解压到ANDROID_HOME,随后即可使用其中的sdkmanager安装 SDK 组件。cmdline-tools的bin目录已在第 2 步加入PATH。
4. 安装 SDK 组件
sdkmanager --sdk_root="${ANDROID_HOME}" --install "build-tools;36.0.0" "ndk;29.0.14206865" "platforms;android-35"安装内容:
build-tools;36.0.0:打包 APK 所需的构建工具(aapt2、zipalign 等),对应PATH中的${BUILDTOOLS_VERSION};ndk;29.0.14206865:NDK 29,提供交叉编译所需的 C 工具链与头文件,目录结构为$ANDROID_HOME/ndk/29.0.14206865,与ANDROID_NDK_ROOT一致;platforms;android-35:Android 35(Android 15)平台库。
注意:官方在文档中特别提示“You may need to change SDK versions”——以上版本号是编写该示例时的推荐组合,请根据你本机可用的 SDK/NDK 版本与目标设备系统版本灵活调整,并保持
ANDROID_NDK_ROOT、PATH中的 build-tools 版本、sdkmanager安装版本三者一致。
SDK 版本信息在工程中也有一处对应:Cargo.toml的[package.metadata.android.sdk]声明了min_sdk_version = 23(最低支持 Android 6.0)、target_sdk_version = 35(目标 Android 15)。
5. 安装 cargo-apk 构建工具
cargo install --git https://github.com/parasyte/cargo-apk.git --rev 282639508eeed7d73f2e1eaeea042da2716436d5 cargo-apkcargo-apk是cargo的子命令扩展,负责把 Rust crate 打包为可安装的 APK。官方 README 特别注明:上游存在一个 bug(对应 issue 链接见 examples/hello_android/README.md),因此必须安装指定--rev(提交哈希282639508eeed7d73f2e1eaeea042da2716436d5)的修复版本,直接cargo install cargo-apk装到最新版可能无法正常工作。
安装完成后可通过cargo apk --help验证是否成功注册为 cargo 子命令。
构建与运行:一行命令双平台
官方 README 给出的两条命令:
# Android:交叉编译并打包安装到连接的设备/模拟器 cargo apk run -p hello_android --lib # 桌面:直接作为原生应用运行 cargo run -p hello_androidcargo apk run -p hello_android --lib:-p指定包名;--lib告诉 cargo-apk 打包库目标(即上文的cdylib),编译产物.so会被封装进 APK,安装后由 Android 系统拉起。运行前提是已有设备通过 adb 连接(adb devices可确认),首次运行也可加上--release获得优化后的性能;cargo run -p hello_android:走的是 examples/hello_android/src/main.rs 的桌面入口,与普通 eframe 应用无异,可用于在开发 Android 功能前快速验证 UI 逻辑,无需等待交叉编译。
应用代码解读
MyApp是桌面与 Android 共享的应用主体(examples/hello_android/src/lib.rs):
pub struct MyApp { demo: egui_demo_lib::DemoWindows, } impl MyApp { pub fn new(cc: &CreationContext) -> Self { egui_extras::install_image_loaders(&cc.egui_ctx); Self { demo: egui_demo_lib::DemoWindows::default(), } } } impl eframe::App for MyApp { fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { // Reserve some space at the top so the demo ui isn't hidden behind the android status bar egui::Panel::top("status_bar_space").show(ui, |ui| { ui.set_height(32.0); }); egui::CentralPanel::default().show(ui, |ui| { self.demo.ui(ui); }); } }三个值得注意的细节:
- 复用官方演示集:
egui_demo_lib::DemoWindows是仓库自带的演示窗口集合,让示例开箱即用地展示大量控件; - 图片加载:
egui_extras::install_image_loaders注册图片加载器(依赖中启用了egui_extras的image特性),为演示里的图片控件提供支持; - 状态栏避让:
Panel::top("status_bar_space")在顶部预留 32 像素高度,避免 UI 被 Android 系统状态栏遮挡。源码注释同时指出这是一个临时 hack,待 winit 在 Android 上实现 safe_area 后应替换为正式方案——如果你要移植自己的应用,这个处理思路可以直接复用。
Android 渲染生命周期(可选:深入底层)
对想了解底层机制的读者,eframe 的 glow 集成在 Android 上有一个特殊生命周期处理,见 crates/eframe/src/native/glow_integration.rs:
- Android 应用会收到
Resumed/Suspended事件,与桌面平台不同(桌面只在启动时进入一次); - 在 Android 上,
Suspended时会销毁 GL surface 与窗口并让 OpenGL 上下文失活,Resumed时则重新创建(glow 集成注释明确写道:Suspended: on android, we drop window + surface); - 这也是为什么示例在桌面与 Android 上都要走
eframe::run_native——eframe 已经帮你封装好了这套平台差异。
入口侧,eframe在 crates/eframe/src/native/run.rs 中通过winit::platform::android::EventLoopBuilderExtAndroid的with_android_app把NativeOptions.android_app注入事件循环,从而衔接android_main与 winit 事件循环。
常见问题与排查建议
cargo apk报找不到 SDK/NDK:确认ANDROID_HOME、ANDROID_NDK_ROOT已 export,且ANDROID_NDK_ROOT指向真实存在的 NDK 版本目录;版本不匹配时参考官方提示调整 SDK 版本。- 构建目标缺失:
rustup target add的目标必须与build_targets一致,缺哪个补哪个。 - UI 被状态栏遮挡:参考示例的顶部
Panel预留方案,或等待 winit safe_area 支持落地后改用正式 API。 android_main未找到 / 链接失败:确认eframe启用了android-native-activity(或android-game-activity)特性,且入口函数保持了#[unsafe(no_mangle)]符号导出。- 启用
accesskit后编译失败:按 crates/eframe/src/lib.rs 中的compile_error!提示,将后端切换到android-game-activity。
相关资源
- hello_android 示例文档:本文的原始依据;
- hello_android 工程配置:特性开关、
build_targets、SDK 版本声明; - hello_android 共享应用代码:双平台入口与
MyApp实现; - eframe Android 入口与事件循环集成:
android_app注入 winit 事件循环的实现; - eframe glow 集成中的 Android 生命周期处理:
Resumed/Suspended时 surface 与窗口的创建销毁逻辑; - eframe NativeOptions 的 android_app 字段:Android 平台特有配置项;
- eframe 特性声明:
android-native-activity/android-game-activity两个后端的特性透传。
【免费下载链接】eguiegui: an easy-to-use immediate mode GUI in Rust that runs on both web and native项目地址: https://gitcode.com/GitHub_Trending/eg/egui
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考