本文面向第一天加入的开发者,目标是让你在一天内跑起项目、看懂代码、提交第一个改动。
N.E.K.O.-RN 是一个 React Native 移动端应用,核心功能是:
- 手机上显示一个 Live2D 虚拟角色
- 用户可以语音或文字与角色对话
- 角色通过 AI 后端生成回复,以语音播放,同时驱动角色口型同步
它不是独立应用,它是主项目(N.E.K.O.TONG,后端 + Web 前端)的移动端客户端。两个项目通过 WebSocket 通信。
手机(本项目) ←──WebSocket──→ AI 后端(主项目)
录音 / 播放 AI 推理 / TTS
Live2D 渲染 角色配置管理
| 工具 | 版本要求 | 验证命令 |
|---|---|---|
| Node.js | >=20 |
node -v |
| Android Studio | 最新版 | - |
| JDK | 17(不能用其他版本) | java -version |
| adb | 随 Android Studio 安装 | adb version |
通过 Android Studio → Settings → Android SDK,确保勾选:
- SDK Platforms:Android 14(API 34)
- SDK Tools:Build-Tools 34、Platform-Tools、Command-line Tools
完整步骤:android-env-macos.md
export ANDROID_SDK_ROOT="$HOME/Library/Android/sdk"
export ANDROID_HOME="$ANDROID_SDK_ROOT"
export PATH="$PATH:$ANDROID_SDK_ROOT/platform-tools"
export PATH="$PATH:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin"
export PATH="$PATH:$ANDROID_SDK_ROOT/emulator"写完记得 source ~/.zshrc。
git clone --recurse-submodules <仓库地址> -b RN
cd N.E.K.O.-RN
⚠️ --recurse-submodules非常关键。packages/react-native-pcm-stream和packages/react-native-live2d是 git submodule, 不带这个参数克隆下来是空目录,编译会直接报错。
如果已经克隆但忘了带该参数,补跑:
git submodule update --init --recursive克隆完成后,验证 pcm-stream 在正确的分支:
git -C packages/react-native-pcm-stream branch
# 应该显示 * fix-audio-echo如果显示的不是 fix-audio-echo,手动切:
git -C packages/react-native-pcm-stream checkout fix-audio-echonpm installnpx expo prebuild --platform android --clean
npm install # prebuild 后再装一次,确保原生依赖同步这一步会生成
android/目录(包含 Gradle 工程)。
cd android
./gradlew assembleDebug
cd ..APK 输出在:android/app/build/outputs/apk/debug/app-debug.apk
安装到手机:
adb install android/app/build/outputs/apk/debug/app-debug.apk或者直接用项目脚本(会自动卸载旧版再装):
bash scripts/install-apk.sh# 启动 Metro 开发服务器
npm start手机打开刚安装的 N.E.K.O. App → 扫码 → 连接成功。
之后改 TypeScript/JavaScript 代码,保存即生效,无需重新编译 APK。
| 需要重新编译 | 不需要 |
|---|---|
修改 android/ 原生代码(Kotlin) |
修改 app/、components/、hooks/ |
修改 packages/react-native-*(原生模块) |
修改 packages/project-neko-*(纯 JS 包) |
package.json 新增原生依赖 |
修改 services/、utils/ |
修改 app.config.ts |
修改样式文件 |
重新编译(不需要重新 prebuild):
cd android && ./gradlew assembleDebug && cd ..
bash scripts/install-apk.sh如果遇到奇怪的编译问题,用完整重建脚本(清缓存 + 重编 + 装机):
bash scripts/force-rebuild.shN.E.K.O.-RN/
│
├── app/ # 页面路由(Expo Router,按文件路由)
│ ├── (tabs)/
│ │ ├── main.tsx # ⭐ 主界面(Live2D + 语音聊天)
│ │ └── settings.tsx # 设置页
│ ├── audio-test.tsx # 音频调试页
│ └── qr-scanner.tsx # 扫码连接
│
├── components/ # 本项目专属 UI 组件
├── hooks/ # React Hooks
│ ├── useAudio.ts # ⭐ 语音功能入口
│ └── useLipSync.ts # 口型同步
│
├── services/ # 核心服务(非 React)
│ ├── AudioService.ts # 语音会话管理
│ ├── wsService.ts # ⭐ WebSocket 连接
│ └── android.pcmstream.native.ts # Android 录音/播放底层
│
├── utils/
│ └── MainManager.ts # ⭐ 主协调层(连接 Audio + Live2D)
│
├── android/ # 原生 Android 工程(由 prebuild 生成,不要手改)
│
└── packages/ # 内部包(部分是 git submodule)
├── project-neko-audio-service/ # ⭐ 语音会话核心逻辑
│ └── src/
│ ├── native/audioServiceNative.ts # Android 语音服务
│ └── protocol.ts # 打断状态机
├── project-neko-components/ # 跨平台 UI 组件库
├── project-neko-realtime/ # WebSocket 封装
├── react-native-live2d/ # Live2D 原生模块(submodule → main)
└── react-native-pcm-stream/ # 音频原生模块(submodule → fix-audio-echo)
用户说话
→ PCMStream(原生)采集麦克风 PCM 数据
→ audioServiceNative.ts 监听 onAudioFrame
→ 通过 WebSocket 发送 { action: "stream_data", data: [...] }
→ AI 后端处理,返回文字 + 音频
AI 回复
→ WebSocket 收到二进制 PCM 数据
→ audioServiceNative.ts 调用 PCMStream.playPCMChunk()
→ 原生层播放音频,同时触发 onAmplitudeUpdate
→ useLipSync 读取振幅 → 驱动 Live2D 口型
语音功能的核心。理解这个文件你就理解了 80% 的语音逻辑:
handleIncomingJson:处理服务端的各类消息(session_started / user_activity / audio_chunk)handleIncomingBinary:接收并播放服务端下发的音频startVoiceSession/stopVoiceSession:语音会话生命周期
WebSocket 连接管理。改连接逻辑看这里。
主界面的协调层,把 Audio 服务和 Live2D 服务连接在一起。onUserSpeechDetected、onGeminiResponse、onTurnEnd 等事件都在这里分发。
- Live2D 角色加载、点击交互、口型同步
- 语音录音 → 发送 → 接收 → 播放完整链路
- 文字聊天
- WebSocket 自动重连、心跳保活
- QR 扫码快速连接后端
- 语音打断 bug:AI 说话时打断后有音频残留,麦克风存在死锁问题
- 角色切换(轻量选择器)
- 图片发送(相册 + 拍照)
- 后台音频(切 App 不断话)
# 日常启动开发服务器
npm start
# 清缓存启动(遇到奇怪问题先试这个)
npx expo start --dev-client --clear
# 本地编译 APK
cd android && ./gradlew assembleDebug && cd ..
# 编译 + 安装一步完成
cd android && ./gradlew assembleDebug && cd .. && bash scripts/install-apk.sh
# 完整重建(清缓存 + 重编 + 装机)
bash scripts/force-rebuild.sh
# 类型检查(提交前跑一下)
npm run typecheck
# 同步主项目的 packages(有上游更新时用)
npm run sync:neko-packages开发时需要连接 AI 后端(主项目)才能测试完整功能。
方式 1:扫码连接(推荐)
- 后端启动后会生成一个 QR 码
- App 进入 QR 扫码页面,扫码后自动配置 host/port/角色名
方式 2:手动配置
- 进入 Settings 页面,填写:
- Host:后端机器的 IP 地址(和手机在同一局域网)
- Port:默认
8000 - Character Name:角色标识符
确保手机和后端机器在同一个 WiFi 下。
十有八九是 submodule 没拉下来:
git submodule update --init --recursive
# 验证
git -C packages/react-native-pcm-stream branch
# 应显示 * fix-audio-echo确认 react-native-pcm-stream 在 fix-audio-echo 分支,这个分支包含关键音频修复:
git -C packages/react-native-pcm-stream log --oneline -3
# 第一行应该是 fix: 修复 AI 语音最后几个字被截断并误识别为用户输入的问题如果不是,切过去再重新编译 APK:
git -C packages/react-native-pcm-stream checkout fix-audio-echo
cd android && ./gradlew assembleDebug && cd ..
bash scripts/install-apk.shnpx expo prebuild --platform android --clean
npm install原生模块需要重新编译 APK:
cd android && ./gradlew assembleDebug && cd ..
bash scripts/install-apk.sh- 手机和电脑必须在同一 WiFi
- 检查系统防火墙是否放通 8081 端口
- 换 tunnel 模式:
npx expo start --dev-client --tunnel
必须用 JDK 17,检查:
java -version # 应显示 17.x如果不是,在 Android Studio → Settings → Build Tools → Gradle → Gradle JDK 里切换到 17。
根据你负责的方向,重点阅读:
| 方向 | 必读文档 |
|---|---|
| 语音功能 | specs/websocket.md、specs/voice-interrupt.md |
| UI / 组件 | strategy/cross-platform-components.md |
| 环境 / 构建 | guides/android-env-macos.md、guides/upstream-sync.md |
| 整体架构 | arch/design.md |
| 问题排查 | troubleshooting/ |