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

34 KiB

读书 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 触发                                   │
└─────────────────────────────────────────────────────────────────────┘