学伴AI项目安卓APP设计分析2
·
EduAI-Companion 学伴AI项目安卓APP设计分析

项目核心数据速览:
| 维度 | 关键数据 |
|---|---|
| 📐 代码规模 | App 源码 436,630 行 + llama.cpp 490,491 行 = ~927K 行 |
| 🗂️ 文件数 | 274 个 Kotlin 文件 + 36 个 C++ 文件 = 310 个源码文件 |
| 👨💻 开发周期 | 32 天(04-08 → 05-10),89 次提交,1 位贡献者 |
| 🏗️ 架构 | Clean Architecture + MVVM + Hilt DI + Jetpack Compose |
| 🧠 推理引擎 | HybridLLMEngine(云端 ↔ 端侧 ↔ MNN ↔ 模拟兜底) |
| 🎯 功能覆盖 | P0(10) + P1(14) + P2(8) + 增强(14) = 46 个功能点 |
| 🔄 开发阶段 | 初始化 → 本地推理集成 → 功能完善 → 推理深度优化(4个阶段) |

一、业务模型
1.1 产品定位
学伴AI — 面向 K12 至研究生的全学段 AI 学习伴侣,支持端侧大模型推理 + 云端 AI 双引擎,提供智能问答、学科学习、错题管理、学习规划等一站式教育服务。

1.2 目标用户
| 用户群体 | 学段 | 年级映射 | 核心诉求 |
|---|---|---|---|
| 小学生 | 小学 (Primary) | 1-6 年级 | 趣味学习、习惯养成、视力保护、基础学科 |
| 初中生 | 初中 (Middle) | 7-9 年级 | 9门主科、作文批改、实验模拟、情绪管理 |
| 高中生 | 高中 (High) | 10-12 年级 | 高考备考、议论文写作、志愿填报、生涯规划 |
| 大学生 | 大学 (College) | 13-14 级 | 专业课学习、职业探索、学习小组 |
| 研究生 | 研究生 (Graduate) | 15+ 级 | 学术研究、论文辅助、科研工具 |

1.3 业务架构

┌──────────────────────────────────────────────────┐
│ EduAI-Companion │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌────────┐ │
│ │智能问答│ │学科学习│ │错题本 │ │学习计划│ │教材中心│ │
│ └──────┘ └──────┘ └──────┘ └──────┘ └────────┘ │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌────────┐ │
│ │知识图谱│ │苏格拉底│ │学习方法│ │音乐课堂│ │公开课 │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ └────────┘ │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌────────┐ │
│ │家长控制│ │学习小组│ │手写识别│ │百科查询│ │设置管理│ │
│ └──────┘ └──────┘ └──────┘ └──────┘ └────────┘ │
└──────────────────────────────────────────────────┘
↕
┌──────────────────────────────────────────────────┐
│ AI推理引擎(核心能力) │
│ HybridLLMEngine (云端 ↔ 端侧 ↔ MNN ↔ 模拟) │
└──────────────────────────────────────────────────┘
↕
┌──────────────────────────────────────────────────┐
│ 数据层 (Room + DataStore + REST API) │
└──────────────────────────────────────────────────┘

1.4 商业模型
- 免费增值模式:基础功能免费(本地模型推理 + 核心学习功能)
- 增值服务:云端 AI 推理(阿里云百炼 API)
- 差异化优势:端侧大模型推理,无需联网即可使用 AI 辅导

二、需求清单
2.1 P0 核心需求(完成)
| ID | 需求 | 实现模块 | 状态 |
|---|---|---|---|
| P0-01 | 智能问答(文本/语音/图片输入) | feature/qa/ | ✅ |
| P0-02 | 多轮对话历史管理 | domain/usecase/chat/ | ✅ |
| P0-03 | 学科选择与提示词适配 | QAViewModel + SendMessageUseCase | ✅ |
| P0-04 | 本地 GGUF 模型加载 + 推理 | core/ai/LlamaCppEngine | ✅ |
| P0-05 | 云端 AI 推理(阿里云百炼) | core/ai/CloudLLMEngine | ✅ |
| P0-06 | 混合引擎自动切换 | core/ai/HybridLLMEngine | ✅ |
| P0-07 | 年级/学段选择与切换 | feature/grade/ | ✅ |
| P0-08 | 9门主科学习(数学/语文/英语/物理/化学/生物/历史/地理/政治) | feature/subject/ | ✅ |
| P0-09 | 错题本(拍照录入 + OCR + 间隔复习) | feature/wrongbook/ | ✅ |
| P0-10 | 学习计划制定与管理 | feature/planning/ | ✅ |
2.2 P1 功能需求(完成)
| ID | 需求 | 实现模块 | 状态 |
|---|---|---|---|
| P1-01 | 小学学段首页 | ui/screens/primary/ | ✅ |
| P1-02 | 初中学段首页 | ui/screens/middle/ | ✅ |
| P1-03 | 高中学段首页 | ui/screens/high/ | ✅ |
| P1-04 | 大学学段首页 | ui/screens/college/ | ✅ |
| P1-05 | 研究生学段首页 | ui/screens/graduate/ | ✅ |
| P1-06 | 学习报告 | feature/primary/report/ | ✅ |
| P1-07 | 习惯养成 | feature/primary/habit/ | ✅ |
| P1-08 | 视力保护 | feature/primary/eyesight/ | ✅ |
| P1-09 | 情绪调节 | feature/primary/mood/ | ✅ |
| P1-10 | 作文批改 | feature/junior/essay/ | ✅ |
| P1-11 | 实验模拟 | feature/junior/experiment/ | ✅ |
| P1-12 | 实验设计 | feature/junior/experimentdesign/ | ✅ |
| P1-13 | 议论文写作 | feature/senior/argumentative/ | ✅ |
| P1-14 | 考试分析 | feature/senior/exam/ | ✅ |
2.3 P2 功能需求(完成)
| ID | 需求 | 实现模块 | 状态 |
|---|---|---|---|
| P2-01 | 青春期情绪调节 | feature/junior/mood/ | ✅ |
| P2-02 | 高考志愿填报 | feature/senior/volunteer/ | ✅ |
| P2-03 | 生涯规划 | feature/senior/career/ | ✅ |
| P2-04 | 无障碍辅助 | feature/settings/AccessibilityScreen | ✅ |
| P2-05 | 学习小组 | feature/studygroup/ | ✅ |
| P2-06 | 考研/毕业规划 | feature/graduate/ | ✅ |
| P2-07 | 模型管理 | feature/settings/ModelManagementScreen | ✅ |
| P2-08 | 云端同步 | feature/settings/CloudSyncScreen | ✅ |
2.4 增强功能(迭代中)
| ID | 需求 | 实现模块 | 状态 |
|---|---|---|---|
| E-01 | MNN 高性能端侧推理 | core/ai/MnnLLMEngine | ✅ |
| E-02 | Agent 插件系统(计算器/翻译/百科) | core/ai/agent/ | ✅ |
| E-03 | 苏格拉底式问答 | feature/socratic/ | ✅ |
| E-04 | 归纳法/演绎法学习方法 | feature/learningmethod/ | ✅ |
| E-05 | 知识图谱 | feature/knowledge/ | ✅ |
| E-06 | 音乐教学 | feature/music/ | ✅ |
| E-07 | 手写输入/公式识别 | feature/handwriting/ | ✅ |
| E-08 | 教材中心 + PDF 阅读器 | feature/textbook/ | ✅ |
| E-09 | 离线百科 | feature/encyclopedia/ | ✅ |
| E-10 | 家长控制 | feature/parent/ | ✅ |
| E-11 | 5E 教学模式 | core/ai/FiveEInstructionEngine | ✅ |
| E-12 | MIT OCW 公开课 | feature/ocw/ | ✅ |
| E-13 | 家长引导系统 | core/ai/ParentGuideSystem | ✅ |
| E-14 | 素质教育学科(音乐/美术/体育/劳动/社会实践) | feature/subject/arts/ | ✅ |
三、系统设计
3.1 架构模式
Clean Architecture + MVVM + Hilt DI
┌──────────────────────────────────────────────┐
│ UI 层 (Jetpack Compose) │
│ Screen ←→ ViewModel ←→ StateFlow │
├──────────────────────────────────────────────┤
│ Domain 层 (纯 Kotlin) │
│ UseCase → Repository 接口 → Model │
├──────────────────────────────────────────────┤
│ Data 层 │
│ RepositoryImpl → Room DB / Retrofit │
│ / DataStore / File System │
├──────────────────────────────────────────────┤
│ Core 引擎层 │
│ HybridLLMEngine / AgentEngine / 教育算法 │
└──────────────────────────────────────────────┘
3.2 依赖注入架构(Hilt 6 模块)
| DI 模块 | 作用域 | 主要内容 |
|---|---|---|
AIModule |
Singleton | 所有 AI 引擎(Hybrid/Cloud/Local/LlamaCpp/MNN/Qwen)、模型管理器、Agent 系统、教学引擎、语音/OCR/网络管理器 |
AppModule |
Singleton | Application Context |
DatabaseModule |
Singleton | Room 数据库 + 所有 DAO |
NetworkModule |
Singleton | OkHttpClient + Retrofit |
RepositoryModule |
Singleton | 所有 Repository 实现 |
UseCaseModule |
Singleton | 所有 UseCase |
3.3 导航系统
- 路由定义:
Screen密封类(50+ 路由) - 导航图:
EduAINavigation组合函数(NavHost) - 导航 ViewModel:
NavigationViewModel管理用户状态(学段/年级) - 学段路由:
getHomeRouteForStage()根据学段 ID 映射首页

3.4 数据持久化
| 存储方式 | 用途 | 框架 |
|---|---|---|
| Room | 聊天记录、错题、用户、学习计划、学习任务 | Room 7 表 |
| DataStore | 用户偏好设置、API Key、主题选择 | Jetpack DataStore |
| 文件系统 | GGUF 模型文件、OCR 临时文件、音频缓存 | 系统文件 API |
| FileProvider | 相机拍照文件 URI | AndroidX FileProvider |
3.5 主题与 UI 风格
- Material3 Design System
- 暗黑模式支持(
ThemeManager) - 微信风格消息气泡(
WeChatMessageBubble) - 各学段差异化主题色:
- 小学:暖色活泼(橙色系)
- 初中:清新活力(蓝色系)
- 高中:沉稳庄重(深蓝色系)
- 大学:专业学术(深蓝色)
- 研究生:科研严谨(蓝灰色系)

四、功能清单

4.1 完整功能矩阵
| 模块 | 主要功能 | 组件数 | 核心类 |
|---|---|---|---|
| 智能问答 | 多轮对话、多模态输入、模型切换、Agent 插件、思考模式 | 14 | QAScreen, QAViewModel, SendMessageUseCase |
| 学科学习 | 9门主科 + 5门素质教育,各科独立学习界面 | 22 | MathGameScreen, ChineseLearningScreen 等 |
| 错题本 | OCR拍照录入、智能复习、艾宾浩斯间隔 | 7 | WrongBookScreen, ReviewScreen |
| 学习计划 | 目标制定、任务拆分、进度追踪 | 3 | LearningPlanScreen |
| 学习方法 | 归纳法/演绎法对比学习 | 5 | LearningMethodScreen |
| 苏格拉底 | 启发式对话、概念讲解 | 2 | SocraticDialogScreen |
| 知识图谱 | 知识点可视化、关系展示 | 1 | KnowledgeGraphScreen |
| 模型管理 | 模型下载、加载、切换、量化 | 4 | ModelManagementScreen |
| 音乐教学 | 钢琴键盘、音准练习、乐理课程 | 5 | MusicScreen, SoundPlayer |
| 教材中心 | PDF阅读、教材浏览、教材下载 | 3 | TextbookCenterScreen |
| 公开课 | MIT OCW 课程浏览 | 1 | OcwScreen |
| 设置 | 主题、API Key、无障碍、云同步 | 6 | SettingsScreen |

五、代码规模
5.1 统计总览
| 维度 | 数值 |
|---|---|
| 总提交数 | 89 commits |
| 总文件数(源码) | 310 个 |
| Kotlin 文件数 | 274 个 |
| C++ 文件数(项目原生) | 36 个(CMake + JNI) |
| Kotlin 代码行数 | 95,340 行 |
| C++ 代码行数(项目) | 325,022 行(含 llama.cpp JNI 桥接 + CMake) |
| XML/KTS 代码行数 | 16,268 行 |
| App 源码总行数 | ~436,630 行 |
5.2 llama.cpp 子模块
| 维度 | 数值 |
|---|---|
| 源码文件数 | 2038+ 个(含 .cpp/.h/.cu/.comp) |
| C++/C 代码行数 | ~490,491 行 |
| 磁盘占用 | 546 MB |
| 包含 | ggml 框架、llama 模型、gguf 解析、JNI 桥接 |
5.3 项目总规模(含子模块)
Kotlin 代码: 95,340 行 (10.3%)
C++ 代码: 325,022 行 (49.8%) ← 含 JNI 原生层
llama.cpp: 490,491 行 (40.0%) ← 子模块
XML/KTS: 16,268 行 (1.7%)
─────────────────────────────────────────
总计: ~927,121 行
5.4 Top 20 最大文件
| 排名 | 文件 | 行数 | 说明 |
|---|---|---|---|
| 1 | MusicLessonContent.kt | 618 | 音乐课程内容数据 |
| 2 | OcwScreen.kt | 574 | 公开课界面 |
| 3 | MusicScreen.kt | ~400 | 音乐课堂界面 |
| 4 | SendMessageUseCase.kt | ~590 | 核心消息发送用例 |
| 5 | QAViewModel.kt | ~580 | 智能问答 ViewModel |
| 6 | LlamaCppBridge.kt | ~300 | JNI 桥接层 |
| 7 | LlamaCppEngine.kt | ~330 | llama.cpp 引擎 |
| 8 | KnowledgeGraphScreen.kt | ~278 | 知识图谱可视化 |
| 9 | OcwCourse.kt | 252 | 公开课数据模型 |
| 10 | HybridLLMEngine.kt | ~220 | 混合引擎调度 |
六、端侧推理链路
6.1 推理链路全貌
用户输入 (文本/图片/语音)
│
▼
QAViewModel.sendMessage()
│
▼
SendMessageUseCase.invoke()
│
▼
generateAIResponse()
│
├── canUseLocal() ───────────────────────┐
│ │ │
│ ▼ │
│ generateLocalResponse() │
│ │ │
│ ▼ │
│ buildFullPrompt(subject, grade, msg) │
│ │ │
│ ▼ │
│ LocalLLMEngine.generateResponse() │
│ │ │
│ ├── isRealInference()? │
│ │ │ │
│ │ ├── Yes → LlamaCppEngine │
│ │ │ │ │
│ │ │ ▼ │
│ │ │ llama_jni.cpp │
│ │ │ (JNI → C++推理) │
│ │ │ │ │
│ │ │ ▼ │
│ │ │ llama.cpp (GGUF推理) │
│ │ │ │
│ │ └── No → generateMockResponse │
│ │ (引擎未就绪提示) │
│ │ │
│ ▼ │
│ return response (流式 / 完整) │
│ │
└── cloudConfigured() ─────────────────────┐
│ │
▼ │
CloudLLMEngine.generateResponse() │
│ │
▼ │
阿里云百炼 API │
(SSE 流式 / 非流式降级) │
│
└── 均不可用 → generateMockResponse() ─────┘
GGUF 模型加载全链路深度分析报告
目标设备:骁龙680 / 8GB RAM / 鸿蒙OS 4.2
分析日期:2026-05-10
编译状态:✅ BUILD SUCCESSFUL
📊 链路全景图
┌─────────────────────────────────────────────────────────────────────────┐
│ GGUF 模型加载全链路 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ [阶段0] 模型获取 │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ HuggingFace 下载 │───▶│ 镜像站点(hf-mirror)│───▶│ 本地文件系统 │ │
│ │ GgufModelDownloader│ │ 国内加速 │ │ Downloads/models/ │ │
│ └──────────────────┘ └──────────────────┘ └────────┬─────────┘ │
│ │ │
│ [阶段1] 文件验证 ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ ModelManager.scanLocalModels() → 扫描 .gguf 文件 │ │
│ │ LlamaCppEngine.isValidGgufFile() → 验证 GGUF 魔数 (0x47475546) │ │
│ │ LlamaCppEngine.parseModelInfo() → 解析模型元数据 │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │ │
│ [阶段2] 设备适配检测 ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ detectDeviceCapability() → 检测 RAM/CPU/OS │ │
│ │ calculateOptimalParams() → 计算最优 contextSize/threads │ │
│ │ detectHarmonyOS() → 鸿蒙OS兼容性检测 │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │ │
│ [阶段3] JNI 桥接加载 ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ LlamaCppBridge │───▶│ System.loadLibrary│───▶│ libllama_jni.so │ │
│ │ (Kotlin) │ │ ("llama-jni") │ │ (C++ JNI) │ │
│ └──────────────────┘ └──────────────────┘ └────────┬─────────┘ │
│ │ │
│ [阶段4] Native 模型加载 ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ llama_jni.cpp::loadModel() │ │
│ │ ├── llama_model_params_default() → 默认参数 │ │
│ │ ├── llama_model_load(modelPath, params) → 加载GGUF到内存 │ │
│ │ ├── llama_context_params_default() → 上下文参数 │ │
│ │ ├── llama_new_context_with_model(model, ctxParams) → 创建上下文 │ │
│ │ └── g_contexts.push_back() [MUTEX保护] → 注册到全局列表 │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │ │
│ [阶段5] 推理执行 ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ llama_jni.cpp::generate() / generateToken() │ │
│ │ ├── llama_tokenize() → 文本→Token序列 │ │
│ │ ├── llama_decode() → 模型前向传播 │ │
│ │ ├── llama_sample_token() → 采样下一个Token │ │
│ │ └── llama_token_to_piece() → Token→文本 │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │ │
│ [阶段6] 资源释放 ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ llama_jni.cpp::freeModel() │ │
│ │ ├── llama_free(ctx->llama_context) → 释放上下文 │ │
│ │ ├── llama_free_model(ctx->llama_model) → 释放模型 │ │
│ │ └── g_contexts.erase() [MUTEX保护] → 从全局列表移除 │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
🔍 各阶段详细分析
阶段0:模型获取
| 组件 | 文件 | 职责 |
|---|---|---|
GgufModelDownloader |
core/ai/GgufModelDownloader.kt |
HuggingFace 模型下载,支持镜像加速 |
ModelManager |
core/ai/ModelManager.kt |
本地模型扫描与路径管理 |
ModelConfigManager |
core/ai/ModelConfigManager.kt |
模型配置管理 |
关键路径:
HuggingFace → hf-mirror.com(国内镜像)→ /sdcard/Download/*.gguf
→ /data/data/.../files/models/*.gguf
隐患点:
- ⚠️ 鸿蒙OS 4.2 文件系统权限模型与标准Android不同,
Environment.getExternalStoragePublicDirectory()可能返回受限路径 - ✅ 已修复:
ModelManager.scanLocalModels()同时扫描 Downloads 和应用私有目录
阶段1:文件验证
| 组件 | 文件 | 职责 |
|---|---|---|
LlamaCppEngine.isValidGgufFile() |
core/ai/LlamaCppEngine.kt |
GGUF 魔数验证 |
LlamaCppEngine.parseModelInfo() |
core/ai/LlamaCppEngine.kt |
模型元数据解析 |
LLMDiagnostic.scanModelFiles() |
core/ai/LLMDiagnostic.kt |
诊断用模型扫描 |
GGUF 魔数验证:
private val GGUF_MAGIC = byteArrayOf(0x47, 0x47, 0x55, 0x46) // "GGUF"
private fun isValidGgufFile(file: File): Boolean {
return try {
RandomAccessFile(file, "r").use { raf ->
val magic = ByteArray(4)
raf.read(magic)
magic.contentEquals(GGUF_MAGIC)
}
} catch (e: Exception) { false }
}
隐患点:
- ⚠️ 大文件(>2GB)的 RandomAccessFile 在低内存设备上可能触发 OOM
- ✅ 已修复:
loadModel()中添加了文件大小 vs 可用内存的预检
阶段2:设备适配检测
| 组件 | 文件 | 职责 |
|---|---|---|
LlamaCppEngine.detectDeviceCapability() |
core/ai/LlamaCppEngine.kt |
设备能力检测 |
LlamaCppEngine.calculateOptimalParams() |
core/ai/LlamaCppEngine.kt |
参数自适应计算 |
LlamaCppEngine.detectHarmonyOS() |
core/ai/LlamaCppEngine.kt |
鸿蒙OS检测 |
LLMDiagnostic.runDeviceDiagnostic() |
core/ai/LLMDiagnostic.kt |
设备诊断报告 |
骁龙680 参数自适应策略:
| 内存 | 上下文长度 | 推理线程 | 说明 |
|---|---|---|---|
| < 4GB | 512 | 2 | 极低端设备 |
| 4-6GB | 768 | 2 | 低端设备 |
| 6-8GB | 1024 | 3 | 骁龙680/8GB 目标档位 |
| 8-12GB | 1536 | 4 | 中端设备 |
| > 12GB | 2048 | 4 | 高端设备 |
鸿蒙OS检测逻辑:
private fun detectHarmonyOS(): Boolean {
return try {
context.classLoader.loadClass("ohos.aafwk.ability.Ability") // 纯鸿蒙
true
} catch (e: ClassNotFoundException) {
try {
context.classLoader.loadClass("com.huawei.system.BuildEx") // 兼容模式
true
} catch (e2: ClassNotFoundException) {
Build.BRAND.equals("Huawei", true) || // 兜底检测
Build.MANUFACTURER.equals("Huawei", true)
}
}
}
阶段3:JNI 桥接层
| 组件 | 文件 | 职责 |
|---|---|---|
LlamaCppBridge |
core/ai/LlamaCppBridge.kt |
Kotlin 侧 JNI 声明 |
llama_jni.cpp |
cpp/llama_jni.cpp |
C++ 侧 JNI 实现 |
CMakeLists.txt |
cpp/CMakeLists.txt |
Native 编译配置 |
JNI 调用链:
Kotlin: LlamaCppBridge.loadModel(path, ctxSize)
→ JNI: Java_com_eduai_companion_core_ai_LlamaCppBridge_loadModel()
→ llama_model_load()
→ llama_new_context_with_model()
→ return contextId (Long)
Kotlin: LlamaCppBridge.generate(ctxId, prompt, maxTokens)
→ JNI: Java_com_eduai_companion_core_ai_LlamaCppBridge_generate()
→ llama_tokenize() → tokenize prompt
→ loop: llama_decode() + llama_sample_token()
→ llama_token_to_piece() → detokenize
→ return response (String)
关键编译配置(CMakeLists.txt):
# 16KB 页面大小兼容性(Android 14+ / 鸿蒙4.2)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
# ARM64 优化
set(GGML_SYSTEM_ARCH "ARM" CACHE STRING "")
set(GGML_CPU_KLEIDIAI ON CACHE BOOL "")
set(GGML_OPENMP ON CACHE BOOL "")
隐患点:
- ⚠️ 鸿蒙OS 4.2 使用 16KB 页面大小,未对齐的 .so 文件加载会失败
- ✅ 已修复:CMakeLists.txt 中设置
max-page-size=16384 - ⚠️ 多线程并发访问全局上下文向量导致数据竞争
- ✅ 已修复:所有
g_contexts访问添加std::mutex保护
阶段4:Native 模型加载
C++ 侧核心代码(llama_jni.cpp):
// 全局上下文管理(线程安全)
static std::vector<std::unique_ptr<LlamaContext>> g_contexts;
static std::mutex g_contexts_mutex;
static int g_next_context_id = 1;
JNIEXPORT jlong JNICALL
Java_com_eduai_companion_core_ai_LlamaCppBridge_loadModel(
JNIEnv *env, jclass clazz, jstring model_path, jint context_size) {
const char *path = env->GetStringUTFChars(model_path, nullptr);
// 1. 初始化后端
llama_backend_init();
// 2. 配置模型参数
llama_model_params model_params = llama_model_params_default();
model_params.n_gpu_layers = 0; // 骁龙680纯CPU推理
// 3. 加载模型文件
llama_model *model = llama_model_load(path, model_params);
if (!model) {
LOGE("Failed to load model: %s", path);
return 0;
}
// 4. 配置上下文参数
llama_context_params ctx_params = llama_context_params_default();
ctx_params.n_ctx = context_size; // 上下文长度
ctx_params.n_threads = 4; // CPU线程数
ctx_params.n_batch = 512; // 批处理大小
// 5. 创建推理上下文
llama_context *ctx = llama_new_context_with_model(model, ctx_params);
// 6. 注册到全局列表(线程安全)
auto llama_ctx = std::make_unique<LlamaContext>();
llama_ctx->llama_model = model;
llama_ctx->llama_context = ctx;
{
std::lock_guard<std::mutex> lock(g_contexts_mutex);
g_contexts.push_back(std::move(llama_ctx));
return g_contexts.size(); // contextId = index + 1
}
}
内存占用估算(骁龙680/8GB):
| 模型 | 量化 | 文件大小 | 加载内存 | 推理内存 | 总计 |
|---|---|---|---|---|---|
| Gemma-4-2B | Q4_K_S | ~1.5GB | ~2.0GB | ~0.5GB | ~2.5GB |
| Gemma-4-2B | Q4_K_M | ~1.8GB | ~2.3GB | ~0.5GB | ~2.8GB |
| Qwen2.5-1.5B | Q4_K_S | ~1.0GB | ~1.3GB | ~0.3GB | ~1.6GB |
| Qwen2.5-1.5B | Q4_K_M | ~1.2GB | ~1.5GB | ~0.3GB | ~1.8GB |
骁龙680/8GB 推荐使用 Qwen2.5-1.5B Q4_K_S,内存占用约1.6GB,留足系统开销
阶段5:推理执行
推理流程:
用户输入 "你好"
→ llama_tokenize() → [token_1, token_2, ...]
→ for i in 0..maxTokens:
llama_decode(ctx, batch) ← 模型前向传播(CPU密集型)
token = llama_sample_token() ← 采样策略
text = llama_token_to_piece() ← Token→文本
emit(text) ← 流式输出
推理取消机制:
// LlamaCppEngine.kt
private val inferenceMutex = Mutex()
@Volatile private var cancelRequested = false
suspend fun generateResponse(prompt: String): Flow<String> = flow {
if (!inferenceMutex.tryLock()) {
// 有正在进行的推理,取消它
cancelRequested = true
delay(100) // 等待取消生效
inferenceMutex.lock() // 阻塞等待锁释放
}
try {
cancelRequested = false
// ... 执行推理 ...
} finally {
inferenceMutex.unlock()
}
}
骁龙680 推理性能预估:
| 模型 | 量化 | 预估速度 | 首Token延迟 |
|---|---|---|---|
| Qwen2.5-1.5B | Q4_K_S | 3-5 tokens/s | 2-4秒 |
| Qwen2.5-1.5B | Q4_K_M | 2-4 tokens/s | 3-5秒 |
| Gemma-4-2B | Q4_K_S | 1-3 tokens/s | 4-8秒 |
阶段6:资源释放
JNIEXPORT void JNICALL
Java_com_eduai_companion_core_ai_LlamaCppBridge_freeModel(
JNIEnv *env, jclass clazz, jlong context_id) {
std::lock_guard<std::mutex> lock(g_contexts_mutex);
int idx = context_id - 1;
if (idx >= 0 && idx < (int)g_contexts.size()) {
auto& ctx = g_contexts[idx];
if (ctx) {
llama_free(ctx->llama_context);
llama_free_model(ctx->llama_model);
ctx.reset();
}
}
}
🛡️ 线程安全修复详情
问题
全局上下文向量 g_contexts 在多个 JNI 函数中被并发读写,存在数据竞争风险。
修复方案
为所有访问 g_contexts 的 JNI 函数添加 std::mutex 保护:
| JNI 函数 | 访问类型 | 保护状态 |
|---|---|---|
loadModel() |
写入(push_back) | ✅ std::lock_guard |
generate() |
读取 | ✅ std::lock_guard |
generateToken() |
读取 | ✅ std::lock_guard |
processPrompt() |
读取 | ✅ std::lock_guard |
tokenToPiece() |
读取 | ✅ std::lock_guard |
freeModel() |
写入(reset) | ✅ std::lock_guard |
getModelInfo() |
读取 | ✅ std::lock_guard |
applyChatTemplate() |
读取 | ✅ std::lock_guard |
testTokenize() |
读取 | ✅ std::lock_guard |
📱 骁龙680 / 8GB / 鸿蒙OS 4.2 专项分析
硬件特性
| 参数 | 值 | 影响 |
|---|---|---|
| CPU | 4×A73@2.4GHz + 4×A53@1.9GHz | 中等推理性能 |
| GPU | Adreno 610 | 不支持 GPU 加速 |
| RAM | 8GB LPDDR4x | 可加载 1-2B 量化模型 |
| 存储 | UFS 2.2 | 模型加载速度可接受 |
| 制程 | 6nm | 功耗控制良好 |
鸿蒙OS 4.2 兼容性
| 兼容项 | 状态 | 说明 |
|---|---|---|
| JNI 调用 | ✅ 兼容 | 鸿蒙兼容 Android JNI 接口 |
| .so 加载 | ✅ 已修复 | 16KB 页面大小对齐 |
| 文件访问 | ⚠️ 需注意 | 权限模型差异,使用双路径扫描 |
| 多线程 | ✅ 已修复 | mutex 保护全局状态 |
| 内存管理 | ✅ 兼容 | 标准 Linux 内存模型 |
推荐配置
模型:Qwen2.5-1.5B-Instruct-Q4_K_S.gguf (~1.0GB)
上下文长度:1024
推理线程:3
批处理大小:512
GPU层数:0(纯CPU)
🧪 诊断工具使用
1. 完整诊断
// 在 Application 或 Activity 中调用
val report = LLMDiagnostic.runFullDiagnostic(context)
Timber.i(report)
// 或
val engine = LlamaCppEngine.getInstance(context)
Timber.i(engine.getDeviceDiagnosticReport())
2. 推理链路验证
// 需要提供模型文件路径
lifecycleScope.launch {
val result = LLMDiagnostic.runInferencePipelineTest(
context = context,
modelPath = "/sdcard/Download/qwen2.5-1.5b-q4_k_s.gguf"
)
Timber.i(result.toReport())
}
3. 设备能力检测
val engine = LlamaCppEngine.getInstance(context)
val cap = engine.detectDeviceCapability()
// cap.recommendedContextSize → 推荐上下文长度
// cap.recommendedThreads → 推荐线程数
// cap.isHarmonyOS → 是否鸿蒙OS
// cap.isLowEndDevice → 是否低端设备
📋 隐患点排查清单
| # | 隐患点 | 风险等级 | 状态 | 修复方案 |
|---|---|---|---|---|
| 1 | 多线程数据竞争 | 🔴 高 | ✅ 已修复 | std::mutex 保护全局向量 |
| 2 | 16KB 页面未对齐 | 🔴 高 | ✅ 已修复 | CMake 链接器参数 |
| 3 | 鸿蒙文件权限差异 | 🟡 中 | ✅ 已修复 | 双路径扫描策略 |
| 4 | 低内存 OOM | 🔴 高 | ✅ 已修复 | 加载前内存预检 |
| 5 | 推理锁死锁 | 🟡 中 | ✅ 已修复 | 取消机制+Mutex |
| 6 | 模型文件损坏 | 🟡 中 | ✅ 已修复 | GGUF 魔数验证 |
| 7 | JNI 类名不匹配 | 🔴 高 | ✅ 已修复 | 统一类名 |
| 8 | 上下文过大OOM | 🟡 中 | ✅ 已修复 | 设备自适应参数 |
| 9 | CPU 线程过多 | 🟢 低 | ✅ 已修复 | 核心数-1策略 |
| 10 | 模型格式不兼容 | 🟡 中 | ✅ 已修复 | 格式验证+错误提示 |
🔧 涉及修改的文件清单
| 文件 | 修改类型 | 说明 |
|---|---|---|
cpp/llama_jni.cpp |
安全修复 | 添加 mutex 线程安全保护 |
core/ai/LlamaCppEngine.kt |
功能增强 | 设备检测、参数自适应、内存预检 |
core/ai/LLMDiagnostic.kt |
功能增强 | 设备诊断、推理链路验证 |
core/ai/LlamaCppBridge.kt |
接口定义 | JNI 桥接接口声明 |
core/ai/ModelManager.kt |
路径适配 | 双路径模型扫描 |
core/ai/GgufModelDownloader.kt |
下载管理 | 镜像加速下载 |
cpp/CMakeLists.txt |
编译配置 | 16KB 页面对齐、ARM64 优化 |
✅ 编译验证
BUILD SUCCESSFUL in 4m 48s
16 actionable tasks: 3 executed, 13 up-to-date
所有修改已通过 Kotlin 编译验证,无编译错误。
6.2 HybridLLMEngine 智能切换逻辑
selectAutoMode() → {
1. MNN可用 + 云端不可用 → MNN 模式
2. 本地模型已加载 + 云端不可用 → 本地模式
3. 网络质量 ≥ 80 + 云端可用 → 云端模式(质量优先)
4. 网络质量 ≥ 30 + 本地模型 → 本地模式(响应优先)
5. 以上都不满足 → 模拟兜底
}
6.3 本地模型加载链路
┌──────────────────────────────────────────────────────┐
│ 1. ModelManager.loadModel() │
│ ├─ 扫描 Downloads/ + app models/ 目录 │
│ ├─ GGUF 魔数校验 (0x47 0x47 0x55 0x46) │
│ ├─ 读取 GGUF KV 元数据 (架构/上下文/维度) │
│ └─ 返回 LoadedModelInfo │
├──────────────────────────────────────────────────────┤
│ 2. LocalLLMEngine.loadModel(info) │
│ ├─ 设置 modelPath / modelFormat │
│ ├─ llama.cpp 初始化 JNI │
│ └─ 创建推理上下文 (contextId) │
├──────────────────────────────────────────────────────┤
│ 3. LlamaCppBridge (JNI 层) │
│ ├─ System.loadLibrary("llama-jni") │
│ ├─ native 方法:initModel / createContext │
│ ├─ detectDeviceCapability() │
│ │ ├─ RAM 检测 → 决定 n_ctx 大小 │
│ │ ├─ CPU 检测 → 决定 n_threads │
│ │ └─ 存储检测 → 决定 max_model_size │
│ └─ 返回上下文 ID │
├──────────────────────────────────────────────────────┤
│ 4. llama_jni.cpp (C++ 层) │
│ ├─ llama_init_from_file(modelPath, params) │
│ ├─ llama_new_context_with_model(model, ctx_params) │
│ └─ llama_backend_init() │
├──────────────────────────────────────────────────────┤
│ 5. llama.cpp (GGUF 推理引擎) │
│ ├─ ggml 张量计算库 │
│ ├─ transformer 模型前向推理 │
│ ├─ Chat Template 格式化(内置 + 配置驱动) │
│ └─ 流式 Token 生成 → JNI 回调 → Kotlin │
└──────────────────────────────────────────────────────┘
6.4 MNN 推理链路
User → QAViewModel → LocalLLMEngine → MnnLLMEngine
→ MNN C++ JNI → MNN Interpreter + Session
→ CPU 推理(比 llama.cpp 快 ~8.6x)
→ 通过 onTokenGenerated 静态方法回调 Kotlin
6.5 推理并发控制
// QAViewModel 使用状态机防止并发
val inferenceStates = listOf(IDLE → THINKING → LOADING_MODEL → GENERATING → IDLE)
// LlamaCppEngine 使用 Mutex
inferenceMutex.withLock { /* 推理过程 */ }
// 超时保护 (10s)
val timeout = withTimeout(10000L) { /* 推理 */ }
七、模型特征

7.1 支持的模型格式
| 格式 | 引擎 | 加载方式 | 状态 |
|---|---|---|---|
| GGUF | LlamaCppEngine | JNI → llama.cpp | ✅ 主要格式 |
| SafeTensors | LocalLLMEngine | 文件检测 + 状态标记 | ✅ 检测支持 |
| TFLite | TFLiteLLMEngine / QwenEngine | TensorFlow Lite | ✅ 兼容 |
| MNN | MnnLLMEngine | MNN Framework | ✅ 高性能 |

7.2 内置模型配置(ModelManager)
| 模型别名 | 预期架构 | 格式 | 推荐场景 |
|---|---|---|---|
qwen_1_5b_edu |
Qwen2 | GGUF | 教育推理(主力) |
qwen_0_5b_edu |
Qwen2 | GGUF | 轻量教育 |
qwen_3_5_0_8b |
Qwen2.5 | GGUF | 高精度推理 |
gemma_4b_it |
Gemma2 | GGUF | 通用推理 |
qwen_0_5b_mnn |
Qwen2 | MNN | 超高性能(8.6x) |
vosk_cn_small |
Vosk | 语音 | 离线语音识别 |
7.3 模型加载策略
1. 自动扫描路径:
- /storage/emulated/0/Download/models/
- /data/data/com.eduai.companion/files/models/
2. 匹配优先级:
Gemma4E2B > Gemma > Qwen > Llama > 其他GGUF
3. 量化版本管理:
同一基础模型的不同量化版本 (Q4_K_M / Q5_K_M / Q8_0)
通过 getModelVersionGroups() 分组管理
4. 缓存策略:
10秒扫描缓存,避免频繁文件系统访问
7.4 运行时量化参数(detectDeviceCapability)
| 设备能力 | 上下文大小 | 线程数 | 批处理大小 |
|---|---|---|---|
| RAM ≥ 6GB | 4096 | 4 | 512 |
| RAM ≥ 4GB | 2048 | 2 | 256 |
| RAM < 4GB | 1024 | 1 | 128 |
7.5 云端模型特征
| 参数 | 值 |
|---|---|
| 服务商 | 阿里云百炼 |
| 协议 | OpenAI 兼容模式 |
| 端点 | /compatible-mode/v1/chat/completions |
| 模型选项 | qwen-turbo / qwen-plus / qwen-max / qwen-long |
| 流式模式 | SSE 流式(stream=true) |
| 降级策略 | 非流式请求作为降级 |
| 会话管理 | 维护最近 10 轮对话历史 |
八、Git 日志分析
8.1 提交统计
| 指标 | 数值 |
|---|---|
| 总提交数 | 89 |
| 贡献者 | 1 (david_232656) |
| 首次提交 | 2026-04-08 |
| 最近提交 | 2026-05-10 |
| 开发周期 | 32 天 |

8.2 提交类型分布
| 类型 | 数量 | 占比 |
|---|---|---|
feat: (新功能) |
20 | 22.5% |
fix: (修复) |
25 | 28.1% |
docs: (文档) |
3 | 3.4% |
refactor: (重构) |
2 | 2.2% |
| 其他(无前缀) | 39 | 43.8% |

8.3 开发阶段划分
Phase 1: 项目初始化 (04-08 ~ 04-12)
├── Initial commit, Gradle 配置, CI 设置
├── Clean Architecture 框架搭建
└── P0/P1/P2 全部功能开发完成
Phase 2: 本地推理集成 (04-16 ~ 04-22)
├── LLMEngine 重构 (GenerationConfig分离)
├── llama.cpp JNI 桥接层集成
├── 端侧推理功能多次优化迭代
├── Agent 插件系统 + 工作流
└── KSP 迁移/编译错误修复
Phase 3: 功能完善与增强 (05-01 ~ 05-04)
├── UX 升级、语音优化、思考模式精简
├── 教材中心完成
├── MNN 架构参考重构
├── 音乐课堂功能
└── 年级/学科传递 + UI简化 + 移除虚构
Phase 4: 推理深度优化 (05-07 ~ 05-10)
├── 模型加载全链路优化
├── 推理锁问题修复
├── GGUF 骁龙680/鸿蒙适配
├── MIT OCW 公开课模块
├── 推理鲁棒性增强
└── UI优化 + 内容更新

8.4 关键提交索引

| 提交 | 日期 | 说明 |
|---|---|---|
2e6313c |
04-08 | Initial commit |
15b266c |
04-11 | feat: 完成学伴AI全部功能开发(P0/P1/P2) |
98e9606 |
04-17 | feat: 集成 llama.cpp JNI 桥接层实现真实本地推理 |
88ed9eb |
04-18 | feat: Compile and integrate llama.cpp JNI library |
42266d9 |
04-22 | 完成工具调用框架和智能体体验功能集成 |
93b0d30 |
05-03 | feat: 修复Downloads扫描死循环 + 新增音乐课堂 |
076ea67 |
05-08 | Fix: 解决端侧GGUF模型推理失败问题 |
9361085 |
05-08 | 模型加载后推理功能优化 |
f0db875 |
05-10 | fix: UnsatisfiedLinkError JNI异常处理增强 |
九、技术债务与迭代建议

9.1 当前技术债务

| 问题 | 位置 | 影响 | 建议优先级 |
|---|---|---|---|
| NDK 路径硬编码 | build.gradle.kts:13 |
不同开发环境需手动修改 | 🔴 高 |
PerformanceMonitorPanel / ThinkingModePanel 未删除 |
feature/qa/components/ |
已有合并版但旧文件残留 | 🟡 中 |
generateMockResponse 完成简化后需验证无遗漏引用 |
SendMessageUseCase.kt |
可能影响降级体验 | 🟡 中 |
| SDK 版本兼容性 | build.gradle.kts |
compileSdk=36 可能需适配 | 🟢 低 |

9.2 迭代建议

| 方向 | 具体建议 | 预期收益 |
|---|---|---|
| 测试覆盖 | 添加 UnitTest / Compose Test | 质量保障 |
| CI/CD | 配置 GitHub Actions 自动化构建 | 提效 |
| 模型分发 | 内建模型下载 + 版本更新机制 | 用户体验 |
| 性能监控 | 集成 Android Vitals / Firebase | 线上监控 |
| 多语言 | 补充 i18n 国际化 | 扩展用户群 |
| 离线能力 | 增强 RAG 本地检索精度 | 场景覆盖 |
| 学习分析 | 数据驱动学习报告 + 薄弱点推荐 | 核心价值 |

更多推荐




所有评论(0)