剖析阅读Sigma源码架构:RuleAnalyzer规则解析流水线与 Room 数据库设计
【免费下载链接】legado-E阅读Sigma是legado的继承,保持开源免费,延续开源精神。项目地址: https://gitcode.com/gh_mirrors/legado2/legado-E
阅读Sigma(legado-E)是一款开源免费的 Android 阅读 App,其核心竞争力在于书源规则解析——用规则把网页和接口变成可读的正文。本文以RuleAnalyzer 规则解析流水线与Room 数据库设计为主线,带你快速看懂这款开源阅读应用的源码架构,新手也能轻松上手 📚。
一、源码结构总览:每个模块管什么
项目主代码位于 app/src/main/java/io/legado/app/ 目录,与本文相关的模块可分成三块:
| 模块 | 路径 | 职责 |
|---|---|---|
| 规则解析 | model/analyzeRule/ | 拆分、解析并执行书源规则 |
| 数据存储 | data/ | Room 数据库、DAO 与实体定义 |
| 业务模型 | model/ | 阅读、下载、搜索等动作的编排 |
项目自带了两份模块说明文档:data/README.md 和 model/README.md,是阅读源码的绝佳入口。
二、规则解析流水线:书源生效的魔法
2.1 RuleAnalyzer:先把规则"切"开
一条书源规则常常长这样:
class.name;@css:div.title;@text@前面是 XPath,后面是 JSoup 的 css 选择器。麻烦在于:规则字符串本身就可能含有@、&&、||(比如正则或 jsonPath 里就有),简单按符号切分必然出错。
RuleAnalyzer.kt 就是来解决这个问题的,它的设计思路很巧妙:
- 不用正则、不复制中间串:只在原字符串上标记起点和终点(
pos指针),最后一次性切片,源码注释原话是"高效快速准确切割规则"; - 识别"平衡组":遇到
[...]或(...)筛选器时成对匹配括号整体取出(见 RuleAnalyzer.kt 中chompBalanced的调用),筛选器内部的符号不会骗过它; - 区分引号与转义:单引号、双引号、转义字符分别跟踪状态(见 RuleAnalyzer.kt 的
chompCodeBalanced),JS 代码块里的&&不会被当成分隔符; - tailrec 尾递归:
splitRule方法使用 Kotlin 的tailrec注解(RuleAnalyzer.kt),避免深递归的性能损耗。
2.2 AnalyzeRule 调度器:按类型分派解析器
规则切分完成后,由 AnalyzeRule.kt 作为"总调度"接手,它内置了四个解析器:
| 解析器 | 文件 | 用途 |
|---|---|---|
| XPath | AnalyzeByXPath.kt | HTML 结构解析的默认语言 |
| JSoup | AnalyzeByJSoup.kt | css 选择器语法 |
| JsonPath | AnalyzeByJSonPath.kt | 解析 JSON 接口响应 |
| 正则 | AnalyzeByRegex.kt | 按模式抽取内容 |
规则里的@分隔符让四个解析器可以任意串联使用。同时 AnalyzeRule 内置了三份缓存:stringRuleCache、regexCache、scriptCache(AnalyzeRule.kt)——同一条规则字符串、正则、JS 脚本只编译一次,后续直接复用,这对翻页、批量加载章节时的解析开销优化非常明显。
2.3 JS 脚本与变量传递
书源规则还支持 JS 脚本,引擎来自内置的 modules/rhino/ 模块(Rhino 引擎)。RuleData.kt 负责用variableMap管理跨页面传递的大变量:第一页脚本写入的 token,下一页规则可以取回——这正是"翻页加载""登录态保持"等复杂书源能跑通的底层机制。
三、Room 数据库设计:管理 21 张表的账本
3.1 单一入口:appDb 全局单例
所有数据库操作都经过一个懒加载的全局单例(AppDatabase.kt):
val appDb by lazy { Room.databaseBuilder(appCtx, AppDatabase::class.java, AppDatabase.DATABASE_NAME) .fallbackToDestructiveMigrationFrom(false, 1, 2, 3, 4, 5, 6, 7, 8, 9) .addMigrations(*DatabaseMigrations.migrations) ... }短短几行讲了三件事:数据库首次使用时才构建(by lazy);1~9 的远古版本直接采用"重建"策略;第 10 版起才开始正规的增量迁移。
3.2 89 个版本与 AutoMigration 自动迁移
AppDatabase.kt 的@Database注解中可以看到version = 89,以及 Book、BookChapter、BookSource、Bookmark、RssSource、ReadRecord 等 21 张表定义和一个BookSourcePart视图。
项目大量使用 Room 的AutoMigration 自动迁移:普通改动(加列、加表)由 Room 自动生成迁移 SQL;只有需要手工回填数据的版本(如 54→55、80→81、84→85)才在 DatabaseMigrations.kt 中编写对应 spec 类,把成本压到最低。
另一个亮点是exportSchema = true:每个版本的数据库结构会被自动导出到 app/schemas/io.legado.app.data.AppDatabase/,共 89 个 json 文件。对贡献者来说,数据库结构的每一次变化都能在版本控制中"看见",这是多版本协作项目非常友好的实践。
3.3 DAO 与 Entity:清晰的职责分工
按照 Room 官方推荐的惯例,data/ 目录分两层:
- entities/:21 个纯数据类(表结构),如 Book 书籍表、BookChapter 章节表;
- dao/:21 个数据库读写入口,如 BookDao.kt、BookChapterDao.kt、BookSourceDao.kt,UI 与业务层只与 DAO 打交道,不直接接触表。
这种"实体定结构、DAO 开门面"的划分,正是数据库能稳定迭代到第 89 版而业务代码几乎不用改的原因。
四、两者如何协作:从书源到书架的数据流 💾
把解析流水线和数据库串起来,一次典型的"搜索 → 加书架 → 阅读"流程是:
- 用户输入关键词,App 依次执行各书源的搜索规则:RuleAnalyzer 切分 → AnalyzeRule 分派解析器;
- 结果写入
SearchBook表(SearchBookDao.kt)供界面展示; - 加入书架时,从
BookSource表提取规则数据,书籍信息写入Book表; - 打开书籍后按
BookChapter表逐章取内容,阅读进度实时写入ReadRecord表。
一句话总结:解析流水线负责"取数",Room 数据库负责"存数",两者互不越界,层次干净利落。
五、新手源码阅读路线:30 分钟上手
如果想自己动手读源码,推荐这个顺序:
- 先看 data/README.md 与 model/README.md 建立模块概念;
- 再看 AppDatabase.kt,仅凭
@Database注解就能列出这款 App 有哪 21 张表; - 然后精读 RuleAnalyzer.kt,注释详尽,
splitRule是核心方法; - 最后看 AnalyzeRule.kt,理解四个解析器与 JS 脚本如何组合。
想本地运行项目,可以克隆源码:
git clone https://gitcode.com/gh_mirrors/legado2/legado-E阅读Sigma 的架构并不复杂,它的功力藏在"持续迭代中保持分层清晰"上:RuleAnalyzer 的规则切分、AnalyzeRule 的总调度、Room 的 89 版自动迁移——这三件小事做到位,才撑起了它赖以生存的书源生态。
【免费下载链接】legado-E阅读Sigma是legado的继承,保持开源免费,延续开源精神。项目地址: https://gitcode.com/gh_mirrors/legado2/legado-E
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考