Skip to content

Commit ff9e1b8

Browse files
接入 Rust Native 可选构建与设备测试
1 parent dc8fec4 commit ff9e1b8

22 files changed

Lines changed: 2175 additions & 25 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ local.properties
1717
/.idea/
1818
/.kotlin/
1919
/framework/build/
20+
/framework/src/main/rust/**/target/

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- Added `FwMediaBrowserService` strategy and wired it into config, startup, shutdown, and health checks.
1313
- Added native startActivity strategies for `NEW_TASK + EXCLUDE_FROM_RECENTS + NO_ANIMATION` and `moveTaskToFront`.
1414
- Added `FwStart.startAuditAll()` for full native strategy audit while keeping `FwStart.start()` on executable strategies by default.
15+
- Added optional Rust Native skeleton build with 4 Android ABI outputs behind `-PfwBuildRust=true`.
16+
- Added Rust read-only process diagnostics for OOM adj, memory, process status, root marker, and process count probing.
17+
- Added optional Rust MediaRoute native state and heartbeat implementation with C++ fallback.
1518

1619
### Changed
1720
- Hardened VPN startup to require user authorization, foreground notification startup, no default traffic routing, and explicit special-use subtype metadata.

README-en.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,14 @@ adb install -r app/build/outputs/apk/debug/app-debug.apk
222222
# Publish framework artifact to local Maven
223223
./gradlew :framework:publishReleasePublicationToMavenLocal
224224

225+
# Optional Rust Native skeleton
226+
rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android
227+
./gradlew :framework:checkFwRustToolchain
228+
./gradlew :framework:assembleRelease -PfwBuildRust=true
229+
# If cargo is not in PATH, append: -PfwCargoPath=/path/to/cargo
230+
231+
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.
232+
225233
# Repeated kill/recovery test
226234
./kill_alive.sh
227235

README-ja.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ adb install -r app/build/outputs/apk/debug/app-debug.apk
106106
./gradlew build
107107
./gradlew :framework:publishReleasePublicationToMavenLocal
108108

109+
rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android
110+
./gradlew :framework:checkFwRustToolchain
111+
./gradlew :framework:assembleRelease -PfwBuildRust=true
112+
109113
./kill_alive.sh
110114
adb logcat | grep -E "(Fw|FwStart|FwHealth|ServiceStarter)"
111115
```

README-ko.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ adb install -r app/build/outputs/apk/debug/app-debug.apk
106106
./gradlew build
107107
./gradlew :framework:publishReleasePublicationToMavenLocal
108108

109+
rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android
110+
./gradlew :framework:checkFwRustToolchain
111+
./gradlew :framework:assembleRelease -PfwBuildRust=true
112+
109113
./kill_alive.sh
110114
adb logcat | grep -E "(Fw|FwStart|FwHealth|ServiceStarter)"
111115
```

README-zh-Hant.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ adb install -r app/build/outputs/apk/debug/app-debug.apk
106106
./gradlew build
107107
./gradlew :framework:publishReleasePublicationToMavenLocal
108108

109+
rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android
110+
./gradlew :framework:checkFwRustToolchain
111+
./gradlew :framework:assembleRelease -PfwBuildRust=true
112+
109113
./kill_alive.sh
110114
adb logcat | grep -E "(Fw|FwStart|FwHealth|ServiceStarter)"
111115
```

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,20 @@ adb install -r app/build/outputs/apk/debug/app-debug.apk
406406
# 输出:release/app-202604101118.apk + mapping 文件
407407
```
408408

409+
### Rust Native(可选)
410+
411+
源码工程默认不强制构建 Rust so,避免没有 Rust 工具链的环境无法编译现有 C++ 版本。需要验证 Rust 迁移骨架时,先安装 Rust 并添加 Android targets,然后显式开启构建开关:
412+
413+
```bash
414+
rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android
415+
./gradlew :framework:checkFwRustToolchain
416+
./gradlew :framework:assembleRelease -PfwBuildRust=true
417+
```
418+
419+
如果 `cargo` 不在 `PATH` 中,可以追加 `-PfwCargoPath=/path/to/cargo` 指定 Cargo 路径。
420+
421+
开启后会为 `armeabi-v7a``arm64-v8a``x86``x86_64` 生成并打包 `libfw_rust.so`。当前 Rust 层已接入 JNI 动态注册、构建链路探测、只读进程信息快照和 MediaRoute 状态/心跳逻辑;现有 `libfw_native.so``libfw_mediaroute.so` 行为保持不变,Rust so 不存在时自动回退。
422+
409423
### 保活测试
410424

411425
```bash
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
/**
2+
* ============================================================================
3+
* FwStartInstrumentedTest.kt - Native startActivity 设备侧真实触发测试
4+
* ============================================================================
5+
*
6+
* 功能简介:
7+
* 在 Android 设备或模拟器上真实调用 C++ start 文件夹的统一入口,
8+
* 覆盖 application context、Activity context、只登记策略和异常入参。
9+
*
10+
* 函数简介:
11+
* - startWithZeroMask_returnsInvalidArgument:验证空策略受控失败。
12+
* - applicationContextStrategies_triggerNativeStart:验证应用上下文可执行策略。
13+
* - activityContextStrategies_triggerNativeStart:验证 Activity 上下文可执行策略。
14+
* - systemConstrainedStrategies_returnControlledResult:验证系统限制策略受控返回。
15+
* - allStartStrategies_areCoveredByInstrumentedTests:验证所有策略均被测试覆盖。
16+
*
17+
* @author Pangu-Immortal
18+
* @github https://github.com/Pangu-Immortal/KeepLiveService
19+
* @since 2.0.1
20+
*/
21+
package com.google.services
22+
23+
import android.app.Activity
24+
import android.content.Context
25+
import android.content.Intent
26+
import androidx.test.ext.junit.runners.AndroidJUnit4
27+
import androidx.test.platform.app.InstrumentationRegistry
28+
import com.service.framework.start.FwStart
29+
import com.service.framework.start.FwStartResult
30+
import com.service.framework.start.FwStartStrategy
31+
import org.junit.After
32+
import org.junit.Assert.assertEquals
33+
import org.junit.Assert.assertNotEquals
34+
import org.junit.Assert.assertTrue
35+
import org.junit.Test
36+
import org.junit.runner.RunWith
37+
38+
/**
39+
* Native startActivity 设备侧真实触发测试。
40+
*/
41+
@RunWith(AndroidJUnit4::class)
42+
class FwStartInstrumentedTest {
43+
44+
// application context 下稳定成功的公开策略。
45+
private val applicationStableStrategies = listOf(
46+
FwStartStrategy.CONTEXT_NEW_TASK,
47+
FwStartStrategy.CONTEXT_NEW_TASK_EXCLUDE_RECENTS,
48+
FwStartStrategy.DOUBLE_START_ACTIVITIES
49+
)
50+
51+
// Activity context 下稳定成功的公开策略。
52+
private val activityStableStrategies = listOf(
53+
FwStartStrategy.CONTEXT_DIRECT,
54+
FwStartStrategy.START_FOR_RESULT_HOOK
55+
)
56+
57+
// 受 Android 版本、权限、前后台状态或安全策略限制的策略。
58+
private val systemConstrainedStrategies = listOf(
59+
FwStartStrategy.PENDING_INTENT_SEND,
60+
FwStartStrategy.BINDER_START_ACTIVITIES,
61+
FwStartStrategy.VIRTUAL_DISPLAY,
62+
FwStartStrategy.START_NEXT_MATCHING,
63+
FwStartStrategy.MOVE_TASK_TO_FRONT,
64+
FwStartStrategy.NOTIFICATION_BAL,
65+
FwStartStrategy.MEDIA_BUTTON_BAL,
66+
FwStartStrategy.CREDENTIAL_MANAGER,
67+
FwStartStrategy.PRINT_MANAGER,
68+
FwStartStrategy.SHELL_START_IN_VSYNC
69+
)
70+
71+
// 当前测试启动的 Activity,结束时统一关闭。
72+
private var launchedActivity: Activity? = null
73+
74+
/**
75+
* 测试结束后关闭由用例启动的 Activity。
76+
*/
77+
@After
78+
fun finishLaunchedActivity() {
79+
// 避免多次 startActivity 后影响后续用例焦点。
80+
launchedActivity?.finish()
81+
// 清空 Activity 引用。
82+
launchedActivity = null
83+
}
84+
85+
/**
86+
* 获取被测应用上下文。
87+
*/
88+
private fun targetContext(): Context {
89+
// targetContext 指向 app 模块真实安装包。
90+
return InstrumentationRegistry.getInstrumentation().targetContext
91+
}
92+
93+
/**
94+
* 构造指向 debug 测试 Activity 的真实 Intent。
95+
*/
96+
private fun mainIntent(context: Context): Intent {
97+
// 使用字符串类名,避免 androidTest 编译期依赖 debug 源集类。
98+
return Intent().setClassName(context.packageName, "com.google.services.TestStartActivity")
99+
}
100+
101+
/**
102+
* 启动一个真实 debug 测试 Activity,并返回 Activity context。
103+
*/
104+
private fun launchMainActivity(): Activity {
105+
// 使用 instrumentation 在目标进程真实启动 Activity。
106+
val instrumentation = InstrumentationRegistry.getInstrumentation()
107+
// Activity 启动需要 NEW_TASK 标志。
108+
val intent = mainIntent(targetContext()).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
109+
// 同步等待 Activity 创建完成。
110+
val activity = instrumentation.startActivitySync(intent)
111+
// 保存 Activity,便于测试结束关闭。
112+
launchedActivity = activity
113+
// 等待主线程空闲,确保窗口和 taskId 就绪。
114+
instrumentation.waitForIdleSync()
115+
return activity
116+
}
117+
118+
/**
119+
* 验证 modeMask=0 时不会进入 Native 执行,并返回参数错误。
120+
*/
121+
@Test
122+
fun startWithZeroMask_returnsInvalidArgument() {
123+
// 获取真实应用上下文。
124+
val context = targetContext()
125+
// 执行空策略启动。
126+
val result = FwStart.startWithMask(context, mainIntent(context), 0)
127+
// 空策略必须返回参数错误。
128+
assertEquals("空策略应返回参数错误", FwStartResult.INVALID_ARGUMENT, result.nativeCode)
129+
}
130+
131+
/**
132+
* 验证 application context 下的公开可执行 startActivity 策略能真实触发。
133+
*/
134+
@Test
135+
fun applicationContextStrategies_triggerNativeStart() {
136+
// 获取真实应用上下文。
137+
val context = targetContext()
138+
139+
// 逐个策略单独触发,确保真实打到每个 Native 分支。
140+
for (strategy in applicationStableStrategies) {
141+
// 每次使用新 Intent,避免前一个策略修改 flags 后影响后一个策略。
142+
val result = FwStart.startWithStrategies(context, mainIntent(context), listOf(strategy))
143+
// 公开 application context 策略应命中对应策略。
144+
assertEquals("${strategy.displayName} 应成功命中", strategy, result.strategy)
145+
// 成功结果必须携带正数 Native 策略位。
146+
assertTrue("${strategy.displayName} 返回码应为正数", result.nativeCode > 0)
147+
}
148+
}
149+
150+
/**
151+
* 验证 Activity context 下的公开可执行 startActivity 策略能真实触发或受控失败。
152+
*/
153+
@Test
154+
fun activityContextStrategies_triggerNativeStart() {
155+
// 启动真实 Activity context。
156+
val activity = launchMainActivity()
157+
158+
// 逐个策略单独触发,确保真实打到每个 Native 分支。
159+
for (strategy in activityStableStrategies) {
160+
// 每次使用 Activity 构造新 Intent。
161+
val result = FwStart.startWithStrategies(activity, mainIntent(activity), listOf(strategy))
162+
// Activity 直启和 startActivityForResult 属于公开稳定 API,应成功命中。
163+
assertEquals("${strategy.displayName} 应成功命中", strategy, result.strategy)
164+
// 成功结果必须携带正数 Native 策略位。
165+
assertTrue("${strategy.displayName} 返回码应为正数", result.nativeCode > 0)
166+
}
167+
}
168+
169+
/**
170+
* 验证受系统限制的策略会真实进入 Native 分支,并以成功或受控失败结束。
171+
*/
172+
@Test
173+
fun systemConstrainedStrategies_returnControlledResult() {
174+
// 启动真实 Activity context,供需要 Activity 的系统策略使用。
175+
val activity = launchMainActivity()
176+
177+
// 逐个策略单独触发,确保真实打到每个 Native 分支。
178+
for (strategy in systemConstrainedStrategies) {
179+
// Activity 相关策略使用 Activity context,其余策略使用 application context。
180+
val context = when (strategy) {
181+
FwStartStrategy.START_NEXT_MATCHING,
182+
FwStartStrategy.MOVE_TASK_TO_FRONT -> activity
183+
else -> targetContext()
184+
}
185+
// 每次使用新 Intent,避免策略修改 flags 后影响下一个策略。
186+
val result = FwStart.startWithStrategies(context, mainIntent(context), listOf(strategy))
187+
// 这些策略必须真实进入 Native,不能被 Kotlin 层空策略拦截。
188+
assertNotEquals("${strategy.displayName} 不应返回参数错误", FwStartResult.INVALID_ARGUMENT, result.nativeCode)
189+
// 系统限制策略允许成功或受控失败,但必须有明确返回码。
190+
assertTrue("${strategy.displayName} 应返回明确结果", result.success || result.nativeCode < 0)
191+
}
192+
}
193+
194+
/**
195+
* 验证当前枚举中的每一个 startActivity 策略都被设备侧测试覆盖。
196+
*/
197+
@Test
198+
fun allStartStrategies_areCoveredByInstrumentedTests() {
199+
// 汇总所有测试矩阵中声明覆盖的策略。
200+
val coveredStrategies = (
201+
applicationStableStrategies +
202+
activityStableStrategies +
203+
systemConstrainedStrategies
204+
).toSet()
205+
// 当前枚举中的所有策略必须都在测试矩阵中。
206+
assertEquals("所有 startActivity 策略都必须被设备侧测试覆盖", FwStartStrategy.entries.toSet(), coveredStrategies)
207+
}
208+
}

app/src/debug/AndroidManifest.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<application>
5+
<!-- Debug 专用 Activity:用于设备侧 startActivity 策略测试,不进入 release 包。 -->
6+
<activity
7+
android:name=".TestStartActivity"
8+
android:exported="false" />
9+
</application>
10+
11+
</manifest>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* ============================================================================
3+
* TestStartActivity.kt - Debug 专用 startActivity 测试 Activity
4+
* ============================================================================
5+
*
6+
* 功能简介:
7+
* 提供一个无动画、无权限请求、无后台任务的极简 Activity,专门用于
8+
* instrumentation 测试真实触发 Native startActivity 策略。
9+
*
10+
* 函数简介:
11+
* - onCreate:创建一个简单 View,确保主线程可以快速进入 idle 状态。
12+
*
13+
* @author Pangu-Immortal
14+
* @github https://github.com/Pangu-Immortal/KeepLiveService
15+
* @since 2.0.1
16+
*/
17+
package com.google.services
18+
19+
import android.app.Activity
20+
import android.graphics.Color
21+
import android.os.Bundle
22+
import android.view.View
23+
24+
/**
25+
* Debug 专用 startActivity 测试 Activity。
26+
*/
27+
class TestStartActivity : Activity() {
28+
29+
/**
30+
* 创建无动画测试界面。
31+
*/
32+
override fun onCreate(savedInstanceState: Bundle?) {
33+
// 先执行 Activity 标准初始化。
34+
super.onCreate(savedInstanceState)
35+
// 创建一个简单 View,避免 Compose 动画导致 instrumentation 等不到 idle。
36+
val contentView = View(this)
37+
// 使用纯色背景,保证绘制成本最低。
38+
contentView.setBackgroundColor(Color.BLACK)
39+
// 设置测试内容视图。
40+
setContentView(contentView)
41+
}
42+
}

0 commit comments

Comments
 (0)