Skip to content

如何使用这套文档

建议 8 分钟读法版本基线源码导航

这不是 API 手册,也不是“每个目录一句话”的索引。它的目标是帮助你建立一个能预测代码位置和运行行为的模型:看到一个现象时,你大致知道状态属于谁、事件从哪里来、谁负责持久化,以及在哪一层下断点最划算。

每章的固定结构

每章都尽量回答四层问题:

  1. 是什么:先给对象关系和职责边界;
  2. 怎么跑:用真实调用链串起关键函数;
  3. 为什么:解释一致性、生命周期、性能或跨平台方面的取舍;
  4. 怎么验证:给出断点、日志与不变量,避免停在“看懂代码”的错觉里。

源码链接都指向上游 v7.0.6 tag。正文使用 Telegram/SourceFiles/... 作为路径起点;lib_rpllib_base 等子模块在当前源码包中可能只保留子模块入口,因此相关章节以它们在主项目中的调用方式为重点。

三条推荐路线

路线 A:第一次接触 tdesktop

01 → 03 → 04 → 05 → 06 → 07 → 08 → 09 阅读。你会先建立所有权树,再把请求、更新和界面串起来。不要一上来钻进 history_widget.cpp:它超过一万行,但只是许多状态汇合后的显示层。

路线 B:带着真实问题

先把现象归类:

现象第一站第二站
启动卡住、重复进程、账号未恢复第 3 章第 11 章
请求没有回调、DC/代理异常第 4 章第 3 章
消息漏了、顺序错了、重复了第 5 章第 7 章
点击发送后本地状态不一致第 8 章第 5 章
页面跳转或订阅泄漏第 9 章第 6 章
下载慢、进度停住、缓存失效第 10 章第 11 章
通话状态和 UI 不一致第 12 章第 9 章

路线 C:准备改代码

先读第 2 章确认生成文件与手写文件边界;再读目标子系统章节;最后读第 13、14 章确认平台差异、构建目标和验证策略。Telegram Desktop 的常见成本不是“不会写 C++”,而是误改了错误的层、绕过了状态机或忘记让订阅跟随 owner 的 lifetime。

四个阅读纪律

1. 区分所有权、引用和观察

std::unique_ptr 通常暴露所有权树;not_null<T*> 表示非空借用;base::weak_ptr / base::weak_qptr 用于跨异步边界;rpl::lifetime 决定订阅何时终止。读一个类时先看成员字段,再看方法体,往往比从构造函数一路单步更快。

2. 区分命令与事实

用户点击“发送”是命令;本地插入 sending item 是预测;服务端 Updates 才是被确认的事实。类似地,界面调用 showPeerHistory 是导航命令,真正的页面状态由 memento、active entry 和 data producer 共同决定。

3. 区分全局序列和频道序列

全局 Updates 有 seq/pts/qts/date,频道还维护自己的 PTS。看到缺口恢复时,先确认代码走的是 updates.getDifference 还是 updates.getChannelDifference,不要把两套恢复路径混为一谈。

4. 区分内存实体与视图元素

HistoryItem 是消息领域对象;HistoryView::Element 是它在某个视图中的表现;同一 item 可能没有 main view,也可能因虚拟化而暂时没有可见 element。UI bug 不一定等于数据缺失。

如何跟着源码验证

一个实用的验证循环:

  1. 从章节给出的入口函数下断点;
  2. 只记录对象身份(account/session/peer/fullMsgId)和状态转移;
  3. 确认事件发生在哪个线程;
  4. 找到下一层的“唯一事实源”,而不是追每个 signal;
  5. 在退出页面、切账号或失败重试后再走一遍,验证清理路径。

不要用行号当架构

行号只是固定版本的定位工具。真正值得记住的是类的所有权、调用方向和状态不变量。升级版本后先搜索符号,不要机械套用旧行号。

独立学习资料 · 基于 Telegram Desktop v7.0.6