这个附录是给谁的如果你看到下面这些话里有任何一个不懂就该读这个附录“变量”、“函数”、循环是什么命令行怎么用cd是什么意思为什么路径有D:\和/两种写法为什么书里说0x4724 字节、f162 字节是什么意思“一个汉字占 3 个字节”——为什么乱码方块是怎么产生的JSON 是什么为什么要序列化CPU、内存、进程、线程、虚拟机——这些词听过但说不清。Git 是什么为什么要clone如果你已经懂这些可以跳过本附录直接读 ch01。本附录不涉及 Android只讲读这本书需要的最小常识。怎么用本附录不用从头背。遇到不懂的词回这里查对应小节。每节末尾都有在本项目哪里用到告诉你去哪一章看它真正的样子。A.1 编程的最基本概念这一节假设你从没写过代码。如果你写过任何一门语言可以快速浏览。A.1.1 程序是什么程序 给计算机的、一步一步的操作说明。类比程序像菜谱先切菜、再热锅、再下油、再翻炒……计算机电脑/手机只会严格按你写的顺序执行不会猜到你想干什么。所以顺序、每一步是否说清楚比聪明重要得多。一个程序通常包含几种最基本的动作动作作用类比变量记住一个值给抽屉贴标签把东西放进去条件判断根据情况走不同路“如果下雨带伞”循环重复做同一件事“把碗一个个洗直到洗完”函数把一组操作打包起个名字菜谱里的炒个底料可复用A.1.2 变量与类型变量就是一个有名字的盒子里面放一个值。score 100 ← 把 100 放进名叫 score 的盒子 name 离线AI ← 把文字放进名叫 name 的盒子类型 这个盒子里装的是哪一类东西。常见类型类型装什么例子整数整数42、-7、8999浮点数带小数的数0.3、3.14159字符串一段文字你好、Q4_K_M布尔值真 / 假true、false列表一串东西[1, 2, 3]、[a, b]⚠️为什么要有类型因为计算机对数字 42和文字 42的处理完全不同数字可以做加法文字不行。类型告诉计算机该怎么处理。在本项目哪里用到ch03 讲 Kotlin 的val/var与类型ch14 讲 GGUF 元数据的 13 种类型。A.1.3 函数函数 一段可以反复使用的操作有一个名字。函数算两个数的和 输入a, b 输出a b 用的时候 算两个数的和(3, 4) → 得到 7术语含义参数函数需要的输入上例的a、b返回值函数给出的结果上例的7调用使用一个函数这个动作类比函数像洗衣机上的按钮。你按标准洗调用它内部有一大堆步骤洗、漂、脱水但你不需要知道细节只要按按钮。这就是函数的两个好处复用按多次封装不用管内部。在本项目哪里用到ch03 讲 Kotlin 函数ch13 讲Kotlin 怎么调用 C 函数。A.1.4 条件判断与循环条件判断走不同的路如果 温度 30: 开空调 否则: 开窗循环重复做对列表里的每一个 消息: 显示它在本项目哪里用到ch16 的生成循环while (!_cancelGeneration)、ch18 的分批解码for循环。A.1.5 数据结构容器现实程序里一个值很少够用往往要装一堆值结构特点类比列表 / 数组有顺序可按下标取排队的队伍字典 / Map键 → 值按键查字典按词查释义对象 / 类一组相关字段打包一张表格姓名、年龄、地址在本项目哪里用到列表ListMessage消息列表ch03MapGGUF 元数据是MapString, MetadataValuech14对象Message/ChatSession/ModelInfo都是ch03。A.1.6 类和对象面向对象类 一张设计图纸对象 按图纸造出来的具体东西。类图纸Message 字段id、content、isUser 对象具体实例 消息1 Message(ida1, content你好, isUsertrue) 消息2 Message(idb2, content你好呀, isUserfalse)术语含义字段 / 属性类里存的数据方法类里能做的操作相当于属于这个类的函数实例化按类造出一个对象也叫创建对象在本项目哪里用到ch03 讲data classMainScreenState是一个类ch09。A.1.7 库、框架、依赖词含义类比库Library别人写好的一堆函数你拿来用五金店里买现成的螺丝框架Framework别人定好的程序骨架你在指定位置填代码买好的毛坯房水电都通了你只管装修依赖Dependency你的项目需要的别人的库菜谱里说需要现成的酱油在本项目哪里用到库Gson转 JSONch11、llama.cpp推理引擎ch14框架Jetpack Compose界面框架ch06、Gradle构建框架ch20依赖全部列在gradle/libs.versions.tomlch02。A.1.8 SDK、API、IDE词全称 / 含义一句话SDKSoftware Development Kit一整套开发工具编译器、工具、文档IDEIntegrated Development Environment写代码的软件如 Android StudioAPIApplication Programming Interface别人提供的函数清单你照它调用编译Compile把源码翻译成机器能执行的形式运行Run让程序真正执行在本项目哪里用到SDKAndroid SDKch02APIllama_decode、llama_model_load_from_file都是 llama.cpp 提供的 APIch15编译Kotlin 编译、C 编译ch20。A.2 命令行与文件路径为什么必须懂命令行本书大量操作用命令行完成构建、校验、看日志。比会用鼠标点更重要的是理解路径——因为大部分报错都和路径有关。A.2.1 什么是命令行命令行CLI 用打字给计算机下命令的界面对比用鼠标点的图形界面。为什么开发常用命令行可复制、可分享把命令贴给同事就行不用截图说明点了哪里可脚本化写进脚本一次跑一批信息更全报错原文完整显示不被界面省略。打开方式Windows按Win R输入powershell或cmd回车。A.2.2 最常用的几条命令目的Windows (PowerShell)说明看当前在哪pwd显示当前目录进入某个目录cd C:\dev\llamacpptestchange directory返回上一级cd ....表示上一级列出目录内容ls或dirlist新建目录mkdir 新名字复制文件copy 源 目标/Copy-Item看文件内容type 文件/Get-Content小文件可以大文件会刷屏清屏cls⚠️本书所有gradlew命令都必须在llama.android目录下执行——因为gradlew是那个目录里的文件。在哪个目录执行是新手最常见的错误来源之一。A.2.3 路径绝对路径 vs 相对路径类型长什么样含义绝对路径C:\dev\llamacpptest\llama.android从盘符或根开始完整相对路径llama.android、..\llama.cpp相对当前位置两个特殊符号符号含义.当前目录..上一级目录所以..\..\llama.cpp的意思是上两级再进 llama.cpp。在本项目哪里用到ch20 讲的LLAMA_SRC就是../../../../../llama.cpp上溯 5 级——数错层数是最常见的构建报错之一。A.2.4 斜杠还是反斜杠场合写法例子Windows 命令行的路径反斜杠\C:\dev\...绝大多数编程语言/配置文件正斜杠/C:/dev/...、llama.android/app/...Windows 其实也认正斜杠✅C:/dev也能用建议写代码/配置时一律用正斜杠/**。它在 Windows、macOS、Linux 上都通用能避免一类转义问题。但有个例外Java 的.properties文件里反斜杠是转义字符所以写C:\dev\Android\Sdk会被解析错必须写成C:\\dev\\Android\\Sdk或改用C:/dev/Android/Sdk。这正是 ch02 讲的local.properties那个坑。A.2.5 环境变量环境变量 系统里的一些全局设置程序可以读它们。例子JAVA_HOMEJava 装在哪PATH系统去哪找可执行程序所以你才能直接敲java。怎么读PowerShellecho$env:JAVA_HOME在本项目哪里用到ch02 讲为什么本项目不依赖 PATH而是在gradle.properties里写死 JDK 路径。A.3 数字在计算机里怎么存为什么必须懂本书里反复出现0x47、24 字节、8192、f162 字节、小端序——不理解这些ch14/ch16/ch18 会读不下去。A.3.1 位bit与字节byte单位含义位bit最小的单位只能是0或1字节byte8 个位 一个字节换算1 字节(byte) 8 位(bit) 1 KB 1024 字节 1 MB 1024 KB 1 GB 1024 MB为什么是 1024 而不是 1000因为计算机用二进制1024 2¹⁰。所以2.0 GB 的模型其实是 2 × 1024 × 1024 × 1024 ≈ 21.5 亿字节。在本项目哪里用到ch01 讲7B Q4 量化后约 3.5–4 GBch10 讲导入中途失败残留 GB 级半成品ch15 算KV cache 约 4 GB2 × 32 × 8192 × 4096 × 2 字节。A.3.2 二进制与十六进制二进制只有0和1。计算机内部全是二进制。十六进制用0–9和A–F共 16 个符号表示每 1 位十六进制 正好 4 位二进制所以它比二进制短写起来方便。十进制二进制十六进制000000101010A151111F161000010710100 0111472551111 1111FF写法约定0x开头表示十六进制。所以0x47 十进制的 71。在本项目哪里用到ch14 讲 GGUF 的 magic 是0x47 0x47 0x55 0x46——翻成 ASCII 就是G G U F见 A.4。A.3.3 “小端序”多字节数字的存放顺序一个数字如果超过 1 字节比如 4 字节的整数这几个字节谁在前方式规则小端序little-endian低位字节放前面低地址大端序big-endian高位字节放前面例子数字1用 4 字节存小端序01 00 00 00 ← 数值 1 在最前面 大端序00 00 00 01 ← 要看到最后才看到 1这就是为什么 GGUF 的 version 3 存成03 00 00 00。在本项目哪里用到ch14 的readLEUInt32LE就是 little-endian读错字节序 全盘皆错。A.3.4 浮点数与精度浮点数 带小数的数。计算机用科学计数法的形式存它。关键问题精度不同、占的字节数不同。类型占字节大概能表示的位数常见称呼FP32单精度4约 7 位有效数字floatFP16半精度2约 3–4 位f16、halfINT81整数 −128~127int8INT40.5“半个字节”16 个取值Q4这就是 ch01 讲的量化把 FP162 字节压成 INT40.5 字节体积缩 4 倍。所以7B 模型 FP16 约 14 GB → Q4 约 3.5–4 GB。也解释了 ch15 的 KV cache 计算KV cache 默认用 f16所以每个数值算2 字节。A.4 文本编码为什么一个汉字 3 字节、为什么会乱码为什么必须懂ch16 讲了一个核心机制——“汉字被 token 切成两半导致乱码”以及 33 行的 UTF-8 校验函数。不懂编码就读不懂那一章。A.4.1 计算机只认数字字符A、中、在计算机里都是数字。编码就是字符 ↔ 数字的对照规则。A.4.2 ASCII最早的编码ASCII 只覆盖英文字母、数字、常用符号每个字符用1 字节。字符ASCII 码十六进制G0x47U0x55F0x46A0x4100x30所以 GGUF 文件的头 4 字节47 47 55 46就是 “GGUF”✅ ch14 讲的就是这个。⚠️ASCII 的问题只有 128 个字符装不下中文、日文、emoji。A.4.3 UTF-8现代通用编码UTF-8 的特点变长编码——不同字符占不同字节数。字符类型占字节例子英文/数字ASCII 范围1a61拉丁扩展、希腊文等2éC3 A9中日韩汉字3中E4 B8 ADemoji 等4F0 9F 98 80这就是一个汉字 3 个字节的由来。A.4.4 UTF-8 的字节结构关键UTF-8 用字节的高几位来表示这个字符总共几字节首字节模式表示后续字节数0xxxxxxx单字节ASCII0110xxxxx双字节11110xxxx三字节汉字211110xxx四字节emoji3其它❌ 非法—后续字节必须以10xxxxxx开头。A.4.5 乱码是怎么产生的如果一段字节不满足上面的规则就不是合法的 UTF-8显示时通常变成乱码方块。在本项目里这个非法是怎么产生的因为模型是按 token 输出的而token 边界可能落在字符的中间中 E4 B8 AD 模型可能这样输出 token A → E4 B8 ← 只有 2 个字节不是完整的 UTF-8 token B → AD ← 补上第 3 个字节如果你在 token A 之后就把E4 B8转成字符串 → 非法 → 乱码方块。✅ 本工程的解法ch16.5先攒着等凑成合法 UTF-8 再发。那个 33 行的is_valid_utf8函数做的就是按上面的规则逐字节检查。一个记住它的办法“半个字不是字”—— 宁可晚一点显示也不能显示半个字。A.5 JSON程序之间传数据的通用格式为什么必须懂ch11 讲持久化时会话列表就是存成 JSON 的。A.5.1 它长什么样JSON 是一种文本格式用来表示结构化数据{id:a1b2c3,title:新对话,messages:[{id:m1,content:你好,isUser:true},{id:m2,content:你好呀,isUser:false}],createdAt:1757000000000}结构要素符号含义{ }一个对象一组键值对[ ]一个列表键: 值键值对键必须是带引号的字符串值是字符串要带双引号值是数字不用引号值是真假true/falseA.5.2 序列化与反序列化术语方向含义序列化对象 → 文本把内存里的对象拍扁成 JSON 字符串反序列化文本 → 对象把 JSON 字符串复原成对象为什么需要它因为存到磁盘和网络传输都只能处理文本/字节而程序内部用的是对象。所以两边要转换。在本项目哪里用到ch11gson.toJson(list)→ 序列化存盘gson.fromJson(json, ...)→ 反序列化读盘。ch11 讲的坏 JSON 兜底因为 JSON 是纯文本可能被改坏、被截断。反序列化失败时必须兜住否则整个列表读不出来。A.6 CPU、内存、进程、线程为什么必须懂ch01 讲为什么手机能跑大模型、ch08 讲为什么不能用 Thread、ch10 讲ANR——都建立在这些概念上。A.6.1 CPU负责算CPU中央处理器 计算机里负责执行指令、做运算的部件。相关词含义核心coreCPU 里能独立干活的小单元8 核 能同时做 8 件事主频每秒能做多少次操作越高越快指令集CPU 认识哪些指令不同 CPU 不一样在本项目里ch01 讲本工程跑在 CPU 上不是 GPUch15 讲线程数 核数 − 2最多 4ch20 讲的ABIarm64-v8a / x86_64就是CPU 的架构/指令集手机几乎都是 arm64电脑/模拟器常见 x86_64。A.6.2 内存RAM负责记内存 程序运行时的工作台程序的数据要放在这里才能被 CPU 使用。特点说明快比磁盘快非常多断电即失关机就没了所以需要存盘有限手机常见 8–16 GB在本项目里ch01 讲一个 4 GB 的模型在 8 GB 内存手机上基本跑不起来ch15 讲内存占用 模型权重 KV cachech04/ch09 讲切后台要释放模型腾内存。存内存和存磁盘的区别本书反复出现内存RAM磁盘存储速度极快慢很多关机后没了还在本项目例子聊天记录ViewModel聊天记录DataStore这就是 ch04 讲的旋转屏幕记录不丢在内存里“vs退出重开还在在磁盘里”。什么是缓存cache缓存 “算过一次的东西先放着下次直接用不重算”。它通常放在快的存储内存里因为重算慢、重读快。本书最大的缓存就是KV cachech15/ch18历史对话的计算结果存起来每新说一句话只算新的不用把整段历史重算一遍。A.6.3 进程与线程概念含义类比进程Process一个正在运行的程序一家公司的办事处线程Thread进程内的一条执行流办事处里的一个员工关键点事实说明一个进程可以有多个线程可以同时做几件事同进程的线程共享内存所以能互相看到对方的数据也所以要小心抢数据不同进程的内存是隔离的所以要通信必须走特殊机制见 A.6.4在本项目里ch01 讲整个 App 从头到尾只有一个InferenceEngine实例进程内单例ch08 讲主线程不能被阻塞、“用后台线程跑推理”ch12 讲TTS 回调跑在 Binder 线程——和主线程不同的线程所以要加锁。A.6.4 跨进程通信IPC与 Binder因为不同进程的内存隔离一个 App 想用系统或别的 App的能力就得通过一套通信机制——Android 上叫Binder。Binder 的一个关键限制一次能传的数据有大小上限约 1MB。在本项目里ch11/ch12 讲的导出对话不能直接塞EXTRA_TEXT——就是因为长对话会超过 Binder 上限TransactionTooLargeException所以要走写文件 给一个content://URI。这也解释了为什么叫跨进程“把文件分享给别的 App” 两个不同的进程在通信。A.7 编译是怎么把代码变成 App 的为什么必须懂ch01/ch20 讲构建流程ch13 讲为什么要有 JNI。A.7.1 三种执行方式方式过程例子编译型源码 →机器码→ 直接给 CPU 执行C / C解释型源码 → 逐行解释执行Python混合型虚拟机源码 → 中间码 → 虚拟机执行Java / Kotlin跑在 ART 上在本项目里Kotlin跑在 Android 的ART 虚拟机上ch01C编成机器码直接跑ch13两者语言、内存、调用规则都不一样所以要JNI 桥ch13。A.7.2 本项目的两条编译线Kotlin 代码 → Kotlin 编译器 → .class → D8/R8 → classes.dex ┐ ├→ APK C 代码 → CMake clang → .so动态库 ┘ 资源图标 → aapt2 → 编译后的资源 ┘这就是 ch20 讲的三条构建线。A.7.3 什么是 APKAPK Android 的安装包本质是个zip 压缩包里面装内容是什么classes*.dex编译后的 Kotlin/Java 代码lib/架构/*.so编译后的 C 库res/资源图标、文字等resources.arsc资源索引表AndroidManifest.xml那个身份证ch04所以unzip -l xxx.apk能看到它的内部——ch20/ch21 讲判断 ABI 只看 APK用的就是这条。A.7.4 什么是 GradleGradle 构建工具管按什么顺序、用什么工具、把代码变成 APK。它做的事下载依赖调用编译器打包资源生成 APK签名。在本项目里gradlew就是用工程自带版本的 Gradlech02。A.8 Git 最小必需为什么必须懂本项目用 Git 管代码书里也建议每次只改一个变量ch22而 Git 正是那个能精确回退的工具。A.8.1 Git 是什么Git 版本控制工具记录每一次改动让你能能力有什么用看历史三天前改了什么对比差异这次改动了哪些行回退改坏了退回上一个能用的状态分支同时试验多个方案类比Git 像游戏的存档点。每到一个里程碑就存一次档玩崩了能读回去。不能回退是新手最大的恐惧来源——有了 Git这个恐惧就没了。A.8.2 四条最常用的命令命令作用git status看当前有哪些改动最常用能救命git diff看具体改了什么内容git add .git commit -m 说明把当前改动存一个档git log --oneline看历史存档列表注意commit只存在本地不会自动上传。A.8.3 关于clonegit clone 仓库地址 把一个远程仓库完整复制到本地含所有历史。为什么很多教程让你先clone因为这样你拿到的不只是代码还有完整历史——能看到这个文件是怎么一步步变成现在这样的。这是学习真实项目的最好材料。本项目的一个相关约定ch21 强调过密钥文件*.jks和keystore.properties绝对不能提交到 Git。因为它们一旦进了仓库就等于把公章复印件发给了所有能看到仓库的人。A.9 读这本书的建议姿势A.9.1 遇到不懂的词怎么办① 先查本附录A.1–A.8 ② 再查《附录 B 术语表》中英对照 一句话 出处章节 ③ 还没懂 → 去看那一章术语表会写明在哪一章详细讲 ④ 还是不懂 → 看《附录 C 常见问题》A.9.2 三条不要不要因为不要试图一次记住所有词术语是在使用中记住的不是背出来的不要跳过动手验证那才是学会和看懂的分界线ch23 讲的不要怕报错报错是信息不是失败。本书每章都有如果失败了怎么办A.9.3 一条要要尽早让项目在你机器上跑起来。哪怕那时还看不懂代码。因为跑起来会给你正反馈而且后面每次调试都有个可用的基线。这一条在 ch01 也强调过值得再提一次。A.10 小结这个附录讲了多少个概念小节概念关键结论A.1变量/函数/条件/循环/数据结构/类/库框架/SDK·API·IDE程序是按顺序的说明书A.2命令行/路径/斜杠/环境变量在哪个目录执行很重要A.3bit/byte/二进制/十六进制/小端序/浮点精度1 字节 8 位0x47GA.4ASCII/UTF-8/乱码一个汉字 3 字节“半个字不是字”A.5JSON/序列化存盘和传输只能处理文本A.6CPU/内存/进程/线程/Binder内存快但易失Binder 有 1MB 上限A.7编译/APK/GradleAPK 是个 zipA.8Git存档点让你敢改代码A.9学习姿势别背术语、别跳验证、别怕报错读懂了这个附录你就可以开始读 ch01 了。最后提醒这个附录不是读完就过的。读正文时遇到不懂的词回来查——用的次数多了自然就记住了。