diff --git a/Cargo.toml b/Cargo.toml index 26d22be..8755f2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,15 @@ +[workspace.package] +version = "1.0.0" +authors = [ + "SmileSky ", + "The Rust Windowing contributors", +] +edition = "2021" +license = "MIT OR Apache-2.0" +keywords = ["android", "ndk", "apk"] +repository = "https://github.com/mzdk100/cargo-apk2.git" +rust-version = "1.82.0" + [workspace.dependencies] dunce = "1.0.5" serde = "1.0.214" diff --git a/cargo-apk2/CHANGELOG.md b/cargo-apk2/CHANGELOG.md index 35efdc8..87fa275 100644 --- a/cargo-apk2/CHANGELOG.md +++ b/cargo-apk2/CHANGELOG.md @@ -1,5 +1,11 @@ # 未发布 +# 1.0.0 (2024-11-14) + +- 将MSRV提升到1.82.0,以反映依赖项更新。 +- 支持aapt2工具对apk资源的编译和处理。 +- 从cargo-apk更名为cargo-apk2,继续维护和支持。 + # 0.10.0 (2023-11-30) - 将MSRV提升到1.70,以反映依赖项更新。 diff --git a/cargo-apk2/Cargo.toml b/cargo-apk2/Cargo.toml index 7abb647..28ecc1c 100644 --- a/cargo-apk2/Cargo.toml +++ b/cargo-apk2/Cargo.toml @@ -1,16 +1,13 @@ [package] name = "cargo-apk2" -version = "0.10.0" -authors = [ - "SmileSky ", - "The Rust Windowing contributors" -] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true description = "Rust轻松构建安卓应用的Cargo扩展" -license = "MIT OR Apache-2.0" -keywords = ["android", "ndk", "apk"] -repository = "https://github.com/mzdk100/cargo-apk2.git" -rust-version = "1.82.0" +license.workspace = true +keywords.workspace = true +repository.workspace = true +rust-version.workspace = true [dependencies] anyhow = "1.0.93" @@ -24,7 +21,7 @@ toml = "0.8.19" [dependencies.ndk-build2] path = "../ndk-build2" -version = "0.10.0" +version = "1.0.0" [dependencies.clap] version = "4.5.20" diff --git a/cargo-apk2/src/apk.rs b/cargo-apk2/src/apk.rs index 94d13ff..d400df6 100644 --- a/cargo-apk2/src/apk.rs +++ b/cargo-apk2/src/apk.rs @@ -12,7 +12,7 @@ use ndk_build2::{ ndk::{Key, Ndk}, target::Target, }; -use std::path::PathBuf; +use std::{env::var_os, path::PathBuf}; pub struct ApkBuilder<'a> { cmd: &'a Subcommand, @@ -77,7 +77,7 @@ impl<'a> ApkBuilder<'a> { }; let version_code = VersionCode::from_semver(&package_version)?.to_code(1); - // Set default Android manifest values + // 设置默认 Android 清单值 if manifest .android_manifest .version_name @@ -123,8 +123,7 @@ impl<'a> ApkBuilder<'a> { }); } - // Export the sole Rust activity on Android S and up, if the user didn't explicitly do so. - // Without this, apps won't start on S+. + // 如果用户未明确执行此操作,则在 Android S 及更高版本上导出 Activity。如果没有此操作,应用将无法在 S+ 上启动。 // https://developer.android.com/about/versions/12/behavior-changes-12#exported if target_sdk_version >= 31 { activity.exported.get_or_insert(true); @@ -162,7 +161,7 @@ impl<'a> ApkBuilder<'a> { } pub fn build(&self, artifact: &Artifact) -> Result { - // Set artifact specific manifest default values. + // 设置工件特定的清单默认值。 let mut manifest = self.manifest.android_manifest.clone(); if manifest.package.is_empty() { @@ -207,11 +206,13 @@ impl<'a> ApkBuilder<'a> { .apk_name .clone() .unwrap_or_else(|| artifact.name.to_string()); + let use_aapt2 = self.manifest.use_aapt2.unwrap_or(true); let config = ApkConfig { ndk: self.ndk.clone(), build_dir: self.build_dir.join(artifact.build_dir()), apk_name, + use_aapt2, assets, resources, manifest, @@ -270,7 +271,7 @@ impl<'a> ApkBuilder<'a> { ); let password_env = format!("{}_PASSWORD", keystore_env); - let path = std::env::var_os(&keystore_env).map(PathBuf::from); + let path = var_os(&keystore_env).map(PathBuf::from); let password = std::env::var(&password_env).ok(); let signing_key = match (path, password) { diff --git a/cargo-apk2/src/manifest.rs b/cargo-apk2/src/manifest.rs index c76cd6e..785eda0 100644 --- a/cargo-apk2/src/manifest.rs +++ b/cargo-apk2/src/manifest.rs @@ -13,15 +13,18 @@ pub enum Inheritable { Inherited { workspace: bool }, } +#[derive(Debug)] pub(crate) struct Manifest { pub(crate) version: Inheritable, pub(crate) apk_name: Option, + /// 使用aapt2编译和处理资源(默认开启) + pub use_aapt2: Option, pub(crate) android_manifest: AndroidManifest, pub(crate) build_targets: Vec, pub(crate) assets: Option, pub(crate) resources: Option, pub(crate) runtime_libs: Option, - /// Maps profiles to keystores + /// 将配置文件映射到密钥库 pub(crate) signing: HashMap, pub(crate) reverse_port_forward: HashMap, pub(crate) strip: StripConfig, @@ -43,6 +46,7 @@ impl Manifest { Ok(Self { version: package.version, apk_name: metadata.apk_name, + use_aapt2: metadata.use_aapt2, android_manifest: metadata.android_manifest, build_targets: metadata.build_targets, assets: metadata.assets, @@ -94,6 +98,8 @@ pub(crate) struct PackageMetadata { #[derive(Clone, Debug, Default, Deserialize)] struct AndroidMetadata { apk_name: Option, + /// 使用aapt2编译和处理资源(默认开启) + use_aapt2: Option, #[serde(flatten)] android_manifest: AndroidManifest, #[serde(default)] diff --git a/ndk-build2/CHANGELOG.md b/ndk-build2/CHANGELOG.md index 251be58..6c47434 100644 --- a/ndk-build2/CHANGELOG.md +++ b/ndk-build2/CHANGELOG.md @@ -1,5 +1,9 @@ # 未发布 +# 1.0.0 (2024-11-14) + +- 支持aapt2工具对apk资源的编译和处理。 + # 0.10.0 (2023-11-30) - 在清单的 `Application` 元素中添加 `android:extractNativeLibs`、`android:usesCleartextTraffic` 属性,并在 `Activity` 元素中添加 `android:alwaysRetainTaskState`。([#15](https://github.com/rust-mobile/cargo-apk/pull/15)) diff --git a/ndk-build2/Cargo.toml b/ndk-build2/Cargo.toml index 065c80b..80e460a 100644 --- a/ndk-build2/Cargo.toml +++ b/ndk-build2/Cargo.toml @@ -1,16 +1,13 @@ [package] name = "ndk-build2" -version = "0.10.0" -authors = [ - "SmileSky ", - "The Rust Windowing contributors", -] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true description = "用于构建 Android 二进制文件的实用程序" -license = "MIT OR Apache-2.0" -keywords = ["android", "ndk", "apk"] -repository = "https://github.com/mzdk100/cargo-apk2.git" -rust-version = "1.82.0" +license.workspace = true +keywords.workspace = true +repository.workspace = true +rust-version.workspace = true [dependencies] android-build = "0.1.0" diff --git a/ndk-build2/src/apk.rs b/ndk-build2/src/apk.rs index fcebd43..8ddd100 100644 --- a/ndk-build2/src/apk.rs +++ b/ndk-build2/src/apk.rs @@ -4,43 +4,39 @@ use crate::{ ndk::{Key, Ndk}, target::Target, }; +use serde::Serialize; use std::{ collections::{HashMap, HashSet}, ffi::OsStr, - fs::{copy, create_dir_all, read_dir}, + fs::{copy, create_dir_all, read_dir, remove_file, rename}, path::{Path, PathBuf}, process::Command, }; -/// The options for how to treat debug symbols that are present in any `.so` -/// files that are added to the APK. +//noinspection SpellCheckingInspection +/// 如何处理添加到 APK 的任何 `.so` 文件中的调试符号的选项。 /// -/// Using [`strip`](https://doc.rust-lang.org/cargo/reference/profiles.html#strip) -/// or [`split-debuginfo`](https://doc.rust-lang.org/cargo/reference/profiles.html#split-debuginfo) -/// in your cargo manifest(s) may cause debug symbols to not be present in a -/// `.so`, which would cause these options to do nothing. -#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Deserialize)] +/// 在您的货物清单中使用 +/// [`strip`](https://doc.rust-lang.org/cargo/reference/profiles.html#strip) +/// 或 [`split-debuginfo`](https://doc.rust-lang.org/cargo/reference/profiles.html#split-debuginfo) +/// 可能会导致调试符号不存在于 `.so` 中,从而导致这些选项不执行任何操作。 +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, serde::Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub enum StripConfig { - /// Does not treat debug symbols specially + /// 不对调试符号进行特殊处理 + #[default] Default, - /// Removes debug symbols from the library before copying it into the APK + /// 在将库复制到 APK 之前,从库中删除调试符号 Strip, - /// Splits the library into into an ELF (`.so`) and DWARF (`.dwarf`). Only the - /// `.so` is copied into the APK + /// 将库拆分为 ELF(`.so`)和 DWARF(`.dwarf`)。只有 `.so` 会被复制到 APK 中 Split, } -impl Default for StripConfig { - fn default() -> Self { - Self::Default - } -} - pub struct ApkConfig { pub ndk: Ndk, pub build_dir: PathBuf, pub apk_name: String, + pub use_aapt2: bool, pub assets: Option, pub resources: Option, pub manifest: AndroidManifest, @@ -61,8 +57,7 @@ impl ApkConfig { .join(format!("{}-unaligned.apk", self.apk_name)) } - /// Retrieves the path of the APK that will be written when [`UnsignedApk::sign`] - /// is invoked + /// 调用 [`UnsignedApk::sign`] 时将写入的 APK 的路径 #[inline] pub fn apk(&self) -> PathBuf { self.build_dir.join(format!("{}.apk", self.apk_name)) @@ -77,7 +72,30 @@ impl ApkConfig { .sdk .target_sdk_version .unwrap_or_else(|| self.ndk.default_target_platform()); + + if self.use_aapt2 { + let out_dir = self + .build_dir + .join(format!("compiled_{}_resource", self.manifest.package).as_str()); + self.aapt2_compile(&out_dir)?; + self.aapt2_link(target_sdk_version)?; + if !self.manifest.application.debuggable.unwrap_or(false) { + self.aapt2_optimize()?; + } + remove_file(out_dir)?; + } else { + self.aapt_package(target_sdk_version)?; + } + + Ok(UnalignedApk { + config: self, + pending_libs: HashSet::default(), + }) + } + + fn aapt_package(&self, target_sdk_version: u32) -> Result<(), NdkError> { let mut aapt = self.build_tool(bin!("aapt"))?; + println!("Packing apk resources......"); aapt.arg("package") .arg("-f") .arg("-F") @@ -102,11 +120,67 @@ impl ApkConfig { if !aapt.status()?.success() { return Err(NdkError::CmdFailed(aapt)); } + Ok(()) + } - Ok(UnalignedApk { - config: self, - pending_libs: HashSet::default(), - }) + fn aapt2_compile(&self, out_dir: impl AsRef) -> Result<(), NdkError> { + let mut aapt = self.build_tool(bin!("aapt2"))?; + println!("Compiling apk resources..."); + aapt.arg("compile").arg("-o").arg(out_dir); + + if let Some(res) = &self.resources { + aapt.arg("--dir").arg(res); + } + + if !aapt.status()?.success() { + return Err(NdkError::CmdFailed(aapt)); + } + Ok(()) + } + + fn aapt2_link(&self, target_sdk_version: u32) -> Result<(), NdkError> { + let mut aapt = self.build_tool(bin!("aapt2"))?; + println!("Linking apk resources..."); + aapt.arg("link") + .arg("-o") + .arg(self.unaligned_apk()) + .arg("--manifest") + .arg("AndroidManifest.xml") + .arg("-I") + .arg(self.ndk.android_jar(target_sdk_version)?); + + if self.disable_aapt_compression { + aapt.arg("--no-compress").arg("-0").arg(""); + } + + if let Some(assets) = &self.assets { + aapt.arg("-A").arg(assets); + } + + if !aapt.status()?.success() { + return Err(NdkError::CmdFailed(aapt)); + } + Ok(()) + } + + fn aapt2_optimize(&self) -> Result<(), NdkError> { + let mut aapt = self.build_tool(bin!("aapt2"))?; + println!("Optimizing apk resources..."); + let path = self.unaligned_apk(); + let input_path = path.parent().unwrap().join(&self.manifest.package); + rename(&path, &input_path)?; + aapt.arg("optimize") + .arg("-o") + .arg(path) + .arg(&input_path) + .arg("--enable-sparse-encoding"); + + if !aapt.status()?.success() { + remove_file(input_path)?; + return Err(NdkError::CmdFailed(aapt)); + } + remove_file(input_path)?; + Ok(()) } } diff --git a/ndk-build2/src/cargo.rs b/ndk-build2/src/cargo.rs index d3abf34..d1a3948 100644 --- a/ndk-build2/src/cargo.rs +++ b/ndk-build2/src/cargo.rs @@ -1,8 +1,12 @@ -use crate::error::NdkError; -use crate::ndk::Ndk; -use crate::target::Target; -use std::path::Path; -use std::process::Command; +use crate::{ + error::NdkError, + ndk::Ndk, + target::Target +}; +use std::{ + path::Path, + process::Command +}; pub fn cargo_ndk( ndk: &Ndk, diff --git a/ndk-build2/src/manifest.rs b/ndk-build2/src/manifest.rs index 9ba28f9..de0066a 100644 --- a/ndk-build2/src/manifest.rs +++ b/ndk-build2/src/manifest.rs @@ -2,20 +2,30 @@ use crate::error::NdkError; use serde::{Deserialize, Serialize, Serializer}; use std::{fs::File, io::Write, path::Path}; -/// Android [manifest element](https://developer.android.com/guide/topics/manifest/manifest-element), containing an [`Application`] element. +/// Android [manifest 元素](https://developer.android.com/guide/topics/manifest/manifest-element), containing an [`Application`] element. +// quick_xml规定#[serde(rename)]的值如果带有`@`符号表示属性,否则表示tag #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename = "manifest")] pub struct AndroidManifest { - #[serde(rename(serialize = "xmlns:android"))] + #[serde(rename(serialize = "@xmlns:android"))] #[serde(default = "default_namespace")] ns_android: String, - #[serde(default)] + #[serde(default, rename(serialize = "@package"))] pub package: String, - #[serde(rename(serialize = "android:sharedUserId"))] + #[serde( + rename(serialize = "@android:sharedUserId"), + skip_serializing_if = "Option::is_none" + )] pub shared_user_id: Option, - #[serde(rename(serialize = "android:versionCode"))] + #[serde( + rename(serialize = "@android:versionCode"), + skip_serializing_if = "Option::is_none" + )] pub version_code: Option, - #[serde(rename(serialize = "android:versionName"))] + #[serde( + rename(serialize = "@android:versionName"), + skip_serializing_if = "Option::is_none" + )] pub version_name: Option, #[serde(rename(serialize = "uses-sdk"))] @@ -25,11 +35,12 @@ pub struct AndroidManifest { #[serde(rename(serialize = "uses-feature"))] #[serde(default)] pub uses_feature: Vec, + #[serde(rename(serialize = "uses-permission"))] #[serde(default)] pub uses_permission: Vec, - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub queries: Option, #[serde(default)] @@ -63,24 +74,39 @@ impl AndroidManifest { } } -/// Android [application element](https://developer.android.com/guide/topics/manifest/application-element), containing an [`Activity`] element. +/// Android [application 元素](https://developer.android.com/guide/topics/manifest/application-element), containing an [`Activity`] element. #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct Application { - #[serde(rename(serialize = "android:debuggable"))] + #[serde( + rename(serialize = "@android:debuggable"), + skip_serializing_if = "Option::is_none" + )] pub debuggable: Option, - #[serde(rename(serialize = "android:theme"))] + #[serde( + rename(serialize = "@android:theme"), + skip_serializing_if = "Option::is_none" + )] pub theme: Option, - #[serde(rename(serialize = "android:hasCode"))] + #[serde(rename(serialize = "@android:hasCode"))] #[serde(default)] pub has_code: bool, - #[serde(rename(serialize = "android:icon"))] + #[serde( + rename(serialize = "@android:icon"), + skip_serializing_if = "Option::is_none" + )] pub icon: Option, - #[serde(rename(serialize = "android:label"))] + #[serde(rename(serialize = "@android:label"))] #[serde(default)] pub label: String, - #[serde(rename(serialize = "android:extractNativeLibs"))] + #[serde( + rename(serialize = "@android:extractNativeLibs"), + skip_serializing_if = "Option::is_none" + )] pub extract_native_libs: Option, - #[serde(rename(serialize = "android:usesCleartextTraffic"))] + #[serde( + rename(serialize = "@android:usesCleartextTraffic"), + skip_serializing_if = "Option::is_none" + )] pub uses_cleartext_traffic: Option, #[serde(rename(serialize = "meta-data"))] @@ -90,32 +116,53 @@ pub struct Application { pub activity: Activity, } -/// Android [activity element](https://developer.android.com/guide/topics/manifest/activity-element). +/// Android [activity 元素](https://developer.android.com/guide/topics/manifest/activity-element). #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Activity { - #[serde(rename(serialize = "android:configChanges"))] - #[serde(default = "default_config_changes")] + #[serde(rename(serialize = "@android:configChanges"))] + #[serde( + default = "default_config_changes", + skip_serializing_if = "Option::is_none" + )] pub config_changes: Option, - #[serde(rename(serialize = "android:label"))] + #[serde( + rename(serialize = "@android:label"), + skip_serializing_if = "Option::is_none" + )] pub label: Option, - #[serde(rename(serialize = "android:launchMode"))] + #[serde( + rename(serialize = "@android:launchMode"), + skip_serializing_if = "Option::is_none" + )] pub launch_mode: Option, - #[serde(rename(serialize = "android:name"))] + #[serde(rename(serialize = "@android:name"))] #[serde(default = "default_activity_name")] pub name: String, - #[serde(rename(serialize = "android:screenOrientation"))] + #[serde( + rename(serialize = "@android:screenOrientation"), + skip_serializing_if = "Option::is_none" + )] pub orientation: Option, - #[serde(rename(serialize = "android:exported"))] + #[serde( + rename(serialize = "@android:exported"), + skip_serializing_if = "Option::is_none" + )] pub exported: Option, - #[serde(rename(serialize = "android:resizeableActivity"))] + #[serde( + rename(serialize = "@android:resizeableActivity"), + skip_serializing_if = "Option::is_none" + )] pub resizeable_activity: Option, - #[serde(rename(serialize = "android:alwaysRetainTaskState"))] + #[serde( + rename(serialize = "@android:alwaysRetainTaskState"), + skip_serializing_if = "Option::is_none" + )] pub always_retain_task_state: Option, #[serde(rename(serialize = "meta-data"))] #[serde(default)] pub meta_data: Vec, - /// If no `MAIN` action exists in any intent filter, a default `MAIN` filter is serialized by `cargo-apk`. + /// 如果任何意图过滤器中都不存在“MAIN”动作,则默认的“MAIN”过滤器由“cargo-apk2”序列化。 #[serde(rename(serialize = "intent-filter"))] #[serde(default)] pub intent_filter: Vec, @@ -141,12 +188,12 @@ impl Default for Activity { /// Android [intent filter element](https://developer.android.com/guide/topics/manifest/intent-filter-element). #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct IntentFilter { - /// Serialize strings wrapped in `` + /// 序列化包裹在 `` 中的字符串。 #[serde(serialize_with = "serialize_actions")] #[serde(rename(serialize = "action"))] #[serde(default)] pub actions: Vec, - /// Serialize as vector of structs for proper xml formatting + /// 序列化为结构向量以实现正确的 xml 格式 #[serde(serialize_with = "serialize_catergories")] #[serde(rename(serialize = "category"))] #[serde(default)] @@ -163,7 +210,7 @@ where #[derive(Serialize)] struct Action { - #[serde(rename = "android:name")] + #[serde(rename = "@android:name")] name: String, } let mut seq = serializer.serialize_seq(Some(actions.len()))?; @@ -183,7 +230,7 @@ where #[derive(Serialize)] struct Category { - #[serde(rename = "android:name")] + #[serde(rename = "@android:name")] pub name: String, } @@ -196,40 +243,40 @@ where seq.end() } -/// Android [intent filter data element](https://developer.android.com/guide/topics/manifest/data-element). +/// Android [intent filter data 元素](https://developer.android.com/guide/topics/manifest/data-element). #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct IntentFilterData { - #[serde(rename(serialize = "android:scheme"))] + #[serde(rename(serialize = "@android:scheme"))] pub scheme: Option, - #[serde(rename(serialize = "android:host"))] + #[serde(rename(serialize = "@android:host"))] pub host: Option, - #[serde(rename(serialize = "android:port"))] + #[serde(rename(serialize = "@android:port"))] pub port: Option, - #[serde(rename(serialize = "android:path"))] + #[serde(rename(serialize = "@android:path"))] pub path: Option, - #[serde(rename(serialize = "android:pathPattern"))] + #[serde(rename(serialize = "@android:pathPattern"))] pub path_pattern: Option, - #[serde(rename(serialize = "android:pathPrefix"))] + #[serde(rename(serialize = "@android:pathPrefix"))] pub path_prefix: Option, - #[serde(rename(serialize = "android:mimeType"))] + #[serde(rename(serialize = "@android:mimeType"))] pub mime_type: Option, } -/// Android [meta-data element](https://developer.android.com/guide/topics/manifest/meta-data-element). +/// Android [meta-data 元素](https://developer.android.com/guide/topics/manifest/meta-data-element). #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct MetaData { - #[serde(rename(serialize = "android:name"))] + #[serde(rename(serialize = "@android:name"))] pub name: String, - #[serde(rename(serialize = "android:value"))] + #[serde(rename(serialize = "@android:value"))] pub value: String, } -/// Android [uses-feature element](https://developer.android.com/guide/topics/manifest/uses-feature-element). +/// Android [uses-feature 元素](https://developer.android.com/guide/topics/manifest/uses-feature-element). #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct Feature { - #[serde(rename(serialize = "android:name"))] + #[serde(rename(serialize = "@android:name"))] pub name: Option, - #[serde(rename(serialize = "android:required"))] + #[serde(rename(serialize = "@android:required"))] pub required: Option, /// The `version` field is currently used for the following features: /// @@ -241,9 +288,9 @@ pub struct Feature { /// /// - `name="android.hardware.vulkan.version"`: Represents the value of Vulkan's `VkPhysicalDeviceProperties::apiVersion`. See the [Android documentation](https://developer.android.com/reference/android/content/pm/PackageManager#FEATURE_VULKAN_HARDWARE_VERSION) /// for available levels and the respective Vulkan features required/provided. - #[serde(rename(serialize = "android:version"))] + #[serde(rename(serialize = "@android:version"))] pub version: Option, - #[serde(rename(serialize = "android:glEsVersion"))] + #[serde(rename(serialize = "@android:glEsVersion"))] #[serde(serialize_with = "serialize_opengles_version")] pub opengles_version: Option<(u8, u8)>, } @@ -264,35 +311,34 @@ where } } -/// Android [uses-permission element](https://developer.android.com/guide/topics/manifest/uses-permission-element). +/// Android [uses-permission 元素](https://developer.android.com/guide/topics/manifest/uses-permission-element). #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct Permission { - #[serde(rename(serialize = "android:name"))] + #[serde(rename(serialize = "@android:name"))] pub name: String, - #[serde(rename(serialize = "android:maxSdkVersion"))] + #[serde(rename(serialize = "@android:maxSdkVersion"))] pub max_sdk_version: Option, } -/// Android [package element](https://developer.android.com/guide/topics/manifest/queries-element#package). +/// Android [package 元素](https://developer.android.com/guide/topics/manifest/queries-element#package). #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct Package { - #[serde(rename(serialize = "android:name"))] + #[serde(rename(serialize = "@android:name"))] pub name: String, } -/// Android [provider element](https://developer.android.com/guide/topics/manifest/queries-element#provider). +/// Android [provider 元素](https://developer.android.com/guide/topics/manifest/queries-element#provider). #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct QueryProvider { - #[serde(rename(serialize = "android:authorities"))] + #[serde(rename(serialize = "@android:authorities"))] pub authorities: String, - // The specs say only an `authorities` attribute is required for providers contained in a `queries` element - // however this is required for aapt support and should be made optional if/when cargo-apk migrates to aapt2 - #[serde(rename(serialize = "android:name"))] + // 规范规定,对于包含在“queries”元素中的提供程序,仅需要一个“authorities”属性,但这对于 aapt 支持是必需的,并且当 cargo-apk2 迁移到 aapt2 时,应将其设为可选 + #[serde(rename(serialize = "@android:name"))] pub name: String, } -/// Android [queries element](https://developer.android.com/guide/topics/manifest/queries-element). +/// Android [queries 元素](https://developer.android.com/guide/topics/manifest/queries-element). #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct Queries { #[serde(default)] @@ -303,21 +349,30 @@ pub struct Queries { pub provider: Vec, } -/// Android [uses-sdk element](https://developer.android.com/guide/topics/manifest/uses-sdk-element). +/// Android [uses-sdk 元素](https://developer.android.com/guide/topics/manifest/uses-sdk-element)。 #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Sdk { - #[serde(rename(serialize = "android:minSdkVersion"))] + #[serde( + rename(serialize = "@android:minSdkVersion"), + skip_serializing_if = "Option::is_none" + )] pub min_sdk_version: Option, - #[serde(rename(serialize = "android:targetSdkVersion"))] + #[serde( + rename(serialize = "@android:targetSdkVersion"), + skip_serializing_if = "Option::is_none" + )] pub target_sdk_version: Option, - #[serde(rename(serialize = "android:maxSdkVersion"))] + #[serde( + rename(serialize = "@android:maxSdkVersion"), + skip_serializing_if = "Option::is_none" + )] pub max_sdk_version: Option, } impl Default for Sdk { fn default() -> Self { Self { - min_sdk_version: Some(23), + min_sdk_version: Some(24), target_sdk_version: None, max_sdk_version: None, } diff --git a/ndk-build2/src/ndk.rs b/ndk-build2/src/ndk.rs index c861125..3710f8a 100644 --- a/ndk-build2/src/ndk.rs +++ b/ndk-build2/src/ndk.rs @@ -1,12 +1,13 @@ use crate::{error::NdkError, target::Target}; use std::{ collections::HashMap, + env::var, + fs::read_dir, path::{Path, PathBuf}, process::Command, }; -/// The default password used when creating the default `debug.keystore` via -/// [`Ndk::debug_key`] +/// 通过 [`Ndk::debug_key`] 创建默认 `debug.keystore` 时使用的默认密码 pub const DEFAULT_DEV_KEYSTORE_PASSWORD: &str = "android"; #[derive(Clone, Debug, Eq, PartialEq)] @@ -19,11 +20,12 @@ pub struct Ndk { } impl Ndk { + //noinspection SpellCheckingInspection pub fn from_env() -> Result { let user_home = { - let user_home = std::env::var("ANDROID_SDK_HOME") + let user_home = var("ANDROID_SDK_HOME") .map(PathBuf::from) - // Unlike ANDROID_USER_HOME, ANDROID_SDK_HOME points to the _parent_ directory of .android: + // 与 ANDROID_USER_HOME 不同,ANDROID_SDK_HOME 指向 .android 的 _父_ 目录: // https://developer.android.com/studio/command-line/variables#envar .map(|home| home.join(".android")) .ok(); @@ -36,9 +38,9 @@ impl Ndk { ); } - // Default to $HOME/.android + // 默认为 $HOME/.android user_home - .or_else(|| std::env::var("ANDROID_USER_HOME").map(PathBuf::from).ok()) + .or_else(|| var("ANDROID_USER_HOME").map(PathBuf::from).ok()) .or_else(|| dirs::home_dir().map(|home| home.join(".android"))) .ok_or_else(|| NdkError::PathNotFound(PathBuf::from("$HOME")))? }; @@ -46,13 +48,13 @@ impl Ndk { android_build::android_sdk().map_or(Err(NdkError::SdkNotFound), |i| Ok(i))?; let ndk_path = { - let ndk_path = std::env::var("ANDROID_NDK_ROOT") + let ndk_path = var("ANDROID_NDK_ROOT") .ok() - .or_else(|| std::env::var("ANDROID_NDK_PATH").ok()) - .or_else(|| std::env::var("ANDROID_NDK_HOME").ok()) - .or_else(|| std::env::var("NDK_HOME").ok()); + .or_else(|| var("ANDROID_NDK_PATH").ok()) + .or_else(|| var("ANDROID_NDK_HOME").ok()) + .or_else(|| var("NDK_HOME").ok()); - // default ndk installation path + // 默认 ndk 安装路径 if ndk_path.is_none() && sdk_path.join("ndk-bundle").exists() { sdk_path.join("ndk-bundle") } else { @@ -61,7 +63,7 @@ impl Ndk { }; let build_tools_dir = sdk_path.join("build-tools"); - let build_tools_version = std::fs::read_dir(&build_tools_dir) + let build_tools_version = read_dir(&build_tools_dir) .or(Err(NdkError::PathNotFound(build_tools_dir)))? .filter_map(|path| path.ok()) .filter(|path| path.path().is_dir()) @@ -80,13 +82,12 @@ impl Ndk { .split_once('=') .expect("Failed to parse `key = value` from source.properties"); if key.trim() == "Pkg.Revision" { - // AOSP writes a constantly-incrementing build version to the patch field. - // This number is incrementing across NDK releases. + // AOSP 将不断增加的版本号写入补丁字段。此数字会随着 NDK 版本的推移而不断增加。 let mut parts = value.trim().split('.'); let _major = parts.next().unwrap(); let _minor = parts.next().unwrap(); let patch = parts.next().unwrap(); - // Can have an optional `XXX-beta1` + // 可以有一个可选的“XXX-beta1” let patch = patch.split_once('-').map_or(patch, |(patch, _beta)| patch); Some(patch.parse().expect("Failed to parse patch field")) } else { @@ -109,7 +110,7 @@ impl Ndk { .unwrap(); let platforms_dir = sdk_path.join("platforms"); - let platforms: Vec = std::fs::read_dir(&platforms_dir) + let platforms: Vec = read_dir(&platforms_dir) .or(Err(NdkError::PathNotFound(platforms_dir)))? .filter_map(|path| path.ok()) .filter(|path| path.path().is_dir()) @@ -188,12 +189,11 @@ impl Ndk { self.platforms().iter().max().cloned().unwrap() } - /// Returns platform `30` as currently [required by Google Play], or lower - /// when the detected SDK does not support it yet. + /// 返回当前 [Google Play 所要求的] 平台“35”或更低版本(如果检测到的 SDK 尚不支持)。 /// - /// [required by Google Play]: https://developer.android.com/distribute/best-practices/develop/target-sdk + /// [Google Play 要求]: https://developer.android.com/distribute/best-practices/develop/target-sdk pub fn default_target_platform(&self) -> u32 { - self.highest_supported_platform().min(30) + self.highest_supported_platform().min(35) } pub fn platform_dir(&self, platform: u32) -> Result { @@ -209,16 +209,17 @@ impl Ndk { Ok(dir) } - pub fn android_jar(&self, platform: u32) -> Result { - let android_jar = self.platform_dir(platform)?.join("android.jar"); - if !android_jar.exists() { - return Err(NdkError::PathNotFound(android_jar)); - } + pub fn android_jar(&self, api_level: u32) -> Result { + let Some(android_jar) = + android_build::android_jar(Some(format!("android-{}", api_level).as_str())) + else { + return Err(NdkError::PlatformNotFound(api_level)); + }; Ok(android_jar) } fn host_arch() -> Result<&'static str, NdkError> { - let host_os = std::env::var("HOST").ok(); + let host_os = var("HOST").ok(); let host_contains = |s| host_os.as_ref().map(|h| h.contains(s)).unwrap_or(false); Ok(if host_contains("linux") { @@ -294,7 +295,7 @@ impl Ndk { let toolchain_path = self.toolchain_dir()?.join("bin"); // Since r21 (https://github.com/android/ndk/wiki/Changelog-r21) LLVM binutils are included _for testing_; - // Since r22 (https://github.com/android/ndk/wiki/Changelog-r22) GNU binutils are deprecated in favour of LLVM's; + // Since r22 (https://github.com/android/ndk/wiki/Changelog-r22) GNU binutils are deprecated in favour of LL-VM's; // Since r23 (https://github.com/android/ndk/wiki/Changelog-r23) GNU binutils have been removed. // To maintain stability with the current ndk-build crate release, prefer GNU binutils for // as long as it is provided by the NDK instead of trying to use llvm-* from r21 onwards. @@ -369,7 +370,7 @@ impl Ndk { if let Ok(keytool) = which::which(bin!("keytool")) { return Ok(Command::new(keytool)); } - if let Ok(java) = std::env::var("JAVA_HOME") { + if let Some(java) = android_build::java_home() { let keytool = PathBuf::from(java).join("bin").join(bin!("keytool")); if keytool.exists() { return Ok(Command::new(keytool)); @@ -378,6 +379,7 @@ impl Ndk { Err(NdkError::CmdNotFound("keytool".to_string())) } + //noinspection SpellCheckingInspection pub fn debug_key(&self) -> Result { let path = self.android_user_home()?.join("debug.keystore"); let password = DEFAULT_DEV_KEYSTORE_PASSWORD.to_owned(); @@ -453,6 +455,7 @@ impl Ndk { Err(NdkError::PlatformNotFound(min_sdk_version)) } + //noinspection SpellCheckingInspection pub fn detect_abi(&self, device_serial: Option<&str>) -> Result { let mut adb = self.adb(device_serial)?; diff --git a/ndk-build2/src/readelf.rs b/ndk-build2/src/readelf.rs index 1f8778f..935abcb 100644 --- a/ndk-build2/src/readelf.rs +++ b/ndk-build2/src/readelf.rs @@ -41,8 +41,7 @@ impl<'a> UnalignedApk<'a> { while let Some(artifact) = artifacts.pop() { self.add_lib(&artifact, target)?; for need in list_needed_libs(&readelf_path, &artifact)? { - // c++_shared is available in the NDK but not on-device. - // Must be bundled with the apk if used: + // c++_shared 在 NDK 中可用,但在设备上不可用。如果使用,必须与 apk 捆绑在一起: // https://developer.android.com/ndk/guides/cpp-support#libc let search_paths = if need == "libc++_shared.so" { &android_search_paths diff --git a/ndk-build2/src/target.rs b/ndk-build2/src/target.rs index 676b900..7366c4d 100644 --- a/ndk-build2/src/target.rs +++ b/ndk-build2/src/target.rs @@ -4,18 +4,18 @@ use serde::Deserialize; #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)] #[repr(u8)] pub enum Target { - #[serde(rename = "armv7-linux-androideabi")] + #[serde(rename(serialize = "armv7-linux-androideabi"))] ArmV7a = 1, - #[serde(rename = "aarch64-linux-android")] + #[serde(rename(serialize = "aarch64-linux-android"))] Arm64V8a = 2, - #[serde(rename = "i686-linux-android")] + #[serde(rename(serialize = "i686-linux-android"))] X86 = 3, - #[serde(rename = "x86_64-linux-android")] + #[serde(rename(serialize = "x86_64-linux-android"))] X86_64 = 4, } impl Target { - /// Identifier used in the NDK to refer to the ABI + /// NDK 中用于引用 ABI 的标识符 pub fn android_abi(self) -> &'static str { match self { Self::Arm64V8a => "arm64-v8a", @@ -25,7 +25,7 @@ impl Target { } } - /// Returns `Target` for abi. + /// 返回 abi 的“target”。 pub fn from_android_abi(abi: &str) -> Result { match abi { "arm64-v8a" => Ok(Self::Arm64V8a), @@ -36,7 +36,7 @@ impl Target { } } - /// Returns the triple used by the rust build tools + /// 返回 rust 构建工具使用的三元组 pub fn rust_triple(self) -> &'static str { match self { Self::Arm64V8a => "aarch64-linux-android", @@ -46,7 +46,7 @@ impl Target { } } - /// Returns `Target` for rust triple. + /// 返回 Rust 三元组的“target”。 pub fn from_rust_triple(triple: &str) -> Result { match triple { "aarch64-linux-android" => Ok(Self::Arm64V8a), @@ -57,7 +57,7 @@ impl Target { } } - // Returns the triple NDK provided LLVM + /// 返回三重 NDK 提供的 LLVM pub fn ndk_llvm_triple(self) -> &'static str { match self { Self::Arm64V8a => "aarch64-linux-android", @@ -67,7 +67,7 @@ impl Target { } } - /// Returns the triple used by the non-LLVM parts of the NDK + /// 返回 NDK 非 LLVM 部分使用的三元组 pub fn ndk_triple(self) -> &'static str { match self { Self::Arm64V8a => "aarch64-linux-android", diff --git a/ndk-examples/Cargo.toml b/ndk-examples/Cargo.toml index d2ab15f..1bc06d6 100644 --- a/ndk-examples/Cargo.toml +++ b/ndk-examples/Cargo.toml @@ -1,21 +1,27 @@ [package] name = "ndk-examples" -version = "0.1.0" +version.workspace = true authors = [ "SmileSky ", "David Craven " ] -edition = "2021" +edition.workspace = true publish = false [target.'cfg(target_os = "android")'.dependencies] jni = "0.21.1" -libc = "0.2" -log = "0.4.14" -ndk = { version = "0.7", features = ["api-level-23"] } +libc = "0.2.162" +log = "0.4.22" ndk-context = "0.1.1" -android_logger = "0.11.0" -android-activity = { version = "0.4", features = ["native-activity"] } +android_logger = "0.14.1" + +[target.'cfg(target_os = "android")'.dependencies.android-activity] +version = "0.6.0" +features = ["native-activity"] + +[target.'cfg(target_os = "android")'.dependencies.ndk] +version = "0.9.0" +features = ["api-level-24"] [[example]] name = "hello_world" @@ -29,6 +35,9 @@ crate-type = ["cdylib"] name = "looper" crate-type = ["cdylib"] +[package.metadata.android] +use_aapt2 = true + [package.metadata.android.sdk] min_sdk_version = 24 target_sdk_version = 35 diff --git a/ndk-examples/README.md b/ndk-examples/README.md index f0477aa..07aad76 100644 --- a/ndk-examples/README.md +++ b/ndk-examples/README.md @@ -14,7 +14,7 @@ adb logcat RustStdoutStderr:D '*:S' 在控制台中打印“hello world” ```shell -cargo apk2 build --example hello_world +cargo apk2 run --example hello_world ``` ### jni_audio diff --git a/ndk-examples/examples/hello_world.rs b/ndk-examples/examples/hello_world.rs index 0f5081a..b7c41e1 100644 --- a/ndk-examples/examples/hello_world.rs +++ b/ndk-examples/examples/hello_world.rs @@ -1,10 +1,11 @@ use android_activity::AndroidApp; -use log::info; +use android_logger::{init_once, Config}; +use log::{info, LevelFilter}; use ndk::trace; #[no_mangle] fn android_main(_app: AndroidApp) { - android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Info)); + init_once(Config::default().with_max_level(LevelFilter::Info)); let _trace; if trace::is_trace_enabled() { diff --git a/ndk-examples/examples/jni_audio.rs b/ndk-examples/examples/jni_audio.rs index 5074591..ff58761 100644 --- a/ndk-examples/examples/jni_audio.rs +++ b/ndk-examples/examples/jni_audio.rs @@ -1,22 +1,24 @@ use android_activity::AndroidApp; -use jni::objects::JObject; +use android_logger::{init_once, Config}; +use jni::objects::{JIntArray, JObject, JObjectArray}; +use log::LevelFilter; #[no_mangle] fn android_main(_app: AndroidApp) { - android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Info)); + init_once(Config::default().with_max_level(LevelFilter::Info)); enumerate_audio_devices().unwrap(); } const GET_DEVICES_OUTPUTS: jni::sys::jint = 2; fn enumerate_audio_devices() -> Result<(), Box> { - // Create a VM for executing Java calls + // 创建用于执行 Java 调用的 VM let ctx = ndk_context::android_context(); let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }?; let context = unsafe { JObject::from_raw(ctx.context().cast()) }; - let env = vm.attach_current_thread()?; + let mut env = vm.attach_current_thread()?; - // Query the global Audio Service + // 查询全局音频服务 let class_ctxt = env.find_class("android/content/Context")?; let audio_service = env.get_static_field(class_ctxt, "AUDIO_SERVICE", "Ljava/lang/String;")?; @@ -27,11 +29,11 @@ fn enumerate_audio_devices() -> Result<(), Box> { // JNI type signature needs to be derived from the Java API // (ArgTys)ResultTy "(Ljava/lang/String;)Ljava/lang/Object;", - &[audio_service], + &[(&audio_service).into()], )? .l()?; - // Enumerate output devices + // 枚举输出设备 let devices = env.call_method( audio_manager, "getDevices", @@ -41,28 +43,31 @@ fn enumerate_audio_devices() -> Result<(), Box> { println!("-- Output Audio Devices --"); - let device_array = devices.l()?.into_raw(); - let len = env.get_array_length(device_array)?; + let device_array = unsafe { JObjectArray::from_raw(devices.l()?.into_raw()) }; + let len = env.get_array_length(&device_array)?; for i in 0..len { - let device = env.get_object_array_element(device_array, i)?; + let device = env.get_object_array_element(&device_array, i)?; - // Collect device information + // 收集设备信息 // See https://developer.android.com/reference/android/media/AudioDeviceInfo let product_name: String = { let name = - env.call_method(device, "getProductName", "()Ljava/lang/CharSequence;", &[])?; + env.call_method(&device, "getProductName", "()Ljava/lang/CharSequence;", &[])?; let name = env.call_method(name.l()?, "toString", "()Ljava/lang/String;", &[])?; - env.get_string(name.l()?.into())?.into() + env.get_string((&name.l()?).into())?.into() }; - let id = env.call_method(device, "getId", "()I", &[])?.i()?; - let ty = env.call_method(device, "getType", "()I", &[])?.i()?; + let id = env.call_method(&device, "getId", "()I", &[])?.i()?; + let ty = env.call_method(&device, "getType", "()I", &[])?.i()?; let sample_rates = { - let sample_array = env - .call_method(device, "getSampleRates", "()[I", &[])? - .l()? - .into_raw(); - let len = env.get_array_length(sample_array)?; + let sample_array = unsafe { + JIntArray::from_raw( + env.call_method(&device, "getSampleRates", "()[I", &[])? + .l()? + .into_raw(), + ) + }; + let len = env.get_array_length(&sample_array)?; let mut sample_rates = vec![0; len as usize]; env.get_int_array_region(sample_array, 0, &mut sample_rates)?; diff --git a/ndk-examples/examples/looper.rs b/ndk-examples/examples/looper.rs index f24ea86..8869e7b 100644 --- a/ndk-examples/examples/looper.rs +++ b/ndk-examples/examples/looper.rs @@ -1,22 +1,27 @@ -//! Demonstrates how to manage application lifetime using Android's `Looper` +//! 演示如何使用 Android 的“Looper”管理应用程序生命周期 -use std::mem::MaybeUninit; -use std::os::unix::prelude::RawFd; -use std::time::Duration; - -use log::info; +use log::{error, info, LevelFilter}; use ndk::looper::{FdEvent, ThreadLooper}; - -const U32_SIZE: usize = std::mem::size_of::(); +use std::{ + mem::MaybeUninit, + os::{ + fd::{AsRawFd, BorrowedFd}, + unix::prelude::RawFd, + }, + thread::{sleep, spawn}, + time::Duration, +}; + +const U32_SIZE: usize = size_of::(); use android_activity::{AndroidApp, InputStatus, MainEvent, PollEvent}; +use android_logger::{init_once, Config}; #[no_mangle] fn android_main(app: AndroidApp) { - android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Info)); + init_once(Config::default().with_max_level(LevelFilter::Info)); - // Retrieve the Looper that android-activity created for us on the current thread. - // android-activity uses this to block on events and poll file descriptors with a single mechanism. + // 检索 android-activity 在当前线程上为我们创建的 Looper。android-activity 使用它来阻止事件并通过单一机制轮询文件描述符。 let looper = ThreadLooper::for_thread().expect("ndk-glue did not attach thread looper before main()!"); @@ -26,31 +31,35 @@ fn android_main(app: AndroidApp) { unsafe { ends.assume_init() } } - // Create a Unix pipe to send custom events to the Looper. ndk-glue uses a similar mechanism to deliver - // ANativeActivityCallbacks asynchronously to the Looper through NDK_GLUE_LOOPER_EVENT_PIPE_IDENT. + // 创建一个 Unix 管道,将自定义事件发送到 Looper。ndk-glue 使用类似的机制,通过 NDK_GLUE_LOOPER_EVENT_PIPE_IDENT 将 ANativeActivityCallbacks 异步传递给 Looper。 let custom_event_pipe = create_pipe(); let custom_callback_pipe = create_pipe(); - // Attach the reading end of a pipe to a callback, too + // 将管道的读取端也附加到回调 looper .as_foreign() - .add_fd_with_callback(custom_callback_pipe[0], FdEvent::INPUT, |fd| { - let mut recv = !0u32; - assert_eq!( - unsafe { libc::read(fd, &mut recv as *mut _ as *mut _, U32_SIZE) } as usize, - U32_SIZE - ); - println!("Read custom event from pipe, in callback: {}", recv); - // Detach this handler by returning `false` once the count reaches 5 - recv < 5 - }) + .add_fd_with_callback( + unsafe { BorrowedFd::borrow_raw(custom_callback_pipe[0]) }, + FdEvent::INPUT, + |fd, _| { + let mut recv = !0u32; + assert_eq!( + unsafe { libc::read(fd.as_raw_fd(), &mut recv as *mut _ as *mut _, U32_SIZE) } + as usize, + U32_SIZE + ); + println!("Read custom event from pipe, in callback: {}", recv); + // 一旦计数达到 5,通过返回“false”来分离此处理程序 + recv < 5 + }, + ) .expect("Failed to add file descriptor to Looper"); - std::thread::spawn(move || { - // Send a "custom event" to the looper every second + spawn(move || { + // 每秒向 looper 发送一个“自定义事件” for i in 0.. { let i_addr = &i as *const _ as *const _; - std::thread::sleep(Duration::from_secs(1)); + sleep(Duration::from_secs(1)); assert_eq!( unsafe { libc::write(custom_event_pipe[1], i_addr, U32_SIZE) }, U32_SIZE as isize @@ -67,74 +76,76 @@ fn android_main(app: AndroidApp) { let mut render_state: Option<()> = Default::default(); while !exit { - app.poll_events( - Some(std::time::Duration::from_secs(1)), /* timeout */ - |event| { - match event { - PollEvent::Wake => { - info!("Early wake up"); - } - PollEvent::Timeout => { - info!("Timed out"); - // Real app would probably rely on vblank sync via graphics API... - redraw_pending = true; - } - PollEvent::Main(main_event) => { - info!("Main event: {:?}", main_event); - match main_event { - MainEvent::SaveState { saver, .. } => { - saver.store("foo://bar".as_bytes()); - } - MainEvent::Pause => {} - MainEvent::Resume { loader, .. } => { - if let Some(state) = loader.load() { - if let Ok(uri) = String::from_utf8(state) { - info!("Resumed with saved state = {uri:#?}"); - } + app.poll_events(Some(Duration::from_secs(1)) /* timeout */, |event| { + match event { + PollEvent::Wake => { + info!("Early wake up"); + } + PollEvent::Timeout => { + info!("Timed out"); + // 真正的应用程序可能会依赖通过图形 API 实现的 vblank 同步... + redraw_pending = true; + } + PollEvent::Main(main_event) => { + info!("Main event: {:?}", main_event); + match main_event { + MainEvent::SaveState { saver, .. } => { + saver.store("foo://bar".as_bytes()); + } + MainEvent::Pause => {} + MainEvent::Resume { loader, .. } => { + if let Some(state) = loader.load() { + if let Ok(uri) = String::from_utf8(state) { + info!("Resumed with saved state = {uri:#?}"); } } - MainEvent::InitWindow { .. } => { - render_state = Some(()); - redraw_pending = true; - } - MainEvent::TerminateWindow { .. } => { - render_state = None; - } - MainEvent::WindowResized { .. } => { - redraw_pending = true; - } - MainEvent::RedrawNeeded { .. } => { - redraw_pending = true; - } - MainEvent::InputAvailable { .. } => { - redraw_pending = true; - } - MainEvent::ConfigChanged { .. } => { - info!("Config Changed: {:#?}", app.config()); - } - MainEvent::LowMemory => {} - - MainEvent::Destroy => exit = true, - _ => { /* ... */ } } + MainEvent::InitWindow { .. } => { + render_state = Some(()); + redraw_pending = true; + } + MainEvent::TerminateWindow { .. } => { + render_state = None; + } + MainEvent::WindowResized { .. } => { + redraw_pending = true; + } + MainEvent::RedrawNeeded { .. } => { + redraw_pending = true; + } + MainEvent::InputAvailable { .. } => { + redraw_pending = true; + } + MainEvent::ConfigChanged { .. } => { + info!("Config Changed: {:#?}", app.config()); + } + MainEvent::LowMemory => {} + + MainEvent::Destroy => exit = true, + _ => { /* ... */ } } - _ => {} } - - if redraw_pending { - if let Some(_rs) = render_state { - redraw_pending = false; - - // Handle input - app.input_events(|event| { - info!("Input Event: {event:?}"); - InputStatus::Unhandled - }); - - info!("Render..."); + _ => {} + } + + if redraw_pending { + if let Some(_rs) = render_state { + redraw_pending = false; + + // 处理输入 + match app.input_events_iter() { + Ok(mut it) => loop { + it.next(|event| { + info!("Input Event: {event:?}"); + InputStatus::Unhandled + }); + }, + Err(e) => error!("{}", e), } + + info!("Render..."); } - }, - ); + } + }); } }