ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

《离线AI安卓APP》附录 C 常见问题 FAQ

《离线AI安卓APP》附录 C 常见问题 FAQ

这个附录是给谁的
收录读者最可能卡住的问题,按主题分组。每条都给直接可用的答案,
并附上"去这一章看"。

使用建议:遇到问题时先在这里搜一下关键词(比如"构建失败"“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 foundCMake 没装sdkmanager "cmake;3.31.6"(ch02)
No toolchains found/NDK not foundNDK 没装或版本不符sdkmanager "ndk;29.0.13113456"(ch02)
Unsupported ABI: xxxCMakeLists 没处理这个 ABI改abiFilters(ch20.6.2)
add_subdirectory ... not foundLLAMA_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) ← 最后看细节

读长文件的通用方法:

  1. 先看文件头注释(本工程注释写得很好);
  2. 只看函数签名(fun xxx(...)那一行),跳过函数体;
  3. 画出结构图(哪几个函数、谁调谁);
  4. 再挑你关心的那几块深入。

详看:ch09 全章

Q13:Kotlin 语法看不懂

先明确一点:本项目只用 Kotlin 的一个子集。你不需要学完整本 Kotlin 书。

按这个顺序查:

你看到的去查
val/varch03.2
?./?:/!!ch03.3
data class/copych03.4
by(委托)ch03.5、ch09
objectch03.7
whench03.8
suspend/Flowch08
@Composablech06

然后:给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 代码很繁琐,但概念不难。

只要理解三件事:

#要点
1Kotlin 用external fun声明"实现在 C++"
2C++ 函数名必须拼成Java_包_类_方法(否则找不到)
3C++ 侧所有 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准备一批 tokench16
llama_decode真正的计算ch16
common_sampler_sample采样出下一个 tokench16/ch19
llama_model_free/llama_free释放ch15

其余的:用到再查(或者看 ch14 认识它的目录结构就够了)。

📌记住 ch14 的核心:llama.cpp 是"只调用不修改"的第三方。


C.4 运行与调试

Q17:App 装上但一打开就闪退

按这个顺序排查:

检查怎么查
① 看 logcatadb logcat,找FATAL EXCEPTION或E开头的行
② 如果是UnsatisfiedLinkError库没打进去 / ABI 不对(ch13、ch20)
③ 如果是ClassNotFoundExceptionManifest 的.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 resourcescontext/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…按顺序
会别的语言,没写过 Androidch01 → 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:下一步该学什么?

按你的兴趣选:

方向学什么为什么
深入 AndroidFragment、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.androidch02
SDK location not foundlocal.properties用正斜杠ch02
journal-1.lock拒绝访问去掉--no-daemonch02
桌面图标没变minSdk 33 走自适应图标,要换ic_launcher_foreground.webpch05
界面不刷新要产生新对象(copy),不能原地改ch03/ch09
ANR / 界面卡死耗时活要丢到后台线程ch08
错误提示看不见catch没重抛,后续代码覆盖了它ch08
生成的字有乱码方块UTF-8 被 token 切断,要加缓存ch16
第二轮开始胡说增量拼接错位(24 字节)ch17
聊久了输出乱位置错乱,要同步基准ch18
参数改了不生效六环链路某环断了(最常见是"推送"那环)ch19
APK 声称支持 x86_64 但用不了半残切片,去掉注入参数ch20

下一份:附录 D《速查索引》——按主题和按工程文件两种方式定位内容。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进