Shadow Nav
ARM64 Android 上的 GPU 加速控制面板,Shadow paging 生态的前台 App——用 React Native 0.85 + Skia + Reanimated 4,配一套 Kotlin native bridge,通过持久 su 会话做根权限文件 / 模块 / 设备操作。
不是 hack 工具,是控制台:配置内核功能、管文件、管 .ko 模块、配主题。
一句话
给 root 权限 Android 做一个 macOS Finder 质感、GPU 跑满特效、零 shell 的日常控制台。
文档索引
- 功能分类 — 四张主屏逐一讲:Home / File / Repo / Settings
技术栈
| 层 | 用什么 |
|---|---|
| Framework | React Native 0.85 + React 19(New Architecture / Fabric) |
| UI 组件 | Tamagui 2.0 rc.38 |
| GPU 渲染 | @shopify/react-native-skia 2.6.2 |
| 动画 | react-native-reanimated 4.3(worklet / SharedValue / useClock) |
| 手势 | react-native-gesture-handler 2.31(Pan / Tap / Race / Exclusive / manualActivation) |
| Native bridge | Kotlin(FsModule / DeviceInfoModule / MemModule) |
| 持久化 | @react-native-async-storage 2.2 |
没用 ScrollView 做水平滚动(Miller Columns 是自己 Pan + Animated)。没用 RN 布局做动画(全部 position:absolute + 手算坐标 lerp)。所有视觉效果走 Skia Canvas GPU 线程——JS 侧零渲染成本。
架构
┌─ React Native (JS thread) ─────────────────────────────────┐
│ screens/ HomeScreen / FileScreen / RepoScreen / ... │
│ shell/ BottomNav + Content (tab container) │
│ components/ Tamagui-based cards / pills / menus │
│ gpu/ Skia-rendered glow / orbs / hover text │
│ hooks/ useFanGesture (行自 onLongPress 识别) │
└────────────────────────────┬───────────────────────────────┘
│
┌──────────┴──────────┐
│ Native Bridge │
│ (Kotlin modules) │
└──────────┬──────────┘
│
┌─ 3 个 Kotlin 模块 ───────────────────────────────────────┐
│ FsModule 持久 su 会话池(3 session / 4-thread)│
│ readDir / stat / chmod / chown / ... │
│ DeviceInfoModule SoC / manufacturer / Android 版本快照 │
│ MemModule Java / native / graphics / PSS 监控 │
└──────────────────────────────────────────────────────────┘所有特权操作都是标准 Linux 系统调用——finit_module / delete_module / openat / fstatat——不打内核补丁。Kernel 侧和 Shadow paging 完全解耦:这个 App 可以在任何 root Android 上跑。
项目布局
shadow_nav/
├── src/
│ ├── screens/ # 主屏(Home / File / Repo / Settings / Drivers / HWID / PrivIds)
│ ├── shell/ # BottomNav + Content tab 容器
│ ├── components/ # Tamagui 卡片 / 药丸 / 菜单 / 汉堡
│ ├── gpu/ # Skia 特效:HoverText / LiquidPill / OrbitGlow / SkiaOrbBackground
│ ├── hooks/ # useFanGesture
│ ├── native/ # fs.ts / deviceInfo.ts / fileIcons.ts(bridge 包装)
│ ├── contexts/ # Theme / Language / ...
│ ├── i18n.ts # 中英双语
│ └── theme.ts # HSL 主题
├── android/ # Kotlin native modules
├── ios/
├── macos/
└── assets/屏幕清单(底部 BottomNav 7 个 tab):
| 屏 | 中文 | 状态 | 本文档 |
|---|---|---|---|
| Home | 首页 | ✅ | 01-home |
| File | 文件管理 | ✅ | 02-file |
| Repo | 软件仓库 | ✅ | 03-repo |
| Settings | 设置 | ✅ | 04-settings |
| Drivers | 驱动管理 | 🚧 WIP | — |
| HWID | HWID 轮换 | 🚧 | — |
| PrivIds | 私有 ID | 🚧 | — |
本组文档先覆盖已完成的 4 个主屏。其他屏 stable 之后补。
核心技术要点
1. 持久 su 会话池(FsModule)
Android 上调用 root 最常见的做法是每次 Runtime.exec("su -c ...")——每次都 fork 一个 shell、解析输出、关闭;开销大、状态无共享。
Shadow Nav 的 FsModule 开3 个常驻 su 会话组成池子:
┌─ JS 请求 readDir("/data") ─┐
│ │
▼ │
[Bridge] │
│ │
▼ │
┌─ 4-thread executor ─────────┴────────────┐
│ session 0: idle │
│ session 1: running chmod /data/xxx ← │
│ session 2: idle │
└──────────────────────────────────────────┘
│
▼
写 stdin: "ls /data\n" ← 发给 session 1
读 stdout 直到分隔符:timeout 作 watchdog关键设计:
- ThreadLocal
SimpleDateFormat:不要每次new,避免 GC 压力 - 阻塞 readLine + watchdog:不用 busy-wait loop,节能
- JS 侧 LRU 缓存 32 个目录:同路径重复访问秒出
非 root 模式直接用 android.system.Os.*(纯 syscall),不过池子。
2. root 模式用 nsenter -t 1 -m -- sh
普通 su 开启的 shell 在 zygote namespace 里,看不到系统全部挂载(某些 /proc 、外部存储挂载点不可见)。
nsenter -t 1 -m -- sh 切换到 PID 1(init)的 mount namespace——一进去就能看到整台机器的完整挂载视图。对文件管理器特别重要。
3. 无内核 patch 的模块装载
finit_module 是 Linux 3.8+ 的标准系统调用,insmod 内部也就是调它。Shadow Nav 直接 NDK 调 syscall:
external fun nativeFinitModule(fd: Int, params: String, flags: Int): Int
external fun nativeDeleteModule(name: String, flags: Int): Int不 spawn 任何 shell——省掉 insmod / rmmod 进程开销,也不泄露命令行到 /proc/$pid/cmdline。
4. GPU 特效全程 Skia
所有视觉效果(霓虹光晕、雪花、星空、扫描光圈)都在 Skia Canvas 里走 GPU 线程,JS 线程完全不参与渲染。
关键特效(详见各屏文档):
| 特效 | 实现 |
|---|---|
| SkiaOrbBackground | 4 层可开关:径向光晕 blob(blur 预缓存到独立 Canvas)、星空、网格、飘雪;170 粒子 worklet 批成 5 条 SkPath |
| HoverText | 文本形状 mask + 轨道径向渐变;Inter-Black + CJK fallback via Skia Paragraph API;三层:半亮底 + 轨道 + 描边 |
| Fan Rings | 里顺时针 3s + 外逆时针 4s 发光弧;SweepGradient + BlurMask;底圈低 alpha 做”死区” + 线长半径定位 |
| OrbitGlow | 每张卡片带个相位偏移的轨道光点 |
5. 坐标系那些事
Hero 标题居中 → 左上角动画:不是用 RN flexbox 变化重排,而是 position:absolute + 手算 lerp (fromX, fromY) → (toX, toY),零布局重算。
Drop zone 检测:drag 开始时 measureInWindow 记录矩形,加上容器 offset 偏移补偿(absoluteX/Y 是窗口坐标系,不是父容器的 local 坐标)。这个坑让文件列表拖进 dock 的命中判定差了 64px。
Miller Columns 横向滚动:自己写的 Pan + Animated translateX,不用 ScrollView——用了会和列内的纵向 ScrollView 抢手势。
6. 手势决议
Shadow Nav 的交互密度高(同一个 View 要响应 tap / long-press / pan / fan-commit),靠 Race + Exclusive + manualActivation 手动组合:
- Race:多个手势同时监听,谁先通过 activation criteria 谁赢
- Exclusive:父子关系手动声明(列滑动赢,行长按让路)
- manualActivation:长按成功才手动
.activate()pan 手势,避免误触
详细在 File 页有一张图。
构建 & 运行
开发
cd shadow_nav
npm install
npx pod-install ios # iOS only
npm run android # Android
npm run ios
npm run macos # macOS desktop build需要 Metro bundler 常驻(默认自动起)。
真机 root 测试
install-debug.sh 脚本会:
- 用 adb 推 debug APK
- 打开 App
- 跳到”设置 → 文件系统 → 执行模式”切到 root
- 授予
su权限(弹窗要求)
已验证设备
- OnePlus Ace 5 Pro(SM8750,Android 16,KernelSU 1.0.x)
iOS / macOS 是 bonus 目标——部分模块(FsModule root 路径)不适用,会 fallback 到 Os.*。
许可证
私有项目,暂未公开。