ReadViewSDK/Doc/WXRead/analysis/01_核心架构与数据流图.md

445 lines
34 KiB
Markdown

# 读书 EPUB 阅读器 — 核心架构与数据流图
> 目标: WeRead v10.0.3 (Build 79)
---
## 一、模块拓扑与依赖关系图
### 1.1 App 整体模块拓扑
```
┌─────────────────────────────────────────────────────────────────────┐
│ WeRead.app (iOS/macOS Catalyst) │ │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ WeRead │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ │
│ │ │ 阅读器模块 │ │ 书架模块 │ │ 其他业务模块 │ │ │
│ │ │ WRReader* │ │ WRBookshelf* │ │ WRDiscover/WRMarket │ │ │
│ │ │ WREpub* │ │ WRBook* │ │ WRAccount/WRSetting │ │ │
│ │ │ DTCoreText* │ │ │ │ WRCommunity/WRChat │ │ │
│ │ │ WRPage* │ │ │ │ WRActivity/WRAI │ │ │
│ │ │ WRChapter* │ │ │ │ ... │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────────────┘ │ │
│ │ ┌──────────────────────────────────────────────────────────┐│ │
│ │ │ ObjC 元数据 (未剥离): ││ │
│ │ │ __objc_classlist: 0xb230 (类列表) ││ │
│ │ │ __objc_classname: 0x25a35 (~154KB, 17584 个类名) ││ │
│ │ │ __objc_methname: 0x24ba5a (~2.4MB 方法名) ││ │
│ │ │ __objc_methtype: 0x382bc (~226KB 类型编码) ││ │
│ │ └──────────────────────────────────────────────────────────┘│ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────── Frameworks ────────────────────────────┐ │
│ │ │ │
│ │ ┌─────────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ hermes.framework│ │QMUIKit.fwk │ │WRSharedMod* │ │ │
│ │ │ (RN JS 引擎) │ │(腾讯UI组件库) │ │(共享业务模块) │ │ │
│ │ │ v0.12.0 │ │ │ │ │ │ │
│ │ │ Facebook Hermes │ │ QMUI 配色/ │ │ 书架/发现/ │ │ │
│ │ │ 替代 JSC │ │ 主题/组件 │ │ 社区/活动 │ │ │
│ │ └─────────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌──────────────┐ │ │
│ │ │ WRUICore.fwk │ │CocoaMarkdown │ │ │
│ │ │ (UI核心+字体) │ │(Markdown渲染)│ │ │
│ │ │ 含 WeReadLS 系列│ │ │ │ │
│ │ │ 含 SharpGrotesk │ │ │ │ │
│ │ └─────────────────┘ └──────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────── RNBundles (39个 .hbc) ─────────────────┐ │
│ │ aiSearch bookDetail browse community discoverV2 market │ │
│ │ account medals memberDetail gameCenterHome freeBooks ... │ │
│ │ (Hermes 编译的 HBC 字节码, 业务动态化) │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────── Bundles ────────────────────────────────┐ │
│ │ OnePatch.bundle (ObjC热修复, JS驱动) │ │
│ │ MidasIAPSDK.bundle (腾讯米大师支付) │ │
│ │ Sentry.bundle (崩溃监控) │ │
│ │ QCloudCOSXML.bundle (腾讯云COS存储) │ │
│ │ MJRefresh.bundle (下拉刷新) │ │
│ │ mathFonts.bundle (数学字体: Latin Modern/XITS/TeX Gyre) │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────── App Extensions ─────────────────────────┐ │
│ │ ActionExtension.appex (操作扩展) │ │
│ │ ModernWidgetExtension.appex (新版小组件 WidgetKit) │ │
│ │ NotificationService.appex (通知服务) │ │
│ │ ShareExtension.appex (分享扩展) │ │
│ │ Widget.appex (旧版小组件) │ │
│ └────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
系统框架依赖:
┌────────────────────────────────────────────────────────────┐
│ CoreText CTTypesetter / CTFramesetter / CTFrame │
│ CTLine / CTRun / CTFontCreatePathForGlyph │
│ CoreGraphics CGContext / CGPath / CGAffineTransform │
│ UIKit UIView / UIPageViewController / Gesture │
│ WebKit WKWebView (仅公众号/文集路径) │
│ Foundation NSAttributedString / NSXMLParser │
│ Security Keychain (加密密钥存储) │
│ AVFoundation TTS 朗读音频播放 │
│ PencilKit Apple Pencil 手写笔记 │
│ ImageIO 图片解码与缓存 │
└────────────────────────────────────────────────────────────┘
```
### 1.2 EPUB 渲染模块内部依赖图
```
WRReaderViewController (431 methods, 阅读器主控制器)
├─→ WRPageViewController (80 methods, 翻页控制器)
│ │ 基于 UIPageViewController 封装
│ │ 支持 UIPageCurl (仿真翻页) / Scroll (左右滑动)
│ │ 包含 4 个故障修复补丁
│ │
│ └─→ WRPageView (105 methods, 页面渲染视图)
│ │ 继承 UIView, drawRect: 中直接 CGContext 绘制
│ │ 不使用 UILabel / UITextView
│ │
│ ├─→ WRCoreTextLayoutFrame (4 methods, 单页排版帧)
│ │ │ drawInContext:image:size:inRect:position:
│ │ │ avoidPageBreakInsideByRemovingLastLinesIfNeeded
│ │ │ getRenderHeight / lines
│ │ │
│ │ └─→ DTCoreTextLayoutFrame (56 methods, DTCoreText 排版帧)
│ │ │ 封装 CTFrame, 提取 CTLine 列表
│ │ │ hitTestWithPoint: (文本选择)
│ │ │
│ │ └─→ DTCoreTextLayoutLine (61 methods, 排版行)
│ │ │ 封装 CTLine, 提取 CTRun 列表
│ │ │ typographicBounds / trailingWhitespaceWidth
│ │ │
│ │ └─→ DTCoreTextGlyphRun (23 methods, 字形渲染)
│ │ │ 封装 CTRun
│ │ └─→ newPathWithGlyphs (CTFontCreatePathForGlyph)
│ │
│ └─→ 文本选择 (CoreText hit test)
│ CTLineGetStringIndexForPosition (坐标→字符索引)
│ CTLineGetOffsetForStringIndex (字符索引→坐标)
├─→ WRChapterData (125 methods, 章节数据模型)
│ │ 存储排版后的 NSAttributedString
│ │ 管理高亮/下划线/书评标注
│ │
│ ├─→ WRCoreTextLayouter (20 methods, WeRead CoreText 封装)
│ │ │ 管理 CTTypesetter / CTFramesetter
│ │ │ pageBackgroundImageAtRange:themeBgColor:
│ │ │ resizedImageForImagePath:rect:position:sizePattern:darkMode:themeBgColor:
│ │ │
│ │ └─→ DTCoreTextLayouter (12 methods, DTCoreText 排版器)
│ │ │ 封装 CTTypesetter, 缓存 DTCoreTextLayoutFrame
│ │ │
│ │ └─→ CTTypesetter / CTFramesetter (系统 CoreText)
│ │
│ └─→ WRChapterPageCount (40 methods, 分页计算)
│ rangeValueWithPageInfo: (计算每页 NSRange)
│ currentCacheKeyWithBookId: (缓存键)
├─→ WREpubTypesetter (9 methods, EPUB 排版器入口)
│ │ attributeStringWithFilePath:... (核心类方法)
│ │
│ └─→ DTHTMLAttributedStringBuilder (25 methods, HTML→NSAttributedString)
│ │ _buildString (核心构建)
│ │ parser:didStartElement:attributes:position: (SAX 回调)
│ │ parser:foundCharacters:position:
│ │ parserDidEndDocument:
│ │
│ ├─→ DTHTMLParser (19 methods, HTML SAX 解析器)
│ │ 基于 libxml2 封装
│ │
│ ├─→ DTHTMLElement (168 methods, HTML 元素模型)
│ │ │ applyStyleDictionary:isLatinLanguageBook: (CSS 应用)
│ │ │ attributedString (生成富文本)
│ │ │ interpretAttributes (属性解析)
│ │ │
│ │ ├─→ DTCoreTextFontDescriptor (字体描述)
│ │ ├─→ DTCoreTextParagraphStyle (段落样式)
│ │ ├─→ DTCSSStylesheet (CSS 样式表)
│ │ └─→ DTTextAttachment (附件: 图片/视频/iframe)
│ │
│ └─→ CSS 级联: default.css → replace.css → dark.css → epub内嵌 → 用户设置
├─→ WREpubParser (12 methods, EPUB 文件解析)
│ │ 解析 container.xml / content.opf / toc.ncx
│ │ 输出: 章节列表 + 资源映射
│ └─→ WREpubPositionConverter (22 methods, 位置转换)
│ 文件位置 ↔ 字符位置 映射
├─→ WRChapterDownloadManger (47 methods, 章节下载)
│ │ RAC 任务队列 + 并发控制
│ │
│ ├─→ WRBookNetwork (155 methods, 网络层)
│ │ │ loadTarForEpubBookId:chapter:isPreload: (下载加密 ZIP)
│ │ │ handleUnzipWithBookId:zipPath:encryptKey:plainBookDirectory: (解密解压)
│ │ │ processEncryptedBookFileAtPath:encryptKey:book:chapterUid: (文件处理)
│ │ │ fileContentWithChapter:book:shouldRemoveHtmlTags: (读取内容)
│ │ │
│ │ └─→ WREncryptedFileManager (7 methods, DRM 管理)
│ │ decryptContentsOfFile:forBookId:isFileLost: (AES 解密)
│ │ encryptFileForBookId:originalEncryptKey:atPath:toPath: (本地加密)
│ │ keyForBookId: (密钥管理)
│ │
│ └─→ WRPreloadBookManager (76 methods, 预加载管理)
│ saveEncryptKey:forPath:bookId: (密钥缓存)
│ _preloadWholeBook:scene: (整书预加载)
├─→ 标注系统
│ ├─→ WRPageHighlight (3 methods, 高亮)
│ ├─→ WRPageUnderline (10 methods, 下划线: solid/dashed/wavy/dotted)
│ ├─→ WRPageMark (25 methods, 书签标注)
│ ├─→ WRBookmark (58 methods, 书签模型)
│ └─→ WRReaderPencilNoteManager (11 class methods, Pencil 笔记)
└─→ 附件系统 (WRPageAttachments)
├─→ WRPageImageAttachment (27 methods, 图片附件)
├─→ WRPageHyperlinksAttachment (超链接)
├─→ WRPageChapterToolAttachment (章节工具)
├─→ WRPageFlyleafAttachment (扉页)
├─→ WRPageCodeView (代码块)
├─→ WRPageVideoView (视频)
└─→ WRPageTableAttachment (表格)
```
---
## 二、核心业务数据流图 (DFD)
### 2.1 EPUB 内容数据流 (服务器 → 屏幕像素)
```
┌──────────┐ HTTPS ┌──────────────────────────────────────────────────┐
│ 读书 │─────────────→│ ① WRBookNetwork.loadTarForEpubBookId:chapter: │
│ 服务器 │ 加密 ZIP │ isPreload: │
│ │ {bookId}_ │ ↓ │
│ │ DECRYPT.zip │ ② WRBookNetwork.handleUnzipWithBookId: │
│ │ │ zipPath:encryptKey:plainBookDirectory: │
│ │ │ ↓ │
│ │ │ ③ WREncryptedFileManager │
│ │ │ decryptContentsOfFile:forBookId:isFileLost: │
│ │ │ 密钥来源: keyForBookId: (Keychain) │
│ │ │ ↓ 解密后 XHTML 文件 │
│ │ │ │
│ │ │ ④ WRBookNetwork.fileContentWithChapter: │
│ │ │ book:shouldRemoveHtmlTags:filterTranslate: │
│ │ │ ↓ 读取 XHTML 内容 │
│ │ │ │
│ │ │ ⑤ WREpubTypesetter.attributeStringWithFilePath: │
│ │ │ ↓ CSS 级联 (5层) │
│ │ │ ↓ DTHTMLAttributedStringBuilder._buildString │
│ │ │ ↓ DTHTMLParser (SAX) → DTHTMLElement (DOM) │
│ │ │ ↓ DTHTMLElement.attributedString │
│ │ │ ↓ 输出: NSAttributedString │
│ │ │ │
│ │ │ ⑥ WRCoreTextLayouter (CTTypesetter 分页) │
│ │ │ ↓ CTTypesetterSuggestLineBreak (逐行排版) │
│ │ │ ↓ 每页一个 CTFrame │
│ │ │ ↓ WRChapterPageCount (NSRange 映射) │
│ │ │ │
│ │ │ ⑦ WRCoreTextLayoutFrame (单页排版帧) │
│ │ │ ↓ CTFrameGetLines → CTLine 列表 │
│ │ │ ↓ avoidPageBreakInside (截断保护) │
│ │ │ │
│ │ │ ⑧ WRPageView.drawRect: │
│ │ │ CGContextTranslateCTM (坐标翻转) │
│ │ │ CTFrameDraw (绘制文字) │
│ │ │ 绘制图片/高亮/下划线/选择 │
│ │ │ ↓ │
└──────────┘ └──────────────────────────────────────────────────┘
屏幕像素渲染
```
### 2.2 加密密钥数据流 (DRM 保护链)
```
┌─────────────────────────────────────────────────────────────────────────┐
│ DRM 密钥生命周期 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ │
│ │ 服务器 │─── encryptKey (每章独立) ───┐ │
│ └──────────┘ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ WRPreloadBookManager │ │
│ │ saveEncryptKey:forPath:bookId: │ │
│ │ ↓ 存储到: NSUserDefaults (临时) │ │
│ │ encryptKeyForPath:bookId: (读取) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ WREncryptedFileManager │ │
│ │ keyForBookId: │ │
│ │ ↓ 优先级: Keychain > NSUserDefaults > 内存缓存 │ │
│ │ │ │
│ │ decryptContentsOfFile:forBookId:isFileLost: │ │
│ │ ↓ 算法: AES-128-CBC │ │
│ │ ↓ 输入: 加密的 XHTML 数据 │ │
│ │ ↓ 输出: 明文 XHTML │ │
│ │ ↓ 检测: "REWD" 自定义头部标识 │ │
│ │ │ │
│ │ encryptFileForBookId:originalEncryptKey:atPath:toPath: │ │
│ │ ↓ 解密后立即重新加密存储 (本地 DRM) │ │
│ │ ↓ 防止直接拷贝文件读取 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 本地存储 │ │
│ │ Documents/{bookId}/plainBookDirectory/ (解密后的 EPUB) │ │
│ │ Documents/{bookId}/epubImage/ (书籍图片缓存) │ │
│ │ Library/{cachePath}/epubImage/ (SDWebImage 缓存) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 密钥安全机制: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 1. 传输层: HTTPS + 加密 ZIP │ │
│ │ 2. 解密层: 逐章解密 (WREncryptedFileManager) │ │
│ │ 3. 存储层: 本地二次加密 (encryptFileForBookId) │ │
│ │ 4. 密钥层: 每本书独立密钥 + Keychain 安全存储 │ │
│ │ 5. 设备层: 密钥与设备绑定 │ │
│ │ 6. 试读层: freeTrialChapterCutOffStringLocaion (服务端控制) │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
```
### 2.3 用户标注数据流
```
┌─────────────────────────────────────────────────────────────────────┐
│ 用户触摸/长按 │
│ │ │
│ ▼ │
│ WRPageView 手势识别 (UITapGestureRecognizer / UILongPressGestureRecognizer)
│ │ │
│ ▼ │
│ CoreText Hit Test │
│ touchPoint = WRSFlipPointForCoreText(touch, viewHeight) // 坐标翻转
│ CTLineGetStringIndexForPosition(line, touchPoint) // 坐标→字符索引
│ CTLineGetOffsetForStringIndex(line, charIndex, NULL) // 字符索引→坐标
│ │ │
│ ▼ │
│ 选择范围确定 (selectionStartIndex .. selectionEndIndex) │
│ │ │
│ ▼ │
│ UIMenuController 弹出操作菜单 │
│ ├─ 划线/高亮 ─→ WRChapterData.addHighlightInRange:key:itemId:color:│
│ ├─ 写想法 ─→ WRBookNetwork.addReview:shareToWechat:... │
│ ├─ 复制 ─→ UIPasteboard │
│ ├─ 查询/翻译 ─→ WRReaderDictionaryViewController │
│ └─ 分享 ─→ WRShareManager │
│ │ │
│ ▼ │
│ WRChapterData 标注存储 │
│ NSAttributedString 注入自定义属性: │
│ "com.weread.highlight" → 高亮数据 │
│ "com.weread.underline" → 下划线数据 │
│ │ │
│ ▼ │
│ 服务器同步 │
│ WRBookNetwork.addReview:shareToWechat:audioArticleId:... │
│ WRBookNetwork.loadBookmarkListWithBookId:syncKey:callback: │
│ │ │
│ ▼ │
│ 重新排版 │
│ WRReaderViewController.recomposeCurrentPageViewWithSource: │
│ → WRPageView.setNeedsDisplay → drawRect: 重绘 │
└─────────────────────────────────────────────────────────────────────┘
```
### 2.4 阅读进度数据流
```
┌─────────────────────────────────────────────────────────────────────┐
│ 本地进度持久化 │
│ WRReaderViewController._saveReadingProgressAndIsAsync: │
│ ↓ NSUserDefaults 存储: │
│ - bookId / chapterUid / chapterIdx │
│ - filePosition / stringLocation │
│ - pageOfChapter / pageOfFlyleaf │
│ - lastReadDate / readingTime │
│ │ │
│ ▼ │
│ 云端同步 │
│ WRBookNetwork.uploadBookProgressAndReadingTime: │
│ ↓ POST /api/book/progress │
│ ↓ 参数: bookId, chapterUid, progress, readingTime │
│ │ │
│ ▼ │
│ 启动恢复 │
│ WRReaderViewController.initWithBook:progress:... │
│ ↓ loadBookReadInfoWithBookId: (从服务器获取最新进度) │
│ ↓ gotoChapterIdx:position:positionOfFile: (跳转到上次位置) │
│ ↓ scrollToLastReadingProgressIfNeeded │
└─────────────────────────────────────────────────────────────────────┘
```
### 2.5 预加载数据流
```
┌─────────────────────────────────────────────────────────────────────┐
│ 预测触发 │
│ WRForecastUtils.shouldPreloadChapterUidsForBook: │
│ type:bookRank:archiveRank: │
│ ↓ 根据书籍排名 + 用户行为预测 │
│ │ │
│ ▼ │
│ 预加载执行 │
│ WRChapterDownloadManger._preloadChapterContentWithBook: │
│ type:bookRank: │
│ ↓ 后台线程下载相邻章节 │
│ │ │
│ ▼ │
│ 下载 + 解密 + 缓存 │
│ WRBookNetwork.loadTarForEpubBookId:chapters:isPreload: │
│ → handleUnzipWithBookId → WREncryptedFileManager │
│ → 存储到 Documents/{bookId}/plainBookDirectory/ │
│ │ │
│ ▼ │
│ 图片预缓存 │
│ WRBookNetwork.storeTarEpubImageToDiskWithBookId: │
│ chapterUid:untarDirectory: │
│ → SDWebImage → com.hackemist.SDWebImageCache.epubImage │
│ │ │
│ ▼ │
│ 密钥预缓存 │
│ WRPreloadBookManager.saveEncryptKey:forPath:bookId: │
│ → NSUserDefaults / Keychain │
└─────────────────────────────────────────────────────────────────────┘
```
### 2.6 JS Bridge 数据流 (WebView 路径 — 公众号/文集文章)
```
┌─────────────────────────────────────────────────────────────────────┐
│ JS → 原生 │
│ window.webkit.messageHandlers.MPReader.postMessage(data) │
│ wereadBridge.execMPReaderMethod('MPReader', data) │
│ ↓ WKScriptMessageHandler │
│ ↓ WRMPReadingViewModel 解析 JSON │
│ │ │
│ ▼ │
│ 原生 → JS │
│ [webView evaluateJavaScript:@"..." completionHandler:nil] │
│ ↓ WKUserScript 注入脚本 │
│ │ │
│ ▼ │
│ 高亮系统 (weread-highlighter.js) │
│ rangy.init() → 创建 Highlighter (TextRange 模式) │
│ 注册 ClassApplier: "highlight"/"review"/"reference"/"tts" │
│ 监听 selectionchange 事件 │
│ │ │
│ ▼ │
│ 通信协议: wereadapijs://dispatch_message/ │
│ URL Scheme 编码消息, iframe 触发 │
└─────────────────────────────────────────────────────────────────────┘
```