这个附录是给谁的
收录读者最可能卡住的问题,按主题分组。每条都给直接可用的答案,
并附上"去这一章看"。使用建议:遇到问题时先在这里搜一下关键词(比如"构建失败"“ANR”“乱码”)。
如果这里没有,再看每章末尾的"常见错误与排查"表。
C.1 开始之前
Q1:我完全没写过代码,能学这本书吗?
能,但要调整预期。
| 情况 | 建议 |
|---|---|
| 完全没写过代码 | 先读附录 A(补编程常识、命令行、字节/编码),再回来读 ch01 |
| 写过一点(Python/JS/Excel 宏等) | 可以直接读 ch01,遇到不懂查附录 A/B |
| 写过 Java/其它面向对象语言 | ch03 可以快速过 |
关键:本书的 ch01–ch03(第一部分)就是专门给零基础打地基的。
不要跳。
Q2:学完这本书要多久?
不做时间承诺(因人而异),但给一个节奏参考:
| 部分 | 章节 | 特点 |
|---|---|---|
| 第一部分 起步 | ch01–ch03 | 慢读(这几章决定后面顺不顺) |
| 第二部分 应用开发 | ch04–ch12 | 与 Android 开发主线重合 |
| 第三部分 原生与推理 | ch13–ch19 | 最硬(C++/JNI/推理循环),可能要反复读 |
| 第四部分 工程化 | ch20–ch23 | 相对轻松(偏流程) |
📌不要追求"读完"。追求"每章都能动手验证成功"——
这比"读完 23 章"有价值得多。
Q3:需要什么电脑配置?
| 项 | 建议 |
|---|---|
| 内存 | 16 GB 更稳(编译 llama.cpp 吃内存;8 GB 会吃力) |
| 磁盘 | 预留 30 GB 以上(SDK/NDK/CMake + 构建产物 + 模型文件) |
| 系统 | Windows / macOS / Linux 都可以(本书命令以 Windows 为例) |
| 手机 | 一台 arm64 安卓真机(模拟器跑大模型很吃力) |
⚠️关于手机:本书的 ABI 是arm64-v8a。
如果你想用模拟器,需要额外配置(见 ch20 6.(4))。
Q4:我要懂机器学习吗?
不需要。你只需要理解 ch01 讲的三个词:
| 词 | 一句话 |
|---|---|
| 量化 | 把模型压小(别人做好的,不是你做的) |
| GGUF | 模型文件的格式 |
| 推理 | 给文字,猜下一个字 |
你不需要知道Transformer 的数学、不需要会训练模型。
你要做的是"用一个训练好的模型",就像"用一台冰箱"不需要懂制冷原理。
Q5:这本书的代码都能跑吗?
分三种情况(每章代码块都标了):
| 标记 | 含义 | 能跑吗 |
|---|---|---|
| ✅ | 取自工程、已编译验证 | 能(但要先按 ch02 把环境装好) |
| ⚙️ | 取自工程、未在上板运行 | 源码真实存在,但行为依赖设备/模型 |
| 📝 | 示例(需自行验证) | 要你自己确认(通常是教学用的简化片段) |
看到 📝 就说明"作者没替你验证过"——不要直接当结论。
C.2 环境与构建
Q6:gradlew报"命令找不到"
原因:你没在llama.android目录下执行。
# ❌ 错:在别的地方执行cdllamacpptest gradlew :app:assembleRelease# ✅ 对:先 cd 进 llama.androidcdllamacpptest\llama.android gradlew :app:assembleRelease也可能:Windows 下要用gradlew.bat(或者.\gradlew)。
在 PowerShell 里推荐写.\gradlew.bat。
详看:ch02 2.5.1
Q7:构建报SDK location not found
原因:local.properties没写,或路径写错了。
排查:
# 文件在哪、内容是什么Get-Contentllamacpptest\llama.android\local.properties正确写法(二选一):
sdk.dir=C:/dev/Android/Sdk ← 推荐:正斜杠,最省心 sdk.dir=D\:\\Environment\\Android\\Sdk ← 或者:反斜杠要转义成 \\⚠️常见错误:写成
C:\dev\Android\Sdk(单个反斜杠)——
properties 文件里反斜杠是转义字符,会被解析错。详看:ch02 2.3.1
Q8:构建很慢(几分钟)
这是正常的。因为要编 llama.cpp 的几百个文件。
| 情况 | 大约耗时 |
|---|---|
| 首次构建 | 2 分钟以上(要编全部 C++) |
| 增量构建(只改 Kotlin) | 几十秒 |
| 只改 C++ | 1–2 分钟 |
想提速:
| 方法 | 说明 |
|---|---|
用clang++ -fsyntax-only校验 C++ | 几秒,不用完整构建(ch13) |
只编 Kotlin:gradlew :app:compileDebugKotlin | 约 20–30 秒 |
不要加--no-daemon | 加了会构建失败(见 Q9) |
详看:ch02 2.7、ch20
Q9:构建报journal-1.lock (拒绝访问)
原因:你给gradlew加了--no-daemon。
因为:常驻的 Gradle daemon 已经持有这个锁,--no-daemon又 fork 了一个新进程来争抢。
解决:
# 方案一:去掉 --no-daemon(推荐)gradlew :app:assembleRelease# 方案二:先停掉残留 daemongradlew--stopgradlew :app:assembleRelease📌这是本书特意强调的坑(ch02 坑 1、ch21 第 6 节)。
很多教程/工具会习惯性加--no-daemon,在本工程会直接失败。
Q10:构建报Unsupported ABI或 CMake 找不到
| 报错 | 原因 | 解决 |
|---|---|---|
CMake '3.31.6' was not found | CMake 没装 | sdkmanager "cmake;3.31.6"(ch02) |
No toolchains found/NDK not found | NDK 没装或版本不符 | sdkmanager "ndk;29.0.13113456"(ch02) |
Unsupported ABI: xxx | CMakeLists 没处理这个 ABI | 改abiFilters(ch20.6.2) |
add_subdirectory ... not found | LLAMA_SRC路径层数不对 | 按自己目录结构数(ch20、ch23.5) |
Q11:Gradle 报一堆版本不兼容(AGP/compileSdk/库)
核心认知(ch02):compileSdk/ AGP / AndroidX 库的版本是互相牵制的。
怎么读这类报错:别被前面几行吓住,找requires和Current两个词。
1. Dependency 'androidx.lifecycle:lifecycle-runtime:2.11.0' requires 'compileSdkVersion' to be set to 37 or higher. Current compileSdkVersion is 36. ↑ 它要什么 ↑ 你现在是什么解法:要么按 ch02 的版本表对齐,要么全不升。
不要"先升一半"。
C.3 代码理解
Q12:MainScreenState.kt有 1132 行,从哪读?
不要从第一行读。按 ch09 的职责分区读:
① 类的声明与依赖(:69-86) ← 先看它持有什么 ② 状态清单(:88-144) ← 再列出它管什么 ③ 会话相关(:456-543) ← 一块一块看 ④ 聊天主流程(:771-893) ← 核心 ⑤ 取消与清理(:902、:1059) ← 收尾 ⑥ 常量(:1095-1131) ← 最后看细节读长文件的通用方法:
- 先看文件头注释(本工程注释写得很好);
- 只看函数签名(
fun xxx(...)那一行),跳过函数体; - 画出结构图(哪几个函数、谁调谁);
- 再挑你关心的那几块深入。
详看:ch09 全章
Q13:Kotlin 语法看不懂
先明确一点:本项目只用 Kotlin 的一个子集。你不需要学完整本 Kotlin 书。
按这个顺序查:
| 你看到的 | 去查 |
|---|---|
val/var | ch03.2 |
?./?:/!! | ch03.3 |
data class/copy | ch03.4 |
by(委托) | ch03.5、ch09 |
object | ch03.7 |
when | ch03.8 |
suspend/Flow | ch08 |
@Composable | ch06 |
然后:给ModelInfo加一个字段并编译通过(ch03.9 的动手验证)——
这是检验"真的学会了"的最好方式。
Q14:Flow/协程完全没概念
ch08 是全书第一个难点,一遍没懂很正常。
建议的读法:
① 先看 ch08 的"本章导读"(餐厅上菜类比) ② 只看 8.3(Flow 是什么)+ 8.5(取消) ③ 跑 ch08 的"动手验证"(纯 Kotlin,不用 Android) ④ 回头看 8.2(调度器)和 8.4(flowOn) ⑤ 再读 8.6 那个事故(catch 不重抛)核心记住三句话:
- Flow = 一串随时间陆续到达的值;
emit发射、collect接收;- 取消是协作式的:cancel 只是打标记,代码要有检查点。
详看:ch08
Q15:JNI 那一章完全看不懂
先接受一件事:JNI 代码很繁琐,但概念不难。
只要理解三件事:
| # | 要点 |
|---|---|
| 1 | Kotlin 用external fun声明"实现在 C++" |
| 2 | C++ 函数名必须拼成Java_包_类_方法(否则找不到) |
| 3 | C++ 侧所有 JNI 方法第一行加锁(保护全局状态) |
然后做 ch13 的动手验证:加一个nativeAdd(a, b)并调通。
只要看到nativeAdd(3,4) = 7被打印出来,你就通了。
详看:ch13
Q16:ai_chat.cpp里的 llama.cpp API 我不认识
你不用认识全部。本工程只用了十几个 API。
必记的 6 个:
| API | 作用 | 章节 |
|---|---|---|
llama_model_load_from_file | 加载模型 | ch15 |
llama_init_from_model | 建上下文(KV cache) | ch15 |
llama_batch_init/common_batch_add | 准备一批 token | ch16 |
llama_decode | 真正的计算 | ch16 |
common_sampler_sample | 采样出下一个 token | ch16/ch19 |
llama_model_free/llama_free | 释放 | ch15 |
其余的:用到再查(或者看 ch14 认识它的目录结构就够了)。
📌记住 ch14 的核心:llama.cpp 是"只调用不修改"的第三方。
C.4 运行与调试
Q17:App 装上但一打开就闪退
按这个顺序排查:
| 检查 | 怎么查 |
|---|---|
| ① 看 logcat | adb logcat,找FATAL EXCEPTION或E开头的行 |
② 如果是UnsatisfiedLinkError | 库没打进去 / ABI 不对(ch13、ch20) |
③ 如果是ClassNotFoundException | Manifest 的.MainActivity与包名不一致(ch04.3.4) |
| ④ 如果是 C++ 崩溃(无 Java 堆栈) | 看ai-chattag 的日志;可能是 use-after-free(ch15) |
⚠️ 特别注意:C++ 崩溃不会变成 Java 异常——logcat 里只有一堆内存地址(tombstone)。
这是 ch13 强调的"跨边界出错就是崩溃"。
Q18:界面卡住不动(可能 ANR)
原因几乎总是:在主线程做了耗时操作。
| 现象 | 原因 | 章节 |
|---|---|---|
| 点发送后界面冻住 | 生成跑在主线程 | ch08(要Dispatchers.Default) |
| 退出时卡一下 | onCleared里同步卸载模型 | ch09/ch10(用cleanupScope) |
| 加载模型时界面不响应 | 加载跑在主线程 | ch15/ch08(要Dispatchers.IO) |
记住这条规则(ch08):
主线程只干两件事:更新 UI、响应用户操作。
一切耗时的活都丢到后台。
Q19:模型加载失败 / 加载很慢
| 现象 | 原因 |
|---|---|
File not found/Cannot read file | 路径不对 / 没权限 |
UnsupportedArchitectureException | 模型架构不支持(或文件损坏) |
Failed to prepare resources | context/sampler 建不起来(内存不够?) |
| 加载要几十秒 | 正常(几 GB 的文件要读,ch15.3.2 的 mmap 也有首次缺页开销) |
排查:过滤InferenceEngineImpl和ai-chat的日志,看走到哪一步。
详看:ch15
Q20:生成很慢(每秒几个字)
这是物理限制,不是 bug。
| 影响因素 | 说明 |
|---|---|
| 手机 CPU | 本工程跑在 CPU 上(ch01.2.4) |
| 模型大小 | 7B 比 1.5B 慢很多 |
n_threads | 本工程取"核数−2,最多 4"(ch15.4.1) |
| 上下文长度 | 越长越慢(prefill 更久) |
想看具体速度:用设置页的"推理基准测试"(ch01 见过的benchModel),
看tg(每秒 token)。
Q21:中文输出出现乱码方块(�)
这是 ch16.5 讲的 UTF-8 被切断问题。
确认方法:看是不是"偶尔出现一个或几个方块"(而不是全乱)。
如果是——就是 token 把汉字切成了两半。
修复:用 ch16.5 的 UTF-8 缓存(cached_token_chars+is_valid_utf8)。
自检:在is_valid_utf8的两个分支加日志,
你会偶尔看到UTF8 PENDING——那就是拦截到了半个字符。
Q22:生成第二个字就停 / 一直不结束
| 现象 | 原因 | 章节 |
|---|---|---|
| 生成一个字就停 | stop_generation_position设错了 | ch16.2.3 |
| 一直不结束 | 没判断 EOG / 没设上限 | ch16.4.6 |
| 点"停止"没用 | 缺取消检查点 | ch08.5、ch10.2 |
Q23:多轮对话第二轮开始胡说 / 只输出数字
这就是 ch17 那个经典事故。
先确认:是不是"第 1 轮正常、从第 2 轮起不正常"?
是 → 去看 ch17(根因是"按长度切片"求增量导致的 24 字节偏移)。
自检:看chat_add_and_format的日志,
第 2 轮的增量应该完整包含你的问题(而不是被截断)。
Q24:聊久了输出变乱
这是 ch18 讲的位置错乱。
特征:需要聊到上下文顶满(本工程 8192 token)才出现,
而且一旦出现就持续(之后每轮都错)。
为什么难查:不报错——llama_decode不检查位置合理性(ch18.5)。
对策(ch18 练习 5):加位置不变量检查(current_position ≤ n_ctx)。
Q25:怎么调试 C++ 代码
四条路(按推荐顺序):
| 方法 | 什么时候用 |
|---|---|
clang++ -fsyntax-only | 改完先验语法(几秒,ch13.9) |
LOGd/LOGi/LOGe | 打日志,用adb logcat -s ai-chat看 |
| 纯逻辑模拟 | 位置/算法类问题(ch22) |
| 真机 + tombstone | 真崩溃时(adb logcat里搜DEBUG) |
⚠️ 注意:
LOGv(verbose)在很多设备上默认不输出,调试时用LOGd更稳。
C.5 模型相关
Q26:模型文件去哪找?
本书不能给你下载链接(版权/时效问题),但可以告诉你去哪找:
| 渠道 | 说明 |
|---|---|
| Hugging Face(huggingface.co) | 搜模型名 +GGUF,最主流 |
| ModelScope(modelscope.cn) | 国内,速度快 |
| 模型作者官方仓库 | 有些会直接提供 GGUF |
搜索关键词:<模型名> GGUF,例如Qwen2.5 GGUF、Hy-MT2 GGUF。
选量化版本:
- 内存紧张 →Q4_K_M(本工程测试用的就是 Q4_K_M);
- 想要质量更好、内存够 →
Q5_K_M/Q6_K/Q8_0。
详看:ch01.2.2(量化)、ch14.5(FileType 枚举)
Q27:我的手机能跑多大的模型?
用 ch15.5 的公式估算内存:
总内存 ≈ 模型文件大小 + KV cache KV cache ≈ 2 × 层数 × n_ctx × (KV头数×每头维度) × 2 字节快速估算表(Q4 量化、8K 上下文):
| 模型 | 文件大小 | KV cache | 总需 |
|---|---|---|---|
| 1.5B | 约 1 GB | 约 0.3 GB | 约 1.3 GB |
| 3B | 约 2 GB | 约 0.6 GB | 约 2.6 GB |
| 7B(MHA) | 约 4 GB | 约 4 GB | 约 8 GB⚠️ |
| 8B(GQA) | 约 4.7 GB | 约 1 GB | 约 5.7 GB |
⚠️注意 7B MHA 的 KV cache 就有 4 GB(ch15.5.3 算过)。
而手机还要给系统和别的 App 留内存。
所以 8 GB 内存的手机跑 7B 很勉强。📌建议从小模型开始:1.5B–3B 的 Q4 模型,先在手机上跑通,
再往大的试。
Q28:模型太大装不进手机 / 导入失败
| 问题 | 解决 |
|---|---|
| 手机存储不够 | 用更小的量化(Q3/Q4)或更小的模型 |
| 导入中途失败 | 看 ch10(可能残留半成品,要清理) |
| 导入后加载失败 | 内存不够(KV cache 建不起来) |
注意:导入会把模型拷贝到 App 私有目录(ch10),
所以需要两份空间(原文件 + 副本)。
可以先删掉原文件再导入吗?不行——导入时源文件还要用。
Q29:模型不进 APK 吗?
不进。这是刻意的(ch01.1.3):
| 原因 | 说明 |
|---|---|
| 体积 | 模型 GB 级,进 APK 用户下载不了 |
| 不联网 | 本 App 不联网,所以不能自动下载 |
| 用户自己选 | 用户从文件选择器导入(ch12) |
所以发布 APK 只有 97 MB 左右(ch21),模型另外给用户。
C.6 关于学习本身
Q30:读完一遍还是记不住,正常吗?
完全正常。而且"记住"不是目标。
真实的学习过程是:
第 1 遍:知道"有这么个东西"(能看懂名词) 第 2 遍:知道"它在哪、干什么用"(能定位) 第 3 遍:知道"为什么这么做、不这么做会怎样"(能理解) 动手做 1 次:真的会了 讲给别人听 1 次:再也忘不掉所以:
- 不要指望一遍就懂;
- 但一定要动手(第 4 步是分水岭);
- 讲给别人听是终极检验(能讲清说明真懂)。
Q31:我应该按顺序读吗?
大致按顺序,但可以调整:
| 你的情况 | 建议路线 |
|---|---|
| 完全零基础 | ch01 → ch02(只求跑通,别纠结报错)→ch03(慢读)→ ch04…按顺序 |
| 会别的语言,没写过 Android | ch01 → ch02 → ch03(快过)→ ch04 → ch06 → ch09 → ch13 → ch14 → ch16(尽早打通"能出字")→ 再回头补 |
| 会 Android,想搞懂端侧推理 | ch01(只看架构和规模)→ ch13 → ch14 → ch15 → ch16 → ch17 → ch18 → ch19 → ch22 |
详看:ch01.7.3
Q32:哪些章可以跳过?
建议不要跳这几章(它们是"地基"):
| 章 | 为什么不能跳 |
|---|---|
| ch03 Kotlin | 不然后面所有 Kotlin 代码都看不懂 |
| ch04 Android 骨架 | 不然后面不知道 App 怎么跑起来 |
| ch08 协程与 Flow | 不然后面"流式输出"完全没法理解 |
| ch22 诊断方法论 | 不然后面遇到问题只能靠猜 |
可以快速浏览的:
| 章 | 为什么 |
|---|---|
| ch05 资源系统 | 日常开发用不到那么深,遇到再回来看 |
| ch21 签名发版 | 不发布的话可以先跳过 |
Q33:读完能自己做一个类似的 App 吗?
能,但要注意两点:
① 你会具备的能力(本书 ch01.7.2 列过):
- 搭 Android + NDK 环境并出包;
- 用 Compose 写界面、用 ViewModel 管状态;
- 用协程/Flow 处理流式数据;
- 读写 GGUF、改 JNI 与推理循环;
- 从零复刻一个最小可用版本。
② 但"换个模型/换个需求"仍需适应:
- 换模型 → chat template 不同(ch17 的教训);
- 换功能 → 需要自己设计(ch23 的"加回顺序"是方法论)。
📌关键:本书教的不只是"这个 App 怎么做",
还有**“遇到没做过的事怎么下手”**(ch22/ch23)。
后者才是能带走的能力。
Q34:下一步该学什么?
按你的兴趣选:
| 方向 | 学什么 | 为什么 |
|---|---|---|
| 深入 Android | Fragment、Navigation、Room、WorkManager、测试 | 本书只覆盖了 Compose + ViewModel 这一支 |
| 深入 Compose | 自定义 Layout、动画、性能优化、Preview/测试 | 本书只讲了基础 |
| 深入推理 | llama.cpp 源码、GPU/NPU 加速、更多量化格式 | 本书"只调用不改" |
| 深入工程化 | CI/CD、自动化测试、多模块、依赖注入 | 本书只讲了一次性发版 |
| 做完整产品 | 多语言、无障碍、埋点、崩溃收集、上架流程 | 从"能跑"到"能卖" |
推荐的第一步:把 ch23 的最小版真的做出来,然后按 23.10 的顺序加功能。
做完这个,你就从"读者"变成了"开发者"。
Q35:我卡住了怎么办?
按这个顺序:
① 查本附录 C(FAQ) ② 查该章末尾的"常见错误与排查"表 ③ 查附录 B(术语表)确认你没理解错某个词 ④ 用附录 A 的"学习姿势"(A.9)检查方法 ⑤ 用 ch22 的五步框架自己排查: 复现 → 隔离变量 → 最小对照实验 → 修正 → 回归 ⑥ 实在不行:把问题"最小化"—— 能不能用最简单的输入复现?能不能只保留几行代码?💡最有用的一招:把问题最小化。
一个能稳定复现的 20 行代码,比"我的项目不行"有用一万倍。
这是 ch22 讲的"最小对照实验"的日常版。
C.7 一句话速查(最常见的 12 个坑)
| 现象 | 一句话答案 | 章节 |
|---|---|---|
gradlew找不到 | 要先cd到llama.android | ch02 |
SDK location not found | local.properties用正斜杠 | ch02 |
journal-1.lock拒绝访问 | 去掉--no-daemon | ch02 |
| 桌面图标没变 | minSdk 33 走自适应图标,要换ic_launcher_foreground.webp | ch05 |
| 界面不刷新 | 要产生新对象(copy),不能原地改 | ch03/ch09 |
| ANR / 界面卡死 | 耗时活要丢到后台线程 | ch08 |
| 错误提示看不见 | catch没重抛,后续代码覆盖了它 | ch08 |
| 生成的字有乱码方块 | UTF-8 被 token 切断,要加缓存 | ch16 |
| 第二轮开始胡说 | 增量拼接错位(24 字节) | ch17 |
| 聊久了输出乱 | 位置错乱,要同步基准 | ch18 |
| 参数改了不生效 | 六环链路某环断了(最常见是"推送"那环) | ch19 |
| APK 声称支持 x86_64 但用不了 | 半残切片,去掉注入参数 | ch20 |
下一份:附录 D《速查索引》——按主题和按工程文件两种方式定位内容。