diff --git a/.gitignore b/.gitignore
index 6f8d58c..5342e7d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,4 @@ local.properties
/.idea/
/.kotlin/
/framework/build/
+/framework/src/main/rust/**/target/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0917ef7..4e795bf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `FwMediaBrowserService` strategy and wired it into config, startup, shutdown, and health checks.
- Added native startActivity strategies for `NEW_TASK + EXCLUDE_FROM_RECENTS + NO_ANIMATION` and `moveTaskToFront`.
- Added `FwStart.startAuditAll()` for full native strategy audit while keeping `FwStart.start()` on executable strategies by default.
+- Added optional Rust Native skeleton build with 4 Android ABI outputs behind `-PfwBuildRust=true`.
+- Added Rust read-only process diagnostics for OOM adj, memory, process status, root marker, and process count probing.
+- Added optional Rust MediaRoute native state and heartbeat implementation with C++ fallback.
### Changed
- Hardened VPN startup to require user authorization, foreground notification startup, no default traffic routing, and explicit special-use subtype metadata.
diff --git a/README-en.md b/README-en.md
index 59a9a48..4a88acb 100644
--- a/README-en.md
+++ b/README-en.md
@@ -222,6 +222,14 @@ adb install -r app/build/outputs/apk/debug/app-debug.apk
# Publish framework artifact to local Maven
./gradlew :framework:publishReleasePublicationToMavenLocal
+# Optional Rust Native skeleton
+rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android
+./gradlew :framework:checkFwRustToolchain
+./gradlew :framework:assembleRelease -PfwBuildRust=true
+# If cargo is not in PATH, append: -PfwCargoPath=/path/to/cargo
+
+The optional Rust layer currently provides JNI dynamic registration, build-pipeline probing, read-only process diagnostics, and MediaRoute state/heartbeat logic. Existing `libfw_native.so` and `libfw_mediaroute.so` behavior remains available as fallback.
+
# Repeated kill/recovery test
./kill_alive.sh
diff --git a/README-ja.md b/README-ja.md
index 302ee33..27b16e4 100644
--- a/README-ja.md
+++ b/README-ja.md
@@ -106,6 +106,10 @@ adb install -r app/build/outputs/apk/debug/app-debug.apk
./gradlew build
./gradlew :framework:publishReleasePublicationToMavenLocal
+rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android
+./gradlew :framework:checkFwRustToolchain
+./gradlew :framework:assembleRelease -PfwBuildRust=true
+
./kill_alive.sh
adb logcat | grep -E "(Fw|FwStart|FwHealth|ServiceStarter)"
```
diff --git a/README-ko.md b/README-ko.md
index 39b6c94..1312393 100644
--- a/README-ko.md
+++ b/README-ko.md
@@ -106,6 +106,10 @@ adb install -r app/build/outputs/apk/debug/app-debug.apk
./gradlew build
./gradlew :framework:publishReleasePublicationToMavenLocal
+rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android
+./gradlew :framework:checkFwRustToolchain
+./gradlew :framework:assembleRelease -PfwBuildRust=true
+
./kill_alive.sh
adb logcat | grep -E "(Fw|FwStart|FwHealth|ServiceStarter)"
```
diff --git a/README-zh-Hant.md b/README-zh-Hant.md
index 8d4bf85..de35013 100644
--- a/README-zh-Hant.md
+++ b/README-zh-Hant.md
@@ -106,6 +106,10 @@ adb install -r app/build/outputs/apk/debug/app-debug.apk
./gradlew build
./gradlew :framework:publishReleasePublicationToMavenLocal
+rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android
+./gradlew :framework:checkFwRustToolchain
+./gradlew :framework:assembleRelease -PfwBuildRust=true
+
./kill_alive.sh
adb logcat | grep -E "(Fw|FwStart|FwHealth|ServiceStarter)"
```
diff --git a/README.md b/README.md
index adcab9f..102b386 100644
--- a/README.md
+++ b/README.md
@@ -406,6 +406,20 @@ adb install -r app/build/outputs/apk/debug/app-debug.apk
# 输出:release/app-202604101118.apk + mapping 文件
```
+### Rust Native(可选)
+
+源码工程默认不强制构建 Rust so,避免没有 Rust 工具链的环境无法编译现有 C++ 版本。需要验证 Rust 迁移骨架时,先安装 Rust 并添加 Android targets,然后显式开启构建开关:
+
+```bash
+rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android
+./gradlew :framework:checkFwRustToolchain
+./gradlew :framework:assembleRelease -PfwBuildRust=true
+```
+
+如果 `cargo` 不在 `PATH` 中,可以追加 `-PfwCargoPath=/path/to/cargo` 指定 Cargo 路径。
+
+开启后会为 `armeabi-v7a`、`arm64-v8a`、`x86`、`x86_64` 生成并打包 `libfw_rust.so`。当前 Rust 层已接入 JNI 动态注册、构建链路探测、只读进程信息快照和 MediaRoute 状态/心跳逻辑;现有 `libfw_native.so` 与 `libfw_mediaroute.so` 行为保持不变,Rust so 不存在时自动回退。
+
### 保活测试
```bash
diff --git a/app/src/androidTest/java/com/google/services/FwStartInstrumentedTest.kt b/app/src/androidTest/java/com/google/services/FwStartInstrumentedTest.kt
new file mode 100644
index 0000000..d177222
--- /dev/null
+++ b/app/src/androidTest/java/com/google/services/FwStartInstrumentedTest.kt
@@ -0,0 +1,208 @@
+/**
+ * ============================================================================
+ * FwStartInstrumentedTest.kt - Native startActivity 设备侧真实触发测试
+ * ============================================================================
+ *
+ * 功能简介:
+ * 在 Android 设备或模拟器上真实调用 C++ start 文件夹的统一入口,
+ * 覆盖 application context、Activity context、只登记策略和异常入参。
+ *
+ * 函数简介:
+ * - startWithZeroMask_returnsInvalidArgument:验证空策略受控失败。
+ * - applicationContextStrategies_triggerNativeStart:验证应用上下文可执行策略。
+ * - activityContextStrategies_triggerNativeStart:验证 Activity 上下文可执行策略。
+ * - systemConstrainedStrategies_returnControlledResult:验证系统限制策略受控返回。
+ * - allStartStrategies_areCoveredByInstrumentedTests:验证所有策略均被测试覆盖。
+ *
+ * @author Pangu-Immortal
+ * @github https://github.com/Pangu-Immortal/KeepLiveService
+ * @since 2.0.1
+ */
+package com.google.services
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.service.framework.start.FwStart
+import com.service.framework.start.FwStartResult
+import com.service.framework.start.FwStartStrategy
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Native startActivity 设备侧真实触发测试。
+ */
+@RunWith(AndroidJUnit4::class)
+class FwStartInstrumentedTest {
+
+ // application context 下稳定成功的公开策略。
+ private val applicationStableStrategies = listOf(
+ FwStartStrategy.CONTEXT_NEW_TASK,
+ FwStartStrategy.CONTEXT_NEW_TASK_EXCLUDE_RECENTS,
+ FwStartStrategy.DOUBLE_START_ACTIVITIES
+ )
+
+ // Activity context 下稳定成功的公开策略。
+ private val activityStableStrategies = listOf(
+ FwStartStrategy.CONTEXT_DIRECT,
+ FwStartStrategy.START_FOR_RESULT_HOOK
+ )
+
+ // 受 Android 版本、权限、前后台状态或安全策略限制的策略。
+ private val systemConstrainedStrategies = listOf(
+ FwStartStrategy.PENDING_INTENT_SEND,
+ FwStartStrategy.BINDER_START_ACTIVITIES,
+ FwStartStrategy.VIRTUAL_DISPLAY,
+ FwStartStrategy.START_NEXT_MATCHING,
+ FwStartStrategy.MOVE_TASK_TO_FRONT,
+ FwStartStrategy.NOTIFICATION_BAL,
+ FwStartStrategy.MEDIA_BUTTON_BAL,
+ FwStartStrategy.CREDENTIAL_MANAGER,
+ FwStartStrategy.PRINT_MANAGER,
+ FwStartStrategy.SHELL_START_IN_VSYNC
+ )
+
+ // 当前测试启动的 Activity,结束时统一关闭。
+ private var launchedActivity: Activity? = null
+
+ /**
+ * 测试结束后关闭由用例启动的 Activity。
+ */
+ @After
+ fun finishLaunchedActivity() {
+ // 避免多次 startActivity 后影响后续用例焦点。
+ launchedActivity?.finish()
+ // 清空 Activity 引用。
+ launchedActivity = null
+ }
+
+ /**
+ * 获取被测应用上下文。
+ */
+ private fun targetContext(): Context {
+ // targetContext 指向 app 模块真实安装包。
+ return InstrumentationRegistry.getInstrumentation().targetContext
+ }
+
+ /**
+ * 构造指向 debug 测试 Activity 的真实 Intent。
+ */
+ private fun mainIntent(context: Context): Intent {
+ // 使用字符串类名,避免 androidTest 编译期依赖 debug 源集类。
+ return Intent().setClassName(context.packageName, "com.google.services.TestStartActivity")
+ }
+
+ /**
+ * 启动一个真实 debug 测试 Activity,并返回 Activity context。
+ */
+ private fun launchMainActivity(): Activity {
+ // 使用 instrumentation 在目标进程真实启动 Activity。
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ // Activity 启动需要 NEW_TASK 标志。
+ val intent = mainIntent(targetContext()).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ // 同步等待 Activity 创建完成。
+ val activity = instrumentation.startActivitySync(intent)
+ // 保存 Activity,便于测试结束关闭。
+ launchedActivity = activity
+ // 等待主线程空闲,确保窗口和 taskId 就绪。
+ instrumentation.waitForIdleSync()
+ return activity
+ }
+
+ /**
+ * 验证 modeMask=0 时不会进入 Native 执行,并返回参数错误。
+ */
+ @Test
+ fun startWithZeroMask_returnsInvalidArgument() {
+ // 获取真实应用上下文。
+ val context = targetContext()
+ // 执行空策略启动。
+ val result = FwStart.startWithMask(context, mainIntent(context), 0)
+ // 空策略必须返回参数错误。
+ assertEquals("空策略应返回参数错误", FwStartResult.INVALID_ARGUMENT, result.nativeCode)
+ }
+
+ /**
+ * 验证 application context 下的公开可执行 startActivity 策略能真实触发。
+ */
+ @Test
+ fun applicationContextStrategies_triggerNativeStart() {
+ // 获取真实应用上下文。
+ val context = targetContext()
+
+ // 逐个策略单独触发,确保真实打到每个 Native 分支。
+ for (strategy in applicationStableStrategies) {
+ // 每次使用新 Intent,避免前一个策略修改 flags 后影响后一个策略。
+ val result = FwStart.startWithStrategies(context, mainIntent(context), listOf(strategy))
+ // 公开 application context 策略应命中对应策略。
+ assertEquals("${strategy.displayName} 应成功命中", strategy, result.strategy)
+ // 成功结果必须携带正数 Native 策略位。
+ assertTrue("${strategy.displayName} 返回码应为正数", result.nativeCode > 0)
+ }
+ }
+
+ /**
+ * 验证 Activity context 下的公开可执行 startActivity 策略能真实触发或受控失败。
+ */
+ @Test
+ fun activityContextStrategies_triggerNativeStart() {
+ // 启动真实 Activity context。
+ val activity = launchMainActivity()
+
+ // 逐个策略单独触发,确保真实打到每个 Native 分支。
+ for (strategy in activityStableStrategies) {
+ // 每次使用 Activity 构造新 Intent。
+ val result = FwStart.startWithStrategies(activity, mainIntent(activity), listOf(strategy))
+ // Activity 直启和 startActivityForResult 属于公开稳定 API,应成功命中。
+ assertEquals("${strategy.displayName} 应成功命中", strategy, result.strategy)
+ // 成功结果必须携带正数 Native 策略位。
+ assertTrue("${strategy.displayName} 返回码应为正数", result.nativeCode > 0)
+ }
+ }
+
+ /**
+ * 验证受系统限制的策略会真实进入 Native 分支,并以成功或受控失败结束。
+ */
+ @Test
+ fun systemConstrainedStrategies_returnControlledResult() {
+ // 启动真实 Activity context,供需要 Activity 的系统策略使用。
+ val activity = launchMainActivity()
+
+ // 逐个策略单独触发,确保真实打到每个 Native 分支。
+ for (strategy in systemConstrainedStrategies) {
+ // Activity 相关策略使用 Activity context,其余策略使用 application context。
+ val context = when (strategy) {
+ FwStartStrategy.START_NEXT_MATCHING,
+ FwStartStrategy.MOVE_TASK_TO_FRONT -> activity
+ else -> targetContext()
+ }
+ // 每次使用新 Intent,避免策略修改 flags 后影响下一个策略。
+ val result = FwStart.startWithStrategies(context, mainIntent(context), listOf(strategy))
+ // 这些策略必须真实进入 Native,不能被 Kotlin 层空策略拦截。
+ assertNotEquals("${strategy.displayName} 不应返回参数错误", FwStartResult.INVALID_ARGUMENT, result.nativeCode)
+ // 系统限制策略允许成功或受控失败,但必须有明确返回码。
+ assertTrue("${strategy.displayName} 应返回明确结果", result.success || result.nativeCode < 0)
+ }
+ }
+
+ /**
+ * 验证当前枚举中的每一个 startActivity 策略都被设备侧测试覆盖。
+ */
+ @Test
+ fun allStartStrategies_areCoveredByInstrumentedTests() {
+ // 汇总所有测试矩阵中声明覆盖的策略。
+ val coveredStrategies = (
+ applicationStableStrategies +
+ activityStableStrategies +
+ systemConstrainedStrategies
+ ).toSet()
+ // 当前枚举中的所有策略必须都在测试矩阵中。
+ assertEquals("所有 startActivity 策略都必须被设备侧测试覆盖", FwStartStrategy.entries.toSet(), coveredStrategies)
+ }
+}
diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..51c6a71
--- /dev/null
+++ b/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/debug/java/com/google/services/TestStartActivity.kt b/app/src/debug/java/com/google/services/TestStartActivity.kt
new file mode 100644
index 0000000..ae52a74
--- /dev/null
+++ b/app/src/debug/java/com/google/services/TestStartActivity.kt
@@ -0,0 +1,42 @@
+/**
+ * ============================================================================
+ * TestStartActivity.kt - Debug 专用 startActivity 测试 Activity
+ * ============================================================================
+ *
+ * 功能简介:
+ * 提供一个无动画、无权限请求、无后台任务的极简 Activity,专门用于
+ * instrumentation 测试真实触发 Native startActivity 策略。
+ *
+ * 函数简介:
+ * - onCreate:创建一个简单 View,确保主线程可以快速进入 idle 状态。
+ *
+ * @author Pangu-Immortal
+ * @github https://github.com/Pangu-Immortal/KeepLiveService
+ * @since 2.0.1
+ */
+package com.google.services
+
+import android.app.Activity
+import android.graphics.Color
+import android.os.Bundle
+import android.view.View
+
+/**
+ * Debug 专用 startActivity 测试 Activity。
+ */
+class TestStartActivity : Activity() {
+
+ /**
+ * 创建无动画测试界面。
+ */
+ override fun onCreate(savedInstanceState: Bundle?) {
+ // 先执行 Activity 标准初始化。
+ super.onCreate(savedInstanceState)
+ // 创建一个简单 View,避免 Compose 动画导致 instrumentation 等不到 idle。
+ val contentView = View(this)
+ // 使用纯色背景,保证绘制成本最低。
+ contentView.setBackgroundColor(Color.BLACK)
+ // 设置测试内容视图。
+ setContentView(contentView)
+ }
+}
diff --git a/framework/build.gradle.kts b/framework/build.gradle.kts
index 1e445b5..bf3bfd4 100644
--- a/framework/build.gradle.kts
+++ b/framework/build.gradle.kts
@@ -1,3 +1,9 @@
+import org.gradle.api.GradleException
+import org.gradle.api.tasks.Copy
+import org.gradle.api.tasks.Delete
+import org.gradle.api.tasks.Exec
+import java.util.Properties
+
/**
* ===============================================================================
* Fw Android Keep-Alive Framework - Library Module Build Configuration
@@ -36,10 +42,39 @@ plugins {
signing
}
+data class FwRustAndroidTarget(
+ val abi: String,
+ val rustTriple: String,
+ val linkerPrefix: String
+)
+
+val fwRustAndroidTargets = listOf(
+ FwRustAndroidTarget("armeabi-v7a", "armv7-linux-androideabi", "armv7a-linux-androideabi"),
+ FwRustAndroidTarget("arm64-v8a", "aarch64-linux-android", "aarch64-linux-android"),
+ FwRustAndroidTarget("x86", "i686-linux-android", "i686-linux-android"),
+ FwRustAndroidTarget("x86_64", "x86_64-linux-android", "x86_64-linux-android")
+)
+val fwAndroidNdkVersion = "27.2.12479018"
+val fwBuildRust = providers.gradleProperty("fwBuildRust")
+ .map { value -> value.equals("true", ignoreCase = true) }
+ .orElse(false)
+val fwRustManifest = layout.projectDirectory.file("src/main/rust/fw_rust/Cargo.toml")
+val fwRustTargetDir = layout.buildDirectory.dir("rust/target")
+val fwRustJniDir = layout.buildDirectory.dir("generated/rustJniLibs")
+val fwRustMinSdk = 24
+
+fun String.toFwTaskSuffix(): String =
+ split("-", "_").joinToString("") { part ->
+ part.replaceFirstChar { char -> char.uppercase() }
+ }
+
+fun rustLinkerEnvName(rustTriple: String): String =
+ "CARGO_TARGET_${rustTriple.uppercase().replace("-", "_")}_LINKER"
+
android {
namespace = "com.service.framework"
compileSdk = 36
- ndkVersion = "27.2.12479018"
+ ndkVersion = fwAndroidNdkVersion
defaultConfig {
minSdk = 24
@@ -128,6 +163,17 @@ android {
}
}
+androidComponents {
+ onVariants(selector().all()) { variant ->
+ // 只有显式开启 Rust 构建时才把生成目录加入 jniLibs,避免默认构建误打包旧 so。
+ if (fwBuildRust.get()) {
+ variant.sources.jniLibs?.addStaticSourceDirectory(
+ fwRustJniDir.get().asFile.absolutePath
+ )
+ }
+ }
+}
+
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.media)
@@ -143,6 +189,155 @@ dependencies {
androidTestImplementation(libs.androidx.espresso.core)
}
+// ==================== Rust Native 构建配置 ====================
+
+fun resolveAndroidSdkDir(): File {
+ val localPropertiesFile = rootProject.file("local.properties")
+ if (localPropertiesFile.isFile) {
+ val properties = Properties()
+ localPropertiesFile.inputStream().use { input -> properties.load(input) }
+ val sdkDir = properties.getProperty("sdk.dir")
+ if (!sdkDir.isNullOrBlank()) {
+ return File(sdkDir)
+ }
+ }
+ val envSdkDir = System.getenv("ANDROID_HOME") ?: System.getenv("ANDROID_SDK_ROOT")
+ if (!envSdkDir.isNullOrBlank()) {
+ return File(envSdkDir)
+ }
+ throw GradleException("未找到 Android SDK 目录,请配置 local.properties 的 sdk.dir 或 ANDROID_HOME")
+}
+
+fun resolveExecutable(command: String): File? {
+ val commandFile = File(command)
+ if (commandFile.isAbsolute || command.contains(File.separator)) {
+ return commandFile.takeIf { file -> file.canExecute() }
+ }
+ return System.getenv("PATH")
+ .orEmpty()
+ .split(File.pathSeparator)
+ .asSequence()
+ .filter { path -> path.isNotBlank() }
+ .map { path -> File(path, command) }
+ .firstOrNull { file -> file.canExecute() }
+}
+
+fun resolveNdkToolchainBin(): File {
+ val ndkDir = File(resolveAndroidSdkDir(), "ndk/$fwAndroidNdkVersion")
+ val prebuiltDir = File(ndkDir, "toolchains/llvm/prebuilt")
+ val hostCandidates = listOf("darwin-x86_64", "darwin-aarch64", "linux-x86_64", "windows-x86_64")
+ val hostDir = hostCandidates
+ .map { hostName -> File(prebuiltDir, hostName) }
+ .firstOrNull { candidate -> candidate.isDirectory }
+ ?: throw GradleException("未找到 Android NDK LLVM 工具链目录:${prebuiltDir.absolutePath}")
+ return File(hostDir, "bin")
+}
+
+tasks.register("cleanFwRustAndroid") {
+ group = "rust"
+ description = "清理 Fw Rust Native 产物"
+ delete(fwRustTargetDir, fwRustJniDir)
+}
+
+tasks.register("checkFwRustToolchain") {
+ group = "rust"
+ description = "检查 Rust、Cargo 与 Android NDK 链接器是否可用"
+ doLast {
+ val cargoExecutable = findProperty("fwCargoPath") as String? ?: "cargo"
+ val cargoFile = resolveExecutable(cargoExecutable)
+ ?: throw GradleException("未找到 Cargo 可执行文件,请安装 Rust 或通过 -PfwCargoPath 指定 cargo 路径")
+ val rustcExecutable = findProperty("fwRustcPath") as String? ?: File(cargoFile.parentFile, "rustc").absolutePath
+ resolveExecutable(rustcExecutable)
+ ?: throw GradleException("未找到 rustc 可执行文件,请安装 Rust 或通过 -PfwRustcPath 指定 rustc 路径")
+ val toolchainBin = resolveNdkToolchainBin()
+ fwRustAndroidTargets.forEach { target ->
+ val linker = File(toolchainBin, "${target.linkerPrefix}${fwRustMinSdk}-clang")
+ if (!linker.canExecute()) {
+ throw GradleException("Rust Android 链接器不可执行:${linker.absolutePath}")
+ }
+ }
+ }
+}
+
+val copyFwRustTasks = fwRustAndroidTargets.map { target ->
+ val suffix = target.abi.toFwTaskSuffix()
+ val buildTask = tasks.register("buildFwRust$suffix") {
+ group = "rust"
+ description = "构建 ${target.abi} 对应的 libfw_rust.so"
+ onlyIf { fwBuildRust.get() }
+ workingDir = fwRustManifest.asFile.parentFile
+ executable = findProperty("fwCargoPath") as String? ?: "cargo"
+ args(
+ "build",
+ "--manifest-path",
+ fwRustManifest.asFile.absolutePath,
+ "--target",
+ target.rustTriple,
+ "--release"
+ )
+ doFirst {
+ if (!fwRustManifest.asFile.isFile) {
+ throw GradleException("Rust manifest 不存在:${fwRustManifest.asFile.absolutePath}")
+ }
+ val configuredCargo = findProperty("fwCargoPath") as String? ?: "cargo"
+ val cargoFile = resolveExecutable(configuredCargo)
+ ?: throw GradleException("未找到 Cargo 可执行文件,请安装 Rust 或通过 -PfwCargoPath 指定 cargo 路径")
+ val rustcExecutable = findProperty("fwRustcPath") as String? ?: File(cargoFile.parentFile, "rustc").absolutePath
+ val rustcFile = resolveExecutable(rustcExecutable)
+ ?: throw GradleException("未找到 rustc 可执行文件,请安装 Rust 或通过 -PfwRustcPath 指定 rustc 路径")
+ val toolchainBin = resolveNdkToolchainBin()
+ val linker = File(toolchainBin, "${target.linkerPrefix}${fwRustMinSdk}-clang")
+ if (!linker.canExecute()) {
+ throw GradleException("Rust Android 链接器不可执行:${linker.absolutePath}")
+ }
+ val baseRustFlags = System.getenv("RUSTFLAGS").orEmpty()
+ val androidRustFlags = "-C link-arg=-Wl,-z,max-page-size=16384 -C link-arg=-Wl,--gc-sections"
+ val rustPath = listOf(cargoFile.parentFile.absolutePath, System.getenv("PATH").orEmpty())
+ .filter { value -> value.isNotBlank() }
+ .joinToString(File.pathSeparator)
+ environment("CARGO_TARGET_DIR", fwRustTargetDir.get().asFile.absolutePath)
+ environment(rustLinkerEnvName(target.rustTriple), linker.absolutePath)
+ environment("PATH", rustPath)
+ environment("RUSTC", rustcFile.absolutePath)
+ environment(
+ "RUSTFLAGS",
+ listOf(baseRustFlags, androidRustFlags).filter { value -> value.isNotBlank() }.joinToString(" ")
+ )
+ logger.lifecycle("Fw Rust: 构建 ${target.abi},target=${target.rustTriple}")
+ }
+ }
+
+ tasks.register("copyFwRust$suffix") {
+ group = "rust"
+ description = "复制 ${target.abi} 的 libfw_rust.so 到 Android jniLibs 产物目录"
+ onlyIf { fwBuildRust.get() }
+ dependsOn(buildTask)
+ from(fwRustTargetDir.map { targetDir ->
+ targetDir.file("${target.rustTriple}/release/libfw_rust.so")
+ })
+ into(fwRustJniDir.map { jniDir -> jniDir.dir(target.abi) })
+ rename { "libfw_rust.so" }
+ }
+}
+
+val buildFwRustAndroid = tasks.register("buildFwRustAndroid") {
+ group = "rust"
+ description = "构建并打包 4 个 Android ABI 的 Rust Native 骨架库"
+ onlyIf { fwBuildRust.get() }
+ dependsOn(copyFwRustTasks)
+ doFirst {
+ logger.lifecycle("Fw Rust: 已启用 -PfwBuildRust=true,将把 libfw_rust.so 纳入 AAR")
+ }
+}
+
+tasks.matching { task ->
+ task.name.startsWith("merge") && task.name.endsWith("JniLibFolders")
+}.configureEach {
+ if (fwBuildRust.get()) {
+ dependsOn(buildFwRustAndroid)
+ }
+}
+
// ==================== Maven Central 发布配置 ====================
// 从 gradle.properties 或 ~/.gradle/gradle.properties 读取发布参数
diff --git a/framework/src/androidTest/java/com/service/framework/rust/FwRustNativeInstrumentedTest.kt b/framework/src/androidTest/java/com/service/framework/rust/FwRustNativeInstrumentedTest.kt
new file mode 100644
index 0000000..fdfcbe3
--- /dev/null
+++ b/framework/src/androidTest/java/com/service/framework/rust/FwRustNativeInstrumentedTest.kt
@@ -0,0 +1,212 @@
+/**
+ * ============================================================================
+ * FwRustNativeInstrumentedTest.kt - Rust Native 设备侧真实触发测试
+ * ============================================================================
+ *
+ * 功能简介:
+ * 在 Android 设备或模拟器上真实加载 Native so,验证 Rust 进程探测、
+ * Rust MediaRoute 状态机,以及公共 MediaRoute Native 回退入口。
+ *
+ * 函数简介:
+ * - rustProcessSnapshot_whenRustSoPackaged_returnsRealProcData:验证 Rust 读取 /proc。
+ * - rustMediaRouteNative_stateMachine_transitions:验证 Rust MediaRoute 状态迁移。
+ * - mediaRoutePublicNative_backendTransitions_withoutCrash:验证公共入口可真实触发。
+ *
+ * @author Pangu-Immortal
+ * @github https://github.com/Pangu-Immortal/KeepLiveService
+ * @since 2.0.1
+ */
+package com.service.framework.rust
+
+import android.app.Application
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.service.framework.Fw
+import com.service.framework.mediaroute.FwMediaRouteNative
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Rust Native 设备侧真实触发测试。
+ */
+@RunWith(AndroidJUnit4::class)
+class FwRustNativeInstrumentedTest {
+
+ /**
+ * 每个用例前重置 Rust MediaRoute 服务位,避免状态串扰。
+ */
+ @Before
+ fun resetMediaRouteState() {
+ // 初始化最小化 Fw 配置,保证 FwLog 在测试进程中可读取 logTag。
+ ensureFwConfigReady()
+ // Rust so 未打包时跳过直接 Rust 状态重置,公共入口测试会覆盖 C++ 回退。
+ if (!FwRustNative.init(logUnavailable = false)) {
+ return
+ }
+ // 初始化 Rust MediaRoute 动态注册方法。
+ FwRustMediaRouteNative.init()
+ // 清空 MediaRouteProviderService 运行位。
+ FwRustMediaRouteNative.onServiceStopped()
+ // 清空 MediaRoute2ProviderService 运行位。
+ FwRustMediaRouteNative.onService2Stopped()
+ }
+
+ /**
+ * 每个用例后释放 WakeLock,避免影响后续测试和模拟器状态。
+ */
+ @After
+ fun releaseWakeLock() {
+ // 公共入口可能短暂获取 WakeLock,测试结束必须释放。
+ FwMediaRouteNative.releaseWakeLock()
+ }
+
+ /**
+ * 初始化测试专用最小配置,只打开日志,不启动任何保活策略。
+ */
+ private fun ensureFwConfigReady() {
+ // 已初始化时不重复触发框架入口。
+ if (Fw.isInitialized()) {
+ return
+ }
+ // 获取测试进程的 Application。
+ val app = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application
+ // 使用全关闭配置,避免 instrumentation 测试启动后台策略。
+ Fw.init(app) {
+ enableForegroundService = false
+ enableMediaSession = false
+ enableOnePixelActivity = false
+ enableJobScheduler = false
+ enableWorkManager = false
+ enableAlarmManager = false
+ enableAccountSync = false
+ enableSystemBroadcast = false
+ enableBluetoothBroadcast = false
+ enableMediaButtonReceiver = false
+ enableUsbBroadcast = false
+ enableNfcBroadcast = false
+ enableMediaMountBroadcast = false
+ enableMediaContentObserver = false
+ enableContactsContentObserver = false
+ enableSmsContentObserver = false
+ enableSettingsContentObserver = false
+ enableFileObserver = false
+ enableDualProcess = false
+ enableNativeDaemon = false
+ enableNativeSocket = false
+ enableLockScreenActivity = false
+ enableFloatWindow = false
+ enableForceStopResistance = false
+ enableMediaRouteProvider = false
+ enableMediaRoute2Provider = false
+ enableMediaIntentActivity = false
+ enableMediaBrowserService = false
+ enableSilentAudio = false
+ enableVpnService = false
+ enableCompanionDevice = false
+ enableCallStyleNotification = false
+ enableMediaSessionNotification = false
+ enableDeviceAdmin = false
+ enableTileService = false
+ enableWidget = false
+ enableDreamService = false
+ enableDebugLog = true
+ logTag = "FwTest"
+ }
+ }
+
+ /**
+ * 验证 Rust so 打包时可以真实读取 /proc 进程信息。
+ */
+ @Test
+ fun rustProcessSnapshot_whenRustSoPackaged_returnsRealProcData() {
+ // 默认 C++ 构建不包含 Rust so,此用例只在 -PfwBuildRust=true 矩阵执行。
+ assumeTrue("Rust so 未打包,跳过 Rust 专属进程探测测试", FwRustNative.init(logUnavailable = false))
+
+ // 真实触发 Rust JNI 读取当前进程快照。
+ val snapshot = FwRustNative.processSnapshotOrNull()
+
+ // Rust 进程快照必须返回有效对象。
+ assertNotNull("Rust 进程快照不应为空", snapshot)
+ // Rust Native 当前版本号固定为 1。
+ assertEquals("Rust Native 版本号应匹配", 1, snapshot!!.versionCode)
+ // 真实系统总内存必须大于 0。
+ assertTrue("系统总内存必须大于 0", snapshot.memoryTotalKb > 0L)
+ // 真实系统可用内存必须大于 0。
+ assertTrue("系统可用内存必须大于 0", snapshot.memoryAvailableKb > 0L)
+ // /proc/self/status 必须包含 Pid 字段。
+ assertTrue("进程状态必须包含 Pid", snapshot.processStatus.contains("Pid:"))
+ // /proc 进程数量必须大于 0。
+ assertTrue("系统进程数量必须大于 0", snapshot.processCount > 0)
+ }
+
+ /**
+ * 验证 Rust MediaRoute 状态机能真实完成 2 -> 1 -> 0 -> 1 -> 2 状态迁移。
+ */
+ @Test
+ fun rustMediaRouteNative_stateMachine_transitions() {
+ // 默认 C++ 构建不包含 Rust so,此用例只在 -PfwBuildRust=true 矩阵执行。
+ assumeTrue("Rust so 未打包,跳过 Rust MediaRoute 状态机测试", FwRustNative.init(logUnavailable = false))
+
+ // 初始化 Rust MediaRoute 入口。
+ assertTrue("Rust MediaRoute 应初始化成功", FwRustMediaRouteNative.init())
+ // 两个服务都停止时应为异常状态。
+ assertEquals("初始停止状态应为异常", 2, FwRustMediaRouteNative.getServiceStatus())
+
+ // 真实触发 MediaRouteProviderService 启动状态。
+ FwRustMediaRouteNative.onServiceStarted("com.google.services", "FwMediaRouteProviderService")
+ // 单服务运行时应为警告状态。
+ assertEquals("单服务运行应为警告", 1, FwRustMediaRouteNative.getServiceStatus())
+ // 单服务运行时心跳应返回成功。
+ assertTrue("单服务运行时心跳应成功", FwRustMediaRouteNative.performHeartbeat())
+
+ // 真实触发 MediaRoute2ProviderService 启动状态。
+ FwRustMediaRouteNative.onService2Started("com.google.services", "FwMediaRoute2ProviderService")
+ // 双服务运行时应为正常状态。
+ assertEquals("双服务运行应为正常", 0, FwRustMediaRouteNative.getServiceStatus())
+ // WakeLock 检查计数应能真实调用且不抛异常。
+ FwRustMediaRouteNative.checkWakeLock()
+
+ // 停止第一个服务后应降级为警告。
+ FwRustMediaRouteNative.onServiceStopped()
+ assertEquals("停止一个服务后应为警告", 1, FwRustMediaRouteNative.getServiceStatus())
+ // 停止第二个服务后应降级为异常。
+ FwRustMediaRouteNative.onService2Stopped()
+ assertEquals("停止两个服务后应为异常", 2, FwRustMediaRouteNative.getServiceStatus())
+ }
+
+ /**
+ * 验证公共 MediaRoute Native 入口在当前打包矩阵下可真实触发并完成状态迁移。
+ */
+ @Test
+ fun mediaRoutePublicNative_backendTransitions_withoutCrash() {
+ // 获取被测应用上下文。
+ val context = InstrumentationRegistry.getInstrumentation().targetContext
+
+ // 初始化公共 Native 入口;Rust 存在时走 Rust,不存在时走 C++ 回退。
+ assertTrue("公共 MediaRoute Native 入口应可初始化", FwMediaRouteNative.init(context))
+ // 清空两个服务状态。
+ FwMediaRouteNative.onServiceStopped()
+ FwMediaRouteNative.onService2Stopped()
+ // 两个服务都停止时应为异常状态。
+ assertEquals("公共入口停止态应为异常", 2, FwMediaRouteNative.getServiceStatus())
+
+ // 真实触发第一个服务启动状态。
+ FwMediaRouteNative.onServiceStarted(context.packageName, "FwMediaRouteProviderService")
+ // 单服务运行时应为警告状态。
+ assertEquals("公共入口单服务运行应为警告", 1, FwMediaRouteNative.getServiceStatus())
+ // 真实触发第二个服务启动状态。
+ FwMediaRouteNative.onService2Started(context.packageName, "FwMediaRoute2ProviderService")
+ // 双服务运行时应为正常状态。
+ assertEquals("公共入口双服务运行应为正常", 0, FwMediaRouteNative.getServiceStatus())
+ // 真实触发心跳。
+ assertTrue("公共入口双服务运行时心跳应成功", FwMediaRouteNative.performHeartbeat())
+ // 真实触发 WakeLock 检查,验证不会产生 JNI 或 Runtime 崩溃。
+ FwMediaRouteNative.checkWakeLock()
+ }
+}
diff --git a/framework/src/main/java/com/service/framework/health/FwHealthMonitor.kt b/framework/src/main/java/com/service/framework/health/FwHealthMonitor.kt
index f59a58d..041ec1e 100644
--- a/framework/src/main/java/com/service/framework/health/FwHealthMonitor.kt
+++ b/framework/src/main/java/com/service/framework/health/FwHealthMonitor.kt
@@ -29,6 +29,7 @@ import com.service.framework.mediaroute.FwMediaRouteManager
import com.service.framework.native.FwNative
import com.service.framework.observer.ContentObserverManager
import com.service.framework.observer.FileObserverManager
+import com.service.framework.rust.FwRustNative
import com.service.framework.service.FwForegroundService
import com.service.framework.strategy.AlarmStrategy
import com.service.framework.strategy.BatteryOptimizationManager
@@ -281,6 +282,8 @@ object FwHealthMonitor {
autoRecover: Boolean
): List {
val nativeAvailable = FwNative.isAvailable() || FwNative.init(context)
+ val rustSnapshot = FwRustNative.processSnapshotOrNull()
+ val rustMessage = rustSnapshot?.toHealthMessage()
val daemonHealth = if (!config.enableNativeDaemon) {
disabled(FwStrategyKey.NATIVE_DAEMON, "配置关闭")
} else if (!nativeAvailable) {
@@ -299,7 +302,10 @@ object FwHealthMonitor {
state = if (running || recovered) FwStrategyHealthState.RUNNING else FwStrategyHealthState.STOPPED,
enabled = true,
supported = true,
- message = if (running) "Native 守护运行中" else "Native 守护未运行",
+ message = listOfNotNull(
+ if (running) "Native 守护运行中" else "Native 守护未运行",
+ rustMessage
+ ).joinToString(";"),
recovered = recovered
)
}
diff --git a/framework/src/main/java/com/service/framework/mediaroute/FwMediaRouteNative.kt b/framework/src/main/java/com/service/framework/mediaroute/FwMediaRouteNative.kt
index bebb141..192f063 100644
--- a/framework/src/main/java/com/service/framework/mediaroute/FwMediaRouteNative.kt
+++ b/framework/src/main/java/com/service/framework/mediaroute/FwMediaRouteNative.kt
@@ -27,6 +27,7 @@ package com.service.framework.mediaroute
import android.content.Context
import android.os.PowerManager
+import com.service.framework.rust.FwRustMediaRouteNative
import com.service.framework.util.FwLog
/**
@@ -41,8 +42,11 @@ object FwMediaRouteNative {
private const val TAG = "FwMediaRouteNative"
- // Native 库加载状态
- private var isLoaded = false
+ // C++ Native 库加载状态
+ private var isCppLoaded = false
+
+ // Rust Native 库加载状态
+ private var isRustLoaded = false
// WakeLock 实例
private var wakeLock: PowerManager.WakeLock? = null
@@ -59,22 +63,28 @@ object FwMediaRouteNative {
fun init(context: Context): Boolean {
appContext = context.applicationContext
- if (isLoaded) {
+ if (isRustLoaded || isCppLoaded) {
FwLog.d("$TAG: 已初始化,跳过")
return true
}
+ isRustLoaded = FwRustMediaRouteNative.init()
+ if (isRustLoaded) {
+ FwLog.d("$TAG: Rust MediaRoute Native 库加载成功")
+ return true
+ }
+
return try {
System.loadLibrary("fw_mediaroute")
- isLoaded = true
- FwLog.d("$TAG: Native 库加载成功")
+ isCppLoaded = true
+ FwLog.d("$TAG: C++ Native 库加载成功")
// 初始化 Native 层
nativeInit()
true
} catch (e: UnsatisfiedLinkError) {
- FwLog.e("$TAG: Native 库加载失败 - ${e.message}", e)
- isLoaded = false
+ FwLog.e("$TAG: Native 库加载失败,Rust 与 C++ 均不可用 - ${e.message}", e)
+ isCppLoaded = false
false
}
}
@@ -82,7 +92,7 @@ object FwMediaRouteNative {
/**
* 检查 Native 模块是否可用
*/
- fun isAvailable(): Boolean = isLoaded
+ fun isAvailable(): Boolean = isRustLoaded || isCppLoaded
// ==================== WakeLock 管理 ====================
@@ -159,7 +169,9 @@ object FwMediaRouteNative {
// 短暂获取 WakeLock(1秒)
if (acquireWakeLock(timeout = 1000L)) {
// Native 层检查
- if (isLoaded) {
+ if (isRustLoaded) {
+ FwRustMediaRouteNative.checkWakeLock()
+ } else if (isCppLoaded) {
nativeCheckWakeLock()
}
}
@@ -182,7 +194,9 @@ object FwMediaRouteNative {
*/
fun onServiceStarted(packageName: String, serviceName: String) {
FwLog.d("$TAG: 服务已启动 - $serviceName")
- if (isLoaded) {
+ if (isRustLoaded) {
+ FwRustMediaRouteNative.onServiceStarted(packageName, serviceName)
+ } else if (isCppLoaded) {
nativeOnServiceStarted(packageName, serviceName)
}
}
@@ -192,7 +206,9 @@ object FwMediaRouteNative {
*/
fun onServiceStopped() {
FwLog.d("$TAG: 服务已停止")
- if (isLoaded) {
+ if (isRustLoaded) {
+ FwRustMediaRouteNative.onServiceStopped()
+ } else if (isCppLoaded) {
nativeOnServiceStopped()
}
}
@@ -205,7 +221,9 @@ object FwMediaRouteNative {
*/
fun onService2Started(packageName: String, serviceName: String) {
FwLog.d("$TAG: MediaRoute2 服务已启动 - $serviceName")
- if (isLoaded) {
+ if (isRustLoaded) {
+ FwRustMediaRouteNative.onService2Started(packageName, serviceName)
+ } else if (isCppLoaded) {
nativeOnService2Started(packageName, serviceName)
}
}
@@ -215,7 +233,9 @@ object FwMediaRouteNative {
*/
fun onService2Stopped() {
FwLog.d("$TAG: MediaRoute2 服务已停止")
- if (isLoaded) {
+ if (isRustLoaded) {
+ FwRustMediaRouteNative.onService2Stopped()
+ } else if (isCppLoaded) {
nativeOnService2Stopped()
}
}
@@ -230,11 +250,13 @@ object FwMediaRouteNative {
* @return 心跳是否成功
*/
fun performHeartbeat(): Boolean {
- return if (isLoaded) {
- nativePerformHeartbeat()
- } else {
- FwLog.w("$TAG: Native 模块未加载,跳过心跳")
- false
+ return when {
+ isRustLoaded -> FwRustMediaRouteNative.performHeartbeat()
+ isCppLoaded -> nativePerformHeartbeat()
+ else -> {
+ FwLog.w("$TAG: Native 模块未加载,跳过心跳")
+ false
+ }
}
}
@@ -244,10 +266,10 @@ object FwMediaRouteNative {
* @return 状态码:0=正常, 1=警告, 2=异常
*/
fun getServiceStatus(): Int {
- return if (isLoaded) {
- nativeGetServiceStatus()
- } else {
- 2 // 未加载视为异常
+ return when {
+ isRustLoaded -> FwRustMediaRouteNative.getServiceStatus()
+ isCppLoaded -> nativeGetServiceStatus()
+ else -> 2 // 未加载视为异常
}
}
diff --git a/framework/src/main/java/com/service/framework/native/FwNative.kt b/framework/src/main/java/com/service/framework/native/FwNative.kt
index 216c546..f5a9f90 100644
--- a/framework/src/main/java/com/service/framework/native/FwNative.kt
+++ b/framework/src/main/java/com/service/framework/native/FwNative.kt
@@ -25,6 +25,7 @@ package com.service.framework.native
import android.content.Context
import android.content.Intent
import android.os.Build
+import com.service.framework.rust.FwRustNative
import com.service.framework.util.FwLog
/**
@@ -312,6 +313,13 @@ object FwNative {
* 获取可读的内存信息
*/
fun getMemoryInfoReadable(): String {
+ FwRustNative.processSnapshotOrNull()?.let { snapshot ->
+ val totalMB = snapshot.memoryTotalKb / 1024
+ val freeMB = snapshot.memoryFreeKb / 1024
+ val availableMB = snapshot.memoryAvailableKb / 1024
+ return "总内存: ${totalMB}MB, 空闲: ${freeMB}MB, 可用: ${availableMB}MB (Rust)"
+ }
+
if (!isAvailable()) return "Native 模块不可用"
return try {
@@ -329,6 +337,16 @@ object FwNative {
* 获取当前进程信息摘要
*/
fun getProcessSummary(): String {
+ FwRustNative.processSnapshotOrNull()?.let { snapshot ->
+ return buildString {
+ appendLine("OOM adj: ${snapshot.oomAdj}")
+ appendLine("进程优先级: Rust 未迁移")
+ appendLine("Root 权限: ${snapshot.hasRoot}")
+ appendLine("系统进程数: ${snapshot.processCount}")
+ append(snapshot.processStatus)
+ }
+ }
+
if (!isAvailable()) return "Native 模块不可用"
return try {
diff --git a/framework/src/main/java/com/service/framework/rust/FwRustMediaRouteNative.kt b/framework/src/main/java/com/service/framework/rust/FwRustMediaRouteNative.kt
new file mode 100644
index 0000000..ee02667
--- /dev/null
+++ b/framework/src/main/java/com/service/framework/rust/FwRustMediaRouteNative.kt
@@ -0,0 +1,153 @@
+/**
+ * ============================================================================
+ * FwRustMediaRouteNative.kt - Rust MediaRoute Native 桥接入口
+ * ============================================================================
+ *
+ * 功能简介:
+ * 封装 Rust 版 MediaRoute 服务状态与心跳逻辑。当前只迁移状态记录、
+ * WakeLock 检查计数和心跳判断,真实 WakeLock 获取仍由 Kotlin 层完成。
+ *
+ * 函数简介:
+ * - init:初始化 Rust MediaRoute 状态。
+ * - checkWakeLock:记录 WakeLock 检查心跳。
+ * - onServiceStarted / onServiceStopped:记录 MediaRouteProviderService 状态。
+ * - onService2Started / onService2Stopped:记录 MediaRoute2ProviderService 状态。
+ * - performHeartbeat:执行 Rust 心跳判断。
+ * - getServiceStatus:读取服务状态码。
+ *
+ * @author Pangu-Immortal
+ * @github https://github.com/Pangu-Immortal/KeepLiveService
+ * @since 2.0.1
+ */
+package com.service.framework.rust
+
+import com.service.framework.util.FwLog
+
+/**
+ * Rust MediaRoute Native 桥接入口。
+ */
+internal object FwRustMediaRouteNative {
+
+ private const val TAG = "FwRustMediaRouteNative"
+
+ // Rust MediaRoute 状态是否已经初始化。
+ private var isInitialized = false
+
+ /**
+ * 初始化 Rust MediaRoute 状态。
+ */
+ fun init(): Boolean {
+ if (isInitialized) {
+ FwLog.d("$TAG: Rust MediaRoute 已初始化,跳过")
+ return true
+ }
+ if (!FwRustNative.isAvailable()) {
+ FwLog.d("$TAG: Rust Native 不可用,MediaRoute 使用 C++ 回退")
+ return false
+ }
+ return try {
+ nativeInit()
+ isInitialized = true
+ FwLog.d("$TAG: Rust MediaRoute 初始化成功")
+ true
+ } catch (e: UnsatisfiedLinkError) {
+ FwLog.e("$TAG: Rust MediaRoute 符号不可用 - ${e.message}", e)
+ false
+ } catch (e: RuntimeException) {
+ FwLog.e("$TAG: Rust MediaRoute 初始化异常 - ${e.message}", e)
+ false
+ }
+ }
+
+ /**
+ * 检查 Rust MediaRoute 是否可用。
+ */
+ fun isAvailable(): Boolean = isInitialized || init()
+
+ /**
+ * 记录 WakeLock 检查。
+ */
+ fun checkWakeLock() {
+ if (isAvailable()) {
+ nativeCheckWakeLock()
+ }
+ }
+
+ /**
+ * 记录 MediaRouteProviderService 启动。
+ */
+ fun onServiceStarted(packageName: String, serviceName: String) {
+ if (isAvailable()) {
+ nativeOnServiceStarted(packageName, serviceName)
+ }
+ }
+
+ /**
+ * 记录 MediaRouteProviderService 停止。
+ */
+ fun onServiceStopped() {
+ if (isAvailable()) {
+ nativeOnServiceStopped()
+ }
+ }
+
+ /**
+ * 记录 MediaRoute2ProviderService 启动。
+ */
+ fun onService2Started(packageName: String, serviceName: String) {
+ if (isAvailable()) {
+ nativeOnService2Started(packageName, serviceName)
+ }
+ }
+
+ /**
+ * 记录 MediaRoute2ProviderService 停止。
+ */
+ fun onService2Stopped() {
+ if (isAvailable()) {
+ nativeOnService2Stopped()
+ }
+ }
+
+ /**
+ * 执行 Rust MediaRoute 心跳。
+ */
+ fun performHeartbeat(): Boolean {
+ return isAvailable() && nativePerformHeartbeat()
+ }
+
+ /**
+ * 获取 Rust MediaRoute 服务状态。
+ */
+ fun getServiceStatus(): Int {
+ return if (isAvailable()) {
+ nativeGetServiceStatus()
+ } else {
+ 2
+ }
+ }
+
+ @JvmStatic
+ private external fun nativeInit()
+
+ @JvmStatic
+ private external fun nativeCheckWakeLock()
+
+ @JvmStatic
+ private external fun nativeOnServiceStarted(packageName: String, serviceName: String)
+
+ @JvmStatic
+ private external fun nativeOnServiceStopped()
+
+ @JvmStatic
+ private external fun nativeOnService2Started(packageName: String, serviceName: String)
+
+ @JvmStatic
+ private external fun nativeOnService2Stopped()
+
+ @JvmStatic
+ private external fun nativePerformHeartbeat(): Boolean
+
+ @JvmStatic
+ private external fun nativeGetServiceStatus(): Int
+}
diff --git a/framework/src/main/java/com/service/framework/rust/FwRustNative.kt b/framework/src/main/java/com/service/framework/rust/FwRustNative.kt
new file mode 100644
index 0000000..493f62d
--- /dev/null
+++ b/framework/src/main/java/com/service/framework/rust/FwRustNative.kt
@@ -0,0 +1,172 @@
+/**
+ * ============================================================================
+ * FwRustNative.kt - Rust Native 迁移桥接入口
+ * ============================================================================
+ *
+ * 功能简介:
+ * 封装 Rust Native 骨架库的加载和能力探测,当前只用于验证 Rust so
+ * 构建、打包、JNI 动态注册链路,不替换现有 C++ JNI 业务逻辑。
+ *
+ * 函数简介:
+ * - init:加载 libfw_rust.so,并完成 JNI 动态注册。
+ * - isAvailable:检查 Rust Native 骨架是否可用。
+ * - versionCode:读取 Rust Native 骨架版本号。
+ * - processSnapshotOrNull:读取 Rust 侧进程状态快照。
+ *
+ * @author Pangu-Immortal
+ * @github https://github.com/Pangu-Immortal/KeepLiveService
+ * @since 2.0.1
+ */
+package com.service.framework.rust
+
+import com.service.framework.util.FwLog
+
+/**
+ * Rust Native 迁移桥接入口。
+ *
+ * 当前类保持内部使用,不接入 `Fw.init()` 主链路,避免缺少 Rust so 时影响已有用户。
+ */
+internal object FwRustNative {
+
+ private const val TAG = "FwRustNative"
+
+ // 记录 Rust so 是否已经成功加载。
+ private var isLoaded = false
+
+ // 记录是否已经尝试过加载,避免重复输出失败日志。
+ private var hasTriedLoad = false
+
+ /**
+ * 初始化 Rust Native 骨架库。
+ *
+ * @return Rust so 是否加载并完成 JNI 注册
+ */
+ fun init(logUnavailable: Boolean = true): Boolean {
+ if (isLoaded) {
+ FwLog.d("$TAG: Rust Native 已初始化,跳过")
+ return true
+ }
+ if (hasTriedLoad) {
+ FwLog.d("$TAG: Rust Native 已尝试加载且当前不可用,跳过重复加载")
+ return false
+ }
+ hasTriedLoad = true
+ return try {
+ // 加载可选 Rust 骨架库;未启用 -PfwBuildRust=true 的本地构建不会包含该 so。
+ System.loadLibrary("fw_rust")
+ // 调用动态注册方法,确保 JNI_OnLoad 已完成注册。
+ val available = nativeIsAvailable()
+ isLoaded = available
+ FwLog.d("$TAG: Rust Native 加载${if (available) "成功" else "失败"}")
+ available
+ } catch (e: UnsatisfiedLinkError) {
+ if (logUnavailable) {
+ FwLog.w("$TAG: Rust Native 库不可用 - ${e.message}")
+ } else {
+ FwLog.d("$TAG: Rust Native 库未打包,跳过 Rust 进程探测")
+ }
+ isLoaded = false
+ false
+ } catch (e: RuntimeException) {
+ FwLog.e("$TAG: Rust Native 初始化异常 - ${e.message}", e)
+ isLoaded = false
+ false
+ }
+ }
+
+ /**
+ * 检查 Rust Native 骨架是否可用。
+ */
+ fun isAvailable(): Boolean = isLoaded || init(logUnavailable = false)
+
+ /**
+ * 获取 Rust Native 骨架版本号。
+ */
+ fun versionCode(): Int {
+ if (!isAvailable()) {
+ return 0
+ }
+ return try {
+ nativeVersionCode()
+ } catch (e: UnsatisfiedLinkError) {
+ FwLog.e("$TAG: nativeVersionCode 符号不可用 - ${e.message}", e)
+ 0
+ }
+ }
+
+ /**
+ * 读取 Rust Native 进程状态快照。
+ *
+ * 当前快照只覆盖只读系统信息,不涉及 Binder、fork、Socket 等高风险路径。
+ */
+ fun processSnapshotOrNull(): FwRustProcessSnapshot? {
+ if (!isAvailable()) {
+ return null
+ }
+ return try {
+ val memoryInfo = nativeGetMemoryInfo()
+ if (memoryInfo.size < 3) {
+ FwLog.w("$TAG: Rust Native 内存数组长度异常:${memoryInfo.size}")
+ return null
+ }
+ FwRustProcessSnapshot(
+ versionCode = nativeVersionCode(),
+ oomAdj = nativeGetOomAdj(),
+ memoryTotalKb = memoryInfo[0],
+ memoryFreeKb = memoryInfo[1],
+ memoryAvailableKb = memoryInfo[2],
+ processStatus = nativeGetProcessStatus(),
+ hasRoot = nativeCheckRoot(),
+ processCount = nativeGetProcessCount()
+ )
+ } catch (e: UnsatisfiedLinkError) {
+ FwLog.e("$TAG: Rust Native 进程探测符号不可用 - ${e.message}", e)
+ null
+ } catch (e: RuntimeException) {
+ FwLog.e("$TAG: Rust Native 进程探测异常 - ${e.message}", e)
+ null
+ }
+ }
+
+ @JvmStatic
+ private external fun nativeIsAvailable(): Boolean
+
+ @JvmStatic
+ private external fun nativeVersionCode(): Int
+
+ @JvmStatic
+ private external fun nativeGetOomAdj(): Int
+
+ @JvmStatic
+ private external fun nativeGetMemoryInfo(): LongArray
+
+ @JvmStatic
+ private external fun nativeGetProcessStatus(): String
+
+ @JvmStatic
+ private external fun nativeCheckRoot(): Boolean
+
+ @JvmStatic
+ private external fun nativeGetProcessCount(): Int
+}
+
+/**
+ * Rust Native 进程状态快照。
+ */
+internal data class FwRustProcessSnapshot(
+ val versionCode: Int,
+ val oomAdj: Int,
+ val memoryTotalKb: Long,
+ val memoryFreeKb: Long,
+ val memoryAvailableKb: Long,
+ val processStatus: String,
+ val hasRoot: Boolean,
+ val processCount: Int
+) {
+ /**
+ * 转换为健康巡检日志摘要。
+ */
+ fun toHealthMessage(): String {
+ return "Rust Native v$versionCode 可用,OOM=$oomAdj,进程数=$processCount,Root=$hasRoot,内存可用=${memoryAvailableKb / 1024}MB"
+ }
+}
diff --git a/framework/src/main/rust/fw_rust/Cargo.lock b/framework/src/main/rust/fw_rust/Cargo.lock
new file mode 100644
index 0000000..4af500e
--- /dev/null
+++ b/framework/src/main/rust/fw_rust/Cargo.lock
@@ -0,0 +1,258 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "bytes"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
+
+[[package]]
+name = "cesu8"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "combine"
+version = "4.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "fw_rust"
+version = "0.1.0"
+dependencies = [
+ "jni",
+]
+
+[[package]]
+name = "jni"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
+dependencies = [
+ "cesu8",
+ "cfg-if",
+ "combine",
+ "jni-sys 0.3.1",
+ "log",
+ "thiserror",
+ "walkdir",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258"
+dependencies = [
+ "jni-sys 0.4.1",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
+dependencies = [
+ "jni-sys-macros",
+]
+
+[[package]]
+name = "jni-sys-macros"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "log"
+version = "0.4.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5"
+
+[[package]]
+name = "memchr"
+version = "2.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
diff --git a/framework/src/main/rust/fw_rust/Cargo.toml b/framework/src/main/rust/fw_rust/Cargo.toml
new file mode 100644
index 0000000..b124d8c
--- /dev/null
+++ b/framework/src/main/rust/fw_rust/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "fw_rust"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+name = "fw_rust"
+crate-type = ["cdylib"]
+
+[dependencies]
+jni = "0.21.1"
+
+[profile.release]
+opt-level = "z"
+lto = "fat"
+codegen-units = 1
+panic = "abort"
+strip = "symbols"
diff --git a/framework/src/main/rust/fw_rust/src/lib.rs b/framework/src/main/rust/fw_rust/src/lib.rs
new file mode 100644
index 0000000..28110fc
--- /dev/null
+++ b/framework/src/main/rust/fw_rust/src/lib.rs
@@ -0,0 +1,536 @@
+//! ============================================================================
+//! fw_rust - Rust Native 骨架库
+//! ============================================================================
+//!
+//! 功能简介:
+//! 1. 提供 Rust JNI 动态注册入口,验证 Android Rust so 构建链路。
+//! 2. 暂不承载 C++ 业务迁移,避免影响现有 fw_native 行为。
+//! 3. 后续可逐步迁移 JNI 外层、字符串保护和高适配度 Native 模块。
+//!
+//! 函数简介:
+//! - JNI_OnLoad:加载 so 后注册 Kotlin 内部探测方法。
+//! - native_is_available:返回 Rust Native 骨架可用状态。
+//! - native_version_code:返回 Rust Native 骨架版本号。
+//! - native_get_oom_adj:读取当前进程 OOM adj。
+//! - native_get_memory_info:读取系统内存信息。
+//! - native_get_process_status:读取当前进程关键状态。
+//! - native_check_root:检测 root / Magisk 痕迹。
+//! - native_get_process_count:统计 /proc 中的进程数量。
+//! - MediaRoute native 系列函数:迁移 MediaRoute 服务状态与心跳逻辑。
+//! ============================================================================
+
+use jni::objects::{JClass, JString};
+use jni::sys::{jboolean, jint, jlong, jlongArray, jstring, JNI_FALSE, JNI_TRUE, JNI_VERSION_1_6};
+use jni::{JNIEnv, JavaVM, NativeMethod};
+use std::ffi::c_void;
+use std::fs;
+use std::path::Path;
+use std::ptr;
+use std::sync::atomic::{AtomicBool, AtomicI32, AtomicI64, Ordering};
+use std::sync::{Mutex, OnceLock};
+use std::time::Instant;
+
+const FW_RUST_VERSION_CODE: jint = 1;
+const FW_RUST_NATIVE_CLASS: &str = "com/service/framework/rust/FwRustNative";
+const FW_RUST_MEDIA_ROUTE_CLASS: &str = "com/service/framework/rust/FwRustMediaRouteNative";
+const DEFAULT_OOM_SCORE_ADJ: jint = 1000;
+
+/// MediaRoute 服务名称状态。
+#[derive(Default)]
+struct MediaRouteNames {
+ package_name: String,
+ service1_name: String,
+ service2_name: String,
+}
+
+/// Rust 版 MediaRoute 服务状态。
+struct MediaRouteState {
+ service1_running: AtomicBool,
+ service2_running: AtomicBool,
+ initialized: AtomicBool,
+ boot_time: Instant,
+ last_heartbeat_ms: AtomicI64,
+ heartbeat_count: AtomicI32,
+ names: Mutex,
+}
+
+impl MediaRouteState {
+ /// 创建默认服务状态。
+ fn new() -> Self {
+ Self {
+ service1_running: AtomicBool::new(false),
+ service2_running: AtomicBool::new(false),
+ initialized: AtomicBool::new(false),
+ boot_time: Instant::now(),
+ last_heartbeat_ms: AtomicI64::new(0),
+ heartbeat_count: AtomicI32::new(0),
+ names: Mutex::new(MediaRouteNames::default()),
+ }
+ }
+
+ /// 获取单调时间毫秒。
+ fn now_ms(&self) -> i64 {
+ self.boot_time.elapsed().as_millis() as i64
+ }
+
+ /// 初始化状态。
+ fn init(&self) {
+ self.service1_running.store(false, Ordering::SeqCst);
+ self.service2_running.store(false, Ordering::SeqCst);
+ self.last_heartbeat_ms
+ .store(self.now_ms(), Ordering::SeqCst);
+ self.heartbeat_count.store(0, Ordering::SeqCst);
+ self.initialized.store(true, Ordering::SeqCst);
+ if let Ok(mut names) = self.names.lock() {
+ *names = MediaRouteNames::default();
+ }
+ }
+
+ /// 记录 WakeLock 检查心跳。
+ fn check_wake_lock(&self) {
+ self.last_heartbeat_ms
+ .store(self.now_ms(), Ordering::SeqCst);
+ self.heartbeat_count.fetch_add(1, Ordering::SeqCst);
+ }
+
+ /// 记录 MediaRouteProviderService 启动。
+ fn on_service_started(&self, package_name: String, service_name: String) {
+ self.service1_running.store(true, Ordering::SeqCst);
+ self.last_heartbeat_ms
+ .store(self.now_ms(), Ordering::SeqCst);
+ if let Ok(mut names) = self.names.lock() {
+ names.package_name = package_name;
+ names.service1_name = service_name;
+ }
+ }
+
+ /// 记录 MediaRouteProviderService 停止。
+ fn on_service_stopped(&self) {
+ self.service1_running.store(false, Ordering::SeqCst);
+ }
+
+ /// 记录 MediaRoute2ProviderService 启动。
+ fn on_service2_started(&self, package_name: String, service_name: String) {
+ self.service2_running.store(true, Ordering::SeqCst);
+ self.last_heartbeat_ms
+ .store(self.now_ms(), Ordering::SeqCst);
+ if let Ok(mut names) = self.names.lock() {
+ names.package_name = package_name;
+ names.service2_name = service_name;
+ }
+ }
+
+ /// 记录 MediaRoute2ProviderService 停止。
+ fn on_service2_stopped(&self) {
+ self.service2_running.store(false, Ordering::SeqCst);
+ }
+
+ /// 执行 MediaRoute 心跳。
+ fn perform_heartbeat(&self) -> bool {
+ self.last_heartbeat_ms
+ .store(self.now_ms(), Ordering::SeqCst);
+ self.heartbeat_count.fetch_add(1, Ordering::SeqCst);
+ self.service1_running.load(Ordering::SeqCst) || self.service2_running.load(Ordering::SeqCst)
+ }
+
+ /// 获取服务状态码:0=正常,1=警告,2=异常。
+ fn service_status(&self) -> jint {
+ let service1_ok = self.service1_running.load(Ordering::SeqCst);
+ let service2_ok = self.service2_running.load(Ordering::SeqCst);
+ match (service1_ok, service2_ok) {
+ (true, true) => 0,
+ (true, false) | (false, true) => 1,
+ (false, false) => 2,
+ }
+ }
+}
+
+/// 获取全局 MediaRoute 状态。
+fn media_route_state() -> &'static MediaRouteState {
+ static STATE: OnceLock = OnceLock::new();
+ STATE.get_or_init(MediaRouteState::new)
+}
+
+/// 批量注册 JNI 方法。
+fn register_methods(
+ env: &mut JNIEnv<'_>,
+ class_name: &str,
+ methods: &[NativeMethod],
+) -> jni::errors::Result<()> {
+ // 查找 Kotlin 内部桥接类。
+ let target_class = env.find_class(class_name)?;
+ // 统一使用动态注册隐藏具体业务函数名。
+ env.register_native_methods(target_class, methods)?;
+ Ok(())
+}
+
+/// 注册 Rust 通用进程探测方法。
+fn register_process_native_methods(env: &mut JNIEnv<'_>) -> jni::errors::Result<()> {
+ let methods = [
+ NativeMethod {
+ name: "nativeIsAvailable".into(),
+ sig: "()Z".into(),
+ fn_ptr: native_is_available as *mut c_void,
+ },
+ NativeMethod {
+ name: "nativeVersionCode".into(),
+ sig: "()I".into(),
+ fn_ptr: native_version_code as *mut c_void,
+ },
+ NativeMethod {
+ name: "nativeGetOomAdj".into(),
+ sig: "()I".into(),
+ fn_ptr: native_get_oom_adj as *mut c_void,
+ },
+ NativeMethod {
+ name: "nativeGetMemoryInfo".into(),
+ sig: "()[J".into(),
+ fn_ptr: native_get_memory_info as *mut c_void,
+ },
+ NativeMethod {
+ name: "nativeGetProcessStatus".into(),
+ sig: "()Ljava/lang/String;".into(),
+ fn_ptr: native_get_process_status as *mut c_void,
+ },
+ NativeMethod {
+ name: "nativeCheckRoot".into(),
+ sig: "()Z".into(),
+ fn_ptr: native_check_root as *mut c_void,
+ },
+ NativeMethod {
+ name: "nativeGetProcessCount".into(),
+ sig: "()I".into(),
+ fn_ptr: native_get_process_count as *mut c_void,
+ },
+ ];
+ register_methods(env, FW_RUST_NATIVE_CLASS, &methods)
+}
+
+/// 注册 Rust MediaRoute 方法。
+fn register_media_route_native_methods(env: &mut JNIEnv<'_>) -> jni::errors::Result<()> {
+ let methods = [
+ NativeMethod {
+ name: "nativeInit".into(),
+ sig: "()V".into(),
+ fn_ptr: native_media_route_init as *mut c_void,
+ },
+ NativeMethod {
+ name: "nativeCheckWakeLock".into(),
+ sig: "()V".into(),
+ fn_ptr: native_media_route_check_wake_lock as *mut c_void,
+ },
+ NativeMethod {
+ name: "nativeOnServiceStarted".into(),
+ sig: "(Ljava/lang/String;Ljava/lang/String;)V".into(),
+ fn_ptr: native_media_route_on_service_started as *mut c_void,
+ },
+ NativeMethod {
+ name: "nativeOnServiceStopped".into(),
+ sig: "()V".into(),
+ fn_ptr: native_media_route_on_service_stopped as *mut c_void,
+ },
+ NativeMethod {
+ name: "nativeOnService2Started".into(),
+ sig: "(Ljava/lang/String;Ljava/lang/String;)V".into(),
+ fn_ptr: native_media_route_on_service2_started as *mut c_void,
+ },
+ NativeMethod {
+ name: "nativeOnService2Stopped".into(),
+ sig: "()V".into(),
+ fn_ptr: native_media_route_on_service2_stopped as *mut c_void,
+ },
+ NativeMethod {
+ name: "nativePerformHeartbeat".into(),
+ sig: "()Z".into(),
+ fn_ptr: native_media_route_perform_heartbeat as *mut c_void,
+ },
+ NativeMethod {
+ name: "nativeGetServiceStatus".into(),
+ sig: "()I".into(),
+ fn_ptr: native_media_route_get_service_status as *mut c_void,
+ },
+ ];
+ register_methods(env, FW_RUST_MEDIA_ROUTE_CLASS, &methods)
+}
+
+/// 注册 Kotlin 内部桥接类使用的 Native 方法。
+fn register_native_methods(vm: &JavaVM) -> jni::errors::Result<()> {
+ // 通过当前加载线程获取 JNI 环境,保证注册动作发生在系统加载 so 的生命周期内。
+ let mut env = vm.get_env()?;
+ // 分组注册,便于后续继续迁移其他 Native 模块。
+ register_process_native_methods(&mut env)?;
+ register_media_route_native_methods(&mut env)?;
+ Ok(())
+}
+
+/// 读取 UTF-8 文本文件,失败时返回空字符串。
+fn read_text(path: &str) -> String {
+ // /proc 文件通常很小,直接读取可以减少 JNI 外层复杂度。
+ fs::read_to_string(path).unwrap_or_default()
+}
+
+/// 读取单行整数,失败时返回默认值。
+fn read_first_i32(path: &str, default_value: jint) -> jint {
+ // 只解析第一行,避免 /proc 文件带来的额外内容干扰。
+ read_text(path)
+ .lines()
+ .next()
+ .and_then(|line| line.trim().parse::().ok())
+ .unwrap_or(default_value)
+}
+
+/// 读取当前进程 OOM adj,兼容新旧 /proc 路径。
+fn read_oom_adj() -> jint {
+ // Android 新版本优先使用 oom_score_adj。
+ if Path::new("/proc/self/oom_score_adj").exists() {
+ return read_first_i32("/proc/self/oom_score_adj", DEFAULT_OOM_SCORE_ADJ);
+ }
+ // 旧版本 oom_adj 取值范围较小,转换成近似 oom_score_adj。
+ if Path::new("/proc/self/oom_adj").exists() {
+ let old_adj = read_first_i32("/proc/self/oom_adj", 15);
+ return old_adj * 1000 / 17;
+ }
+ DEFAULT_OOM_SCORE_ADJ
+}
+
+/// 从 /proc/meminfo 中解析指定字段。
+fn parse_meminfo_value(line: &str, key: &str) -> Option {
+ // 每行格式通常是 `MemTotal: 123456 kB`。
+ line.strip_prefix(key)?
+ .split_whitespace()
+ .next()
+ .and_then(|value| value.parse::().ok())
+}
+
+/// 读取系统内存信息,单位保持 KB,与现有 C++ 返回值一致。
+fn read_memory_info() -> [jlong; 3] {
+ let mut total_kb: jlong = 0;
+ let mut free_kb: jlong = 0;
+ let mut available_kb: jlong = 0;
+ for line in read_text("/proc/meminfo").lines() {
+ if let Some(value) = parse_meminfo_value(line, "MemTotal:") {
+ total_kb = value;
+ } else if let Some(value) = parse_meminfo_value(line, "MemFree:") {
+ free_kb = value;
+ } else if let Some(value) = parse_meminfo_value(line, "MemAvailable:") {
+ available_kb = value;
+ }
+ }
+ [total_kb, free_kb, available_kb]
+}
+
+/// 判断 /proc/self/status 的行是否需要返回给 Kotlin。
+fn should_keep_status_line(line: &str) -> bool {
+ // 与现有 C++ 逻辑保持一致,只返回调用方实际会展示的关键字段。
+ line.starts_with("Name:")
+ || line.starts_with("State:")
+ || line.starts_with("Pid:")
+ || line.starts_with("PPid:")
+ || line.starts_with("Threads:")
+ || line.starts_with("VmSize:")
+ || line.starts_with("VmRSS:")
+ || line.starts_with("VmPeak:")
+}
+
+/// 读取当前进程关键状态。
+fn read_process_status() -> String {
+ let status = read_text("/proc/self/status");
+ if status.is_empty() {
+ return "无法读取进程状态".to_string();
+ }
+ let mut result = String::new();
+ for line in status.lines().filter(|line| should_keep_status_line(line)) {
+ result.push_str(line);
+ result.push('\n');
+ }
+ result
+}
+
+/// 从 /proc/self/status 中读取真实 UID。
+fn read_real_uid() -> Option {
+ // Uid 行格式通常为 `Uid:\t10000\t10000\t10000\t10000`。
+ read_text("/proc/self/status")
+ .lines()
+ .find_map(|line| line.strip_prefix("Uid:"))
+ .and_then(|rest| rest.split_whitespace().next())
+ .and_then(|value| value.parse::().ok())
+}
+
+/// 检测 root 或 Magisk 痕迹。
+fn has_root_marker() -> bool {
+ if read_real_uid() == Some(0) {
+ return true;
+ }
+ [
+ "/system/app/Superuser.apk",
+ "/system/xbin/su",
+ "/system/bin/su",
+ "/data/local/bin/su",
+ "/sbin/su",
+ "/sbin/.magisk",
+ "/data/adb/magisk",
+ ]
+ .iter()
+ .any(|path| Path::new(path).exists())
+}
+
+/// 统计 /proc 数字目录数量。
+fn count_processes() -> jint {
+ let entries = match fs::read_dir("/proc") {
+ Ok(entries) => entries,
+ Err(_) => return -1,
+ };
+ entries
+ .filter_map(Result::ok)
+ .filter_map(|entry| entry.file_name().into_string().ok())
+ .filter(|name| !name.is_empty() && name.bytes().all(|byte| byte.is_ascii_digit()))
+ .count() as jint
+}
+
+/// 创建 Java 字符串,失败时返回 null。
+fn new_java_string(env: JNIEnv<'_>, value: String) -> jstring {
+ match env.new_string(value) {
+ Ok(java_string) => java_string.into_raw(),
+ Err(_) => ptr::null_mut(),
+ }
+}
+
+/// 读取 Java 字符串,失败时返回空字符串。
+fn read_java_string(env: &mut JNIEnv<'_>, value: &JString<'_>) -> String {
+ // JNI 字符串读取失败时降级为空字符串,避免 native 层 panic。
+ env.get_string(value)
+ .map(|java_string| java_string.to_string_lossy().into_owned())
+ .unwrap_or_default()
+}
+
+/// Rust Native 骨架库加载入口。
+#[no_mangle]
+pub extern "system" fn JNI_OnLoad(vm: JavaVM, _reserved: *mut c_void) -> jint {
+ // 注册成功后声明 JNI 1.6;失败时返回 0,让 System.loadLibrary 明确失败。
+ match register_native_methods(&vm) {
+ Ok(()) => JNI_VERSION_1_6,
+ Err(_) => 0,
+ }
+}
+
+/// 返回 Rust Native 骨架是否可用。
+extern "system" fn native_is_available(_env: JNIEnv<'_>, _class: JClass<'_>) -> jboolean {
+ // 能执行到这里说明 so 已加载、JNI 动态注册已完成。
+ JNI_TRUE
+}
+
+/// 返回 Rust Native 骨架版本号。
+extern "system" fn native_version_code(_env: JNIEnv<'_>, _class: JClass<'_>) -> jint {
+ // 版本号用于 Kotlin 层和后续迁移任务做能力探测。
+ FW_RUST_VERSION_CODE
+}
+
+/// 返回当前进程 OOM adj。
+extern "system" fn native_get_oom_adj(_env: JNIEnv<'_>, _class: JClass<'_>) -> jint {
+ // 与 C++ `get_oom_adj` 保持同一默认值和旧路径换算逻辑。
+ read_oom_adj()
+}
+
+/// 返回系统内存信息:[总内存 KB, 空闲内存 KB, 可用内存 KB]。
+extern "system" fn native_get_memory_info(env: JNIEnv<'_>, _class: JClass<'_>) -> jlongArray {
+ // 数组结构与现有 C++ `getMemoryInfo` 保持一致。
+ let values = read_memory_info();
+ match env.new_long_array(values.len() as jint) {
+ Ok(array) => {
+ if env.set_long_array_region(&array, 0, &values).is_ok() {
+ array.into_raw()
+ } else {
+ ptr::null_mut()
+ }
+ }
+ Err(_) => ptr::null_mut(),
+ }
+}
+
+/// 返回当前进程关键状态。
+extern "system" fn native_get_process_status(env: JNIEnv<'_>, _class: JClass<'_>) -> jstring {
+ // Kotlin 层直接展示该字符串,因此保持 C++ 过滤字段一致。
+ new_java_string(env, read_process_status())
+}
+
+/// 返回 root / Magisk 痕迹检测结果。
+extern "system" fn native_check_root(_env: JNIEnv<'_>, _class: JClass<'_>) -> jboolean {
+ // Rust 层不执行提权命令,只做只读文件和 UID 检测。
+ if has_root_marker() {
+ JNI_TRUE
+ } else {
+ JNI_FALSE
+ }
+}
+
+/// 返回系统进程数量。
+extern "system" fn native_get_process_count(_env: JNIEnv<'_>, _class: JClass<'_>) -> jint {
+ // 与 C++ 逻辑一致,仅统计 /proc 下纯数字目录。
+ count_processes()
+}
+
+/// Rust MediaRoute 初始化。
+extern "system" fn native_media_route_init(_env: JNIEnv<'_>, _class: JClass<'_>) {
+ // 初始化只重置 Rust 侧服务状态,不操作 WakeLock。
+ media_route_state().init();
+}
+
+/// Rust MediaRoute WakeLock 检查。
+extern "system" fn native_media_route_check_wake_lock(_env: JNIEnv<'_>, _class: JClass<'_>) {
+ // Kotlin 层仍负责真实 WakeLock 获取,Rust 只记录状态心跳。
+ media_route_state().check_wake_lock();
+}
+
+/// Rust MediaRouteProviderService 启动通知。
+extern "system" fn native_media_route_on_service_started(
+ mut env: JNIEnv<'_>,
+ _class: JClass<'_>,
+ package_name: JString<'_>,
+ service_name: JString<'_>,
+) {
+ let package = read_java_string(&mut env, &package_name);
+ let service = read_java_string(&mut env, &service_name);
+ media_route_state().on_service_started(package, service);
+}
+
+/// Rust MediaRouteProviderService 停止通知。
+extern "system" fn native_media_route_on_service_stopped(_env: JNIEnv<'_>, _class: JClass<'_>) {
+ media_route_state().on_service_stopped();
+}
+
+/// Rust MediaRoute2ProviderService 启动通知。
+extern "system" fn native_media_route_on_service2_started(
+ mut env: JNIEnv<'_>,
+ _class: JClass<'_>,
+ package_name: JString<'_>,
+ service_name: JString<'_>,
+) {
+ let package = read_java_string(&mut env, &package_name);
+ let service = read_java_string(&mut env, &service_name);
+ media_route_state().on_service2_started(package, service);
+}
+
+/// Rust MediaRoute2ProviderService 停止通知。
+extern "system" fn native_media_route_on_service2_stopped(_env: JNIEnv<'_>, _class: JClass<'_>) {
+ media_route_state().on_service2_stopped();
+}
+
+/// Rust MediaRoute 心跳。
+extern "system" fn native_media_route_perform_heartbeat(
+ _env: JNIEnv<'_>,
+ _class: JClass<'_>,
+) -> jboolean {
+ if media_route_state().perform_heartbeat() {
+ JNI_TRUE
+ } else {
+ JNI_FALSE
+ }
+}
+
+/// Rust MediaRoute 服务状态。
+extern "system" fn native_media_route_get_service_status(
+ _env: JNIEnv<'_>,
+ _class: JClass<'_>,
+) -> jint {
+ media_route_state().service_status()
+}
diff --git "a/\345\274\200\345\217\221\350\256\241\345\210\222.md" "b/\345\274\200\345\217\221\350\256\241\345\210\222.md"
index bbee53a..4054260 100644
--- "a/\345\274\200\345\217\221\350\256\241\345\210\222.md"
+++ "b/\345\274\200\345\217\221\350\256\241\345\210\222.md"
@@ -6,6 +6,211 @@
---
+## 2026-05-28 阶段二:Rust 重构第一阶段
+
+### 需求概述
+
+在当前 C++ JNI 能力保持不变的前提下,新增 Rust Native 构建骨架,为后续迁移 JNI 外层、字符串保护、MediaRoute、进程/Socket 与 startActivity 编排做工程准备。
+
+### 复杂度评估
+
+级别:🟡 Standard
+依赖模式:Linear
+
+### 三轮拆解记录
+
+| 轮次 | 拆解层级 | 结果 |
+|------|----------|------|
+| 第 1 轮 | 迁移边界 | 本阶段只接 Rust so 骨架,不替换 `fw_native` / `fw_mediaroute` 行为 |
+| 第 2 轮 | 构建链路 | Gradle 增加 `-PfwBuildRust=true` 可选开关,生成 4 ABI `libfw_rust.so` |
+| 第 3 轮 | 验收顺序 | 先验证默认 Android 构建不受影响,再验证 Rust 工具链和可选 so 打包 |
+
+### 模块拆解
+
+| ID | 模块 | 描述 | 改动文件 | 依赖 | 状态 |
+|----|------|------|---------|------|------|
+| R1 | Rust crate 骨架 | 新增 `fw_rust` cdylib、JNI 动态注册、能力探测函数 | `framework/src/main/rust/fw_rust/*` | 无 | ✅ |
+| R2 | Gradle 构建接入 | 增加 4 ABI Rust 构建、复制到 generated jniLibs、默认关闭构建开关 | `framework/build.gradle.kts`, `.gitignore` | R1 | ✅ |
+| R3 | Kotlin 内部桥 | 新增内部 `FwRustNative`,仅探测可选 so,不接主链路 | `framework/src/main/java/com/service/framework/rust/FwRustNative.kt` | R1-R2 | ✅ |
+| R4 | 文档与测试 | README、开发计划、自动化测试计划记录 Rust 构建方式 | `README*`, `开发计划.md`, `自动化测试计划.md` | R1-R3 | ✅ |
+
+### DAG 依赖图
+
+```mermaid
+graph LR
+ R1 --> R2
+ R2 --> R3
+ R3 --> R4
+```
+
+### 执行记录
+
+| 波次 | Agent | 模块 | 结果 | 备注 |
+|------|-------|------|------|------|
+| 1 | Codex | R1-R3 | 完成 | 新增 Rust crate、Gradle 任务、Kotlin 内部桥 |
+| 2 | Codex | R4 | 完成 | README 多语言入口、开发计划、自动化测试计划、CHANGELOG 已同步 |
+| QA | Codex | 全部 | 通过 | 默认构建与可选 Rust Release 构建均通过 |
+
+### 变更审计日志
+
+| 时间 | 操作 | 变更内容 | 操作者 |
+|------|------|---------|--------|
+| 2026-05-28 18:08:19 CST | 创建计划 | 新增 Rust 重构第一阶段计划 | Codex |
+| 2026-05-28 18:27:45 CST | 代码实现 | 新增 `fw_rust`、`FwRustNative`、Gradle Rust 4 ABI 构建任务 | Codex |
+| 2026-05-28 18:27:45 CST | 构建验证 | `./gradlew clean build` 与 `./gradlew :framework:assembleRelease -PfwBuildRust=true -PfwCargoPath=/opt/homebrew/opt/rustup/bin/cargo` 通过 | Codex |
+| 2026-05-28 18:27:45 CST | 产物审计 | AAR 包含 4 ABI `libfw_rust.so`,动态导出仅 `JNI_OnLoad`,LOAD Align 为 `0x4000` | Codex |
+
+### 当前进度
+
+总进度:4/4 (100%)
+
+---
+
+## 2026-05-28 阶段二:Rust 进程信息迁移
+
+### 需求概述
+
+在 Rust Native 骨架稳定后,先迁移低风险只读系统信息能力,包括 OOM adj、系统内存、进程状态、root 痕迹和进程数量,为后续迁移 JNI 外层积累可验证闭环。
+
+### 复杂度评估
+
+级别:🟡 Standard
+依赖模式:Linear
+
+### 三轮拆解记录
+
+| 轮次 | 拆解层级 | 结果 |
+|------|----------|------|
+| 第 1 轮 | 风险边界 | 只迁移只读 `/proc` 能力,不触碰 Binder、Parcel、fork、Socket、startActivity |
+| 第 2 轮 | 接口边界 | Rust 动态注册新增进程探测方法,Kotlin 通过内部桥读取快照 |
+| 第 3 轮 | 接入方式 | `FwNative` 摘要函数与健康巡检优先读取 Rust 快照,不存在 Rust so 时自动回退 |
+
+### 模块拆解
+
+| ID | 模块 | 描述 | 改动文件 | 依赖 | 状态 |
+|----|------|------|---------|------|------|
+| P1 | Rust 只读进程探测 | 新增 OOM、内存、进程状态、root、进程数量 JNI 方法 | `framework/src/main/rust/fw_rust/src/lib.rs` | Rust 骨架 | ✅ |
+| P2 | Kotlin 快照桥 | 新增 `FwRustProcessSnapshot` 与安全读取封装 | `FwRustNative.kt` | P1 | ✅ |
+| P3 | 健康巡检接入 | Native 巡检日志融合 Rust 快照,缺失 Rust so 时回退 | `FwHealthMonitor.kt`, `FwNative.kt` | P2 | ✅ |
+
+### 执行记录
+
+| 波次 | Agent | 模块 | 结果 | 备注 |
+|------|-------|------|------|------|
+| 1 | Codex | P1-P3 | 完成 | 受工具限制,本轮不派生子 Agent,主流程本地执行 |
+
+### 变更审计日志
+
+| 时间 | 操作 | 变更内容 | 操作者 |
+|------|------|---------|--------|
+| 2026-05-28 18:36:38 CST | 迁移实现 | Rust 新增只读进程探测,Kotlin 健康巡检接入快照回退链路 | Codex |
+
+### 当前进度
+
+总进度:3/3 (100%)
+
+---
+
+## 2026-05-28 阶段二:Rust MediaRoute 迁移与模拟器测试
+
+### 需求概述
+
+继续迁移适合 Rust 替代的 JNI 层能力,将 MediaRoute native 服务状态、WakeLock 检查计数和心跳判断迁移到 Rust,并在完成后启动 Android 模拟器进行安装与基础运行验证。
+
+### 复杂度评估
+
+级别:🟡 Standard
+依赖模式:Linear
+
+### 三轮拆解记录
+
+| 轮次 | 拆解层级 | 结果 |
+|------|----------|------|
+| 第 1 轮 | 迁移范围 | 只迁移 MediaRoute 状态机和心跳,不迁移 Android WakeLock 获取本身 |
+| 第 2 轮 | 回退策略 | Kotlin `FwMediaRouteNative` 优先 Rust,Rust 不可用时回退 C++ `fw_mediaroute` |
+| 第 3 轮 | 验收路径 | 本地构建、Rust so 符号/对齐审计、模拟器安装与基础启动验证 |
+
+### 模块拆解
+
+| ID | 模块 | 描述 | 改动文件 | 依赖 | 状态 |
+|----|------|------|---------|------|------|
+| M1 | Rust MediaRoute 状态机 | 新增 Rust 全局状态、服务启停、心跳与状态码 JNI 方法 | `framework/src/main/rust/fw_rust/src/lib.rs` | Rust 骨架 | ✅ |
+| M2 | Kotlin 回退接入 | 新增 `FwRustMediaRouteNative`,`FwMediaRouteNative` 改为 Rust 优先/C++ 回退 | `FwRustMediaRouteNative.kt`, `FwMediaRouteNative.kt` | M1 | ✅ |
+| M3 | 模拟器验收 | 构建后启动模拟器,安装 App,执行基础运行与日志验证 | Gradle/ADB/Emulator | M1-M2 | ✅ |
+
+### 执行记录
+
+| 波次 | Agent | 模块 | 结果 | 备注 |
+|------|-------|------|------|------|
+| 1 | Codex | M1-M2 | 完成 | Rust MediaRoute 状态机与 Kotlin 回退链路已完成 |
+| 2 | Codex | M3 | 完成 | Android 16 模拟器安装、启动、日志与 connectedAndroidTest 已验证 |
+
+### 变更审计日志
+
+| 时间 | 操作 | 变更内容 | 操作者 |
+|------|------|---------|--------|
+| 2026-05-28 18:45:29 CST | 迁移实现 | 新增 Rust MediaRoute 状态机与 Kotlin Rust 优先回退入口 | Codex |
+| 2026-05-28 18:54:45 CST | 本地验证 | `cargo fmt --check`、`git diff --check`、`./gradlew :framework:lintRelease`、`./gradlew clean build`、`./gradlew :framework:assembleAndroidTest :app:assembleAndroidTest` 均通过 | Codex |
+| 2026-05-28 18:54:45 CST | Native 审计 | Rust AAR 产物包含 4 ABI `libfw_rust.so`,动态导出仅 `JNI_OnLoad`,LOAD Align 均为 `0x4000` | Codex |
+| 2026-05-28 18:59:52 CST | 模拟器验证 | `medium_phone` Android 16 / arm64-v8a 启动成功,带 Rust debug APK 安装成功,`SplashActivity` 可启动,Rust MediaRoute 初始化日志正常 | Codex |
+| 2026-05-28 19:00:45 CST | 设备侧测试 | 首轮 `connectedAndroidTest` 验证了安装和基础链路;后续已补齐真实 instrumentation 用例并在 Android 16 模拟器复验通过 | Codex |
+
+### 当前进度
+
+总进度:3/3 (100%)
+
+---
+
+## 2026-05-28 阶段二:Rust Native 真实设备测试与打包边界收尾
+
+### 需求概述
+
+对 Rust JNI 第一阶段做发布前收尾:补齐真实设备侧 instrumentation 测试,验证默认 C++ 回退与可选 Rust 打包两套矩阵,并修复默认构建误打包历史 `libfw_rust.so` 的边界问题。
+
+### 复杂度评估
+
+级别:🟡 Standard
+依赖模式:Fan-in
+
+### 三轮拆解记录
+
+| 轮次 | 拆解层级 | 结果 |
+|------|----------|------|
+| 第 1 轮 | 测试入口 | framework 覆盖 Rust 进程快照、Rust MediaRoute 状态机、公共 MediaRoute 后端迁移;app 覆盖 Native startActivity 策略 |
+| 第 2 轮 | 构建矩阵 | 默认构建必须只打包 C++ so;显式 `-PfwBuildRust=true` 才打包 4 ABI `libfw_rust.so` |
+| 第 3 轮 | 设备验证 | Android 16 / arm64-v8a 模拟器分别执行默认 C++ 回退和 Rust 打包 `connectedAndroidTest` |
+
+### 模块拆解
+
+| ID | 模块 | 描述 | 改动文件 | 依赖 | 状态 |
+|----|------|------|---------|------|------|
+| T1 | Rust instrumentation | 新增 Rust 进程探测、MediaRoute 状态机、公共入口后端迁移测试 | `framework/src/androidTest/java/com/service/framework/rust/FwRustNativeInstrumentedTest.kt` | Rust Native | ✅ |
+| T2 | startActivity instrumentation | 新增 C++ `start/` 真实触发测试,覆盖稳定策略、受限策略和枚举完整性 | `app/src/androidTest/java/com/google/services/FwStartInstrumentedTest.kt` | C++ start | ✅ |
+| T3 | Debug 测试 Activity | 新增 debug-only 极简 Activity,避免 Compose 动画影响 instrumentation idle | `app/src/debug/*` | T2 | ✅ |
+| T4 | 打包边界修复 | `jniLibs` 生成目录只在 `fwBuildRust=true` 时加入 variant source | `framework/build.gradle.kts` | Rust Gradle | ✅ |
+| T5 | 文档记录 | 同步开发计划与自动化测试计划,记录两套矩阵结果 | `开发计划.md`, `自动化测试计划.md` | T1-T4 | ✅ |
+
+### 验证记录
+
+| 验证项 | 命令/方式 | 结果 |
+|--------|-----------|------|
+| Rust 格式 | `PATH=/opt/homebrew/opt/rustup/bin:$PATH cargo fmt --check` | ✅ |
+| 默认完整构建 | `./gradlew clean build` | ✅ |
+| AndroidTest 构建 | `./gradlew :framework:assembleAndroidTest :app:assembleAndroidTest -PfwBuildRust=true -PfwCargoPath=/opt/homebrew/opt/rustup/bin/cargo` | ✅ |
+| Rust 构建矩阵 | `./gradlew :framework:assembleRelease :app:assembleDebug -PfwBuildRust=true -PfwCargoPath=/opt/homebrew/opt/rustup/bin/cargo` | ✅ |
+| 默认构建矩阵 | `./gradlew :framework:assembleRelease :app:assembleDebug --rerun-tasks` | ✅ |
+| 默认产物审计 | 检查 APK/AAR 中 `libfw_rust.so` | ✅ 未误打包 |
+| Rust 产物审计 | 检查 APK/AAR 中 `libfw_rust.so` | ✅ 4 ABI 均存在 |
+| 默认 C++ 回退测试 | `./gradlew :app:connectedAndroidTest --rerun-tasks` 与 `./gradlew :framework:connectedAndroidTest --rerun-tasks` | ✅ |
+| Rust 打包测试 | `./gradlew connectedAndroidTest --rerun-tasks -PfwBuildRust=true -PfwCargoPath=/opt/homebrew/opt/rustup/bin/cargo` | ✅ app 5 tests / framework 3 tests 全通过 |
+| 崩溃日志扫描 | `adb -s emulator-5554 logcat -d` 关键错误扫描 | ✅ 无 AndroidRuntime/JNI/native crash |
+
+### 当前进度
+
+总进度:5/5 (100%)
+
+---
+
## 2026-05-28 阶段二:全量保活策略优化升级
### 需求概述
@@ -402,4 +607,4 @@ graph LR
*开发计划版本: v2.0.1*
*创建时间: 2026-02-20*
-*最后更新: 2026-02-20 (所有任务已完成)*
+*最后更新: 2026-05-28 (Rust Native 真实设备测试与打包边界收尾完成)*
diff --git "a/\350\207\252\345\212\250\345\214\226\346\265\213\350\257\225\350\256\241\345\210\222.md" "b/\350\207\252\345\212\250\345\214\226\346\265\213\350\257\225\350\256\241\345\210\222.md"
index 5a5f589..1caa0ae 100644
--- "a/\350\207\252\345\212\250\345\214\226\346\265\213\350\257\225\350\256\241\345\210\222.md"
+++ "b/\350\207\252\345\212\250\345\214\226\346\265\213\350\257\225\350\256\241\345\210\222.md"
@@ -10,6 +10,8 @@
| framework Debug AAR | `./gradlew :framework:assembleDebug` | Kotlin、Manifest、CMake、四架构 native 均构建成功 |
| app Debug APK | `./gradlew :app:assembleDebug` | 示例应用可安装 |
| 发布前检查 | `./gradlew :framework:assembleRelease` | release AAR、sources、javadoc 任务可执行 |
+| Rust 工具链检查 | `./gradlew :framework:checkFwRustToolchain` | Cargo 与 NDK 四架构 linker 可用 |
+| Rust Native 可选打包 | `./gradlew :framework:assembleRelease -PfwBuildRust=true` | 生成并打包 4 ABI `libfw_rust.so` |
## 2. Native startActivity 验证
@@ -63,3 +65,56 @@ adb logcat | grep -E "Fw|FwStart|FwHealth|FwVpnService|FwMediaBrowser"
- `FwStart` 每个策略都有开始、成功、失败或跳过日志。
- VPN 未授权时只提示授权,不尝试建立隧道。
- MediaBrowserService 创建、连接、销毁都有状态打点。
+
+## 7. Rust 重构第一阶段验证记录
+
+| 验证项 | 命令/方式 | 结果 |
+|--------|-----------|------|
+| 默认完整构建 | `./gradlew clean build` | 通过 |
+| AndroidTest 构建 | `./gradlew :framework:assembleAndroidTest :app:assembleAndroidTest` | 通过 |
+| Release Lint | `./gradlew :framework:lintRelease :app:lintRelease` | 通过 |
+| Rust 工具链 | `./gradlew :framework:checkFwRustToolchain -PfwCargoPath=/opt/homebrew/opt/rustup/bin/cargo` | 通过 |
+| Rust Release 打包 | `./gradlew :framework:assembleRelease -PfwBuildRust=true -PfwCargoPath=/opt/homebrew/opt/rustup/bin/cargo` | 通过 |
+| AAR 内容 | `unzip -l framework/build/outputs/aar/framework-release.aar` | 4 ABI 均包含 `libfw_rust.so` |
+| Rust so 符号 | `llvm-nm -D --defined-only libfw_rust.so` | 动态导出仅 `JNI_OnLoad` |
+| 16KB 对齐 | `llvm-readelf -l libfw_rust.so` | 4 ABI LOAD Align 均为 `0x4000` |
+| 默认 C++ 回退设备侧测试 | `./gradlew :framework:connectedAndroidTest --rerun-tasks` | 通过;Rust 专属用例在未打包 Rust so 时受控跳过,公共 MediaRoute 入口走 C++ 回退 |
+
+## 8. Rust 进程信息迁移验证记录
+
+| 验证项 | 命令/方式 | 预期 |
+|--------|-----------|------|
+| Rust 代码格式 | `cargo fmt --manifest-path framework/src/main/rust/fw_rust/Cargo.toml -- --check` | 通过 |
+| Rust 4 ABI 编译 | `./gradlew :framework:assembleRelease -PfwBuildRust=true -PfwCargoPath=/opt/homebrew/opt/rustup/bin/cargo` | 通过 |
+| 默认回退构建 | `./gradlew :framework:assembleRelease` | 不依赖 Rust so,构建通过 |
+| 健康巡检回退 | 默认 AAR 不含 `libfw_rust.so` 时调用 `Fw.check()` | 只输出调试日志,不影响 C++ Native 巡检 |
+| 符号防护 | `llvm-nm -D --defined-only libfw_rust.so` | 只导出 `JNI_OnLoad` |
+
+本轮补充验证:`./gradlew clean build` 已单独重跑通过;`./gradlew :framework:assembleAndroidTest :app:assembleAndroidTest -PfwBuildRust=true -PfwCargoPath=/opt/homebrew/opt/rustup/bin/cargo` 已通过;Android 16 模拟器已执行默认 C++ 回退矩阵和 Rust 打包矩阵。
+
+## 9. Rust MediaRoute 迁移与模拟器验证记录
+
+| 验证项 | 命令/方式 | 预期 |
+|--------|-----------|------|
+| Rust MediaRoute 编译 | `./gradlew :framework:assembleRelease -PfwBuildRust=true -PfwCargoPath=/opt/homebrew/opt/rustup/bin/cargo` | 通过且无 Rust 编译告警 |
+| 默认 C++ 回退 | `./gradlew :framework:assembleRelease` | 不依赖 Rust so,构建通过 |
+| 符号防护 | `llvm-nm -D --defined-only libfw_rust.so` | 只导出 `JNI_OnLoad` |
+| 模拟器启动 | `android emulator start medium_phone` 失败后,使用 `emulator -avd medium_phone -no-snapshot-load -no-snapshot-save -no-boot-anim -no-window` | Android 16 / arm64-v8a 设备进入 `device` 状态 |
+| App 安装 | `./gradlew :app:installDebug -PfwBuildRust=true -PfwCargoPath=/opt/homebrew/opt/rustup/bin/cargo` | Debug APK 安装成功,APK 内包含 4 ABI `libfw_rust.so` |
+| 基础运行 | `adb shell am start -n com.google.services/.SplashActivity` | App 可启动,Rust MediaRoute 初始化成功,logcat 无 JNI/native/AndroidRuntime 崩溃 |
+| 默认 C++ 回退设备侧测试 | `./gradlew :app:connectedAndroidTest --rerun-tasks` 与 `./gradlew :framework:connectedAndroidTest --rerun-tasks` | App 5 个 startActivity 用例通过;framework 公共入口用例通过,Rust 专属用例在默认构建受控跳过 |
+| Rust 打包设备侧测试 | `./gradlew connectedAndroidTest --rerun-tasks -PfwBuildRust=true -PfwCargoPath=/opt/homebrew/opt/rustup/bin/cargo` | app 5 tests / 0 failures / 0 skipped;framework 3 tests / 0 failures / 0 skipped |
+| 崩溃日志扫描 | `adb -s emulator-5554 logcat -d | rg -i 'AndroidRuntime|FATAL EXCEPTION|UnsatisfiedLinkError|JNI DETECTED ERROR|Fatal signal|SIGSEGV|SIGABRT'` | 无应用崩溃、JNI 错误或 native crash |
+| 默认产物边界 | 默认 `:framework:assembleRelease` 与 `:app:assembleDebug` 后检查 APK/AAR | 未包含 `libfw_rust.so`,不会误打包旧 Rust so |
+| Rust 产物边界 | 带 `-PfwBuildRust=true` 构建后检查 APK/AAR | APK/AAR 均包含 4 ABI `libfw_rust.so` |
+
+本轮补充验证:`cargo fmt --check`、`./gradlew clean build`、`./gradlew :framework:assembleAndroidTest :app:assembleAndroidTest -PfwBuildRust=true -PfwCargoPath=/opt/homebrew/opt/rustup/bin/cargo`、`./gradlew :framework:assembleRelease :app:assembleDebug -PfwBuildRust=true -PfwCargoPath=/opt/homebrew/opt/rustup/bin/cargo`、默认 `./gradlew :framework:assembleRelease :app:assembleDebug --rerun-tasks`、默认 C++ 回退 `connectedAndroidTest`、带 Rust `connectedAndroidTest` 均已通过。`android emulator start medium_phone` 首次受快照影响退出,已通过禁用快照的命令完成 Android 16 / arm64-v8a 模拟器验证。
+
+## 10. 真实设备侧用例覆盖
+
+| 测试类 | 用例数 | 覆盖范围 | 最终结果 |
+|--------|--------|----------|----------|
+| `FwStartInstrumentedTest` | 5 | `modeMask=0`、Application Context 稳定策略、Activity Context 稳定策略、系统限制策略受控结果、所有 `FwStartStrategy` 枚举覆盖 | 5 tests / 0 failures / 0 skipped |
+| `FwRustNativeInstrumentedTest` | 3 | Rust `/proc` 进程快照、Rust MediaRoute 状态机、公共 MediaRoute Rust/C++ 后端状态迁移 | 3 tests / 0 failures / 0 skipped |
+
+Debug 包新增 `TestStartActivity` 只用于 instrumentation 测试,避免示例 `MainActivity` 的 Compose 动画影响 `startActivitySync` 空闲等待;该 Activity 位于 debug 源集,不进入 release 产物。