【免费下载链接】Kotatsu-Redo
Manga reader for Android
Kotatsu-Redo是一款免费开源的 Android 漫画阅读器(Manga reader for Android),内置 1200+ 在线漫画源。它的核心设计是把"抓取网站数据"的工作从 App 本体中剥离出来,交给独立的Parsers 解析器库完成。想给这款阅读器新增一个漫画网站源吗?这篇教程将从架构、接口、开发到调试,带你完整走一遍。
1. 先搞懂架构:为什么解析器单独放一个仓库?
Kotatsu-Redo 采用"壳 + 解析器库"的两层结构:
| 层级 | 位置 | 职责 |
|---|---|---|
| App 壳(本项目) | Kotatsu-Redo | 阅读界面、书架、下载、同步、源管理等用户体验层 |
| Parsers 解析器库 | kotatsu-parsers-redo独立仓库 | 每个漫画网站的 HTML/JSON 抓取与解析逻辑 |
在 app/build.gradle 中可以看到,App 通过 JitPack 依赖引入解析器库:
def parsersVersion = libs.versions.parsers.get() implementation("com.github.clquwu:kotatsu-parsers-redo:$parsersVersion")版本号定义在 gradle/libs.versions.toml 中。
💡这样做的好处:网站改版了只需更新解析器仓库,App 本身不用重新设计;解析器代码也能在 PC 上用 JVM 直接跑测试,不需要模拟器。
CONTRIBUTING.md 也明确写道:新增漫画源(add a new manga source)请去 parsers 仓库贡献,而不是改 App 主仓库。
2. App 如何"认识"一个新的解析器
新源写好后,App 侧需要三步才能生效,这套机制可以从源码中清晰看到:
① 在MangaParserSource枚举中注册
解析器库里的MangaParserSource是一个枚举,每个枚举值对应一个漫画网站源。App 的源管理模块 MangaSourcesRepository.kt 直接读取该枚举构建"全部可用源"列表:
val allMangaSources: Set<MangaParserSource> = ... MangaParserSource.entries.filterNotTo(it, MangaParserSource::isBroken)所以你的新源只要出现在枚举里,就会自动被 App 发现、入库,并出现在"源管理"界面中。
② 工厂模式创建解析器实例
用户点击某个源时,App 通过 MangaRepository.kt 中的工厂为源创建对应的仓库对象:
is MangaParserSource -> ParserMangaRepository( parser = loaderContext.newParserInstance(source), cache = contentCache, mirrorSwitcher = mirrorSwitcher, )newParserInstance会通过反射在解析器库中找到你的解析类并实例化。
③ 统一接口:MangaRepository
无论源多特殊,App 只认 MangaRepository.kt 定义的这套接口——这也是解析器最终要"回答"的问题清单:
| 方法 | 作用 |
|---|---|
getList(offset, order, filter) | 按搜索词/筛选条件获取漫画列表 |
getDetails(manga) | 获取漫画详情(封面、作者、简介等) |
getPages(chapter) | 获取某一章的所有页面 |
getPageUrl(page) | 获取单页图片直链 |
getFilterOptions() | 提供网站支持的高级筛选项(作者、状态等) |
getRelated(seed) | 获取相关推荐(可选) |
ParserMangaRepository.kt 展示了 App 如何调用解析器:每个方法都被withMirrors { }包裹——如果配置了多镜像域名,失败后会自动切换域名重试,你写解析器时不必关心这些细节。
3. 动手写一个解析器(核心 5 步)
以下示例代码基于 parsers 库的MangaParser基类风格,帮助你理解需要实现的内容(完整模板以 parsers 仓库为准):
第一步:定义源元信息
在MangaParserSource枚举中新增一个条目,声明源 ID、标题、主域名、支持的语言和类型:
// MangaParserSource.kt(parsers 库) MY_FIRST_SOURCE( id = 2024, // 唯一整数 ID,决定本地数据隔离 title = "My Manga Site", domain = "www.example-manga.com", locale = "en", contentType = ContentType.MANGA, ),第二步:继承MangaParser,声明基本信息
class MyFirstParser : MangaParser() { override val source: MangaParserSource get() = MangaParserSource.MY_FIRST_SOURCE override val id: Int = 2024 override val name: String = "My Manga Site" override val domain: String get() = config[configKeyDomain] override val availableSortOrders = setOf(SortOrder.UPDATED) }config/configKeyDomain由基类提供:源设置(如镜像域名、登录 Cookie)会被持久化,App 设置页 SourceSettingsFragment.kt 会自动渲染这些配置项。
第三步:实现漫画列表抓取getList
override suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { val query = filter.query ?: return getListImpl(offset) val url = "https://$domain/search/?q=${URLEncoder.encode(query, "UTF-8")}" val doc = httpClient.get(url).document() return doc.select(".manga-item").map { Manga( source = source, url = it.select("a").attr("abs:href"), title = it.select(".title").text().trim(), cover = it.select("img").attr("abs:src"), ) } }🎯
httpClient是上下文注入的 OkHttpClient,自动携带 Cookie、User-Agent 和代理设置,直接拿来用即可。
第四步:实现详情与章节getDetails
override suspend fun getDetails(manga: Manga): Manga { val doc = httpClient.get(manga.url).document() val author = doc.select(".info .author").text().removePrefix("Author:").trim() val description = doc.select(".description").text() val status = if (doc.select(".status").text().contains("Finished")) Manga.Status.FINISHED else Manga.Status.ONGOING return manga.copy( author = author, description = description, status = status, tags = doc.select(".tags a").mapToSet { it.text() }, // 章节列表:每个 MangaChapter 对应一章 chapters = doc.select(".chapter-list li a").map { MangaChapter( source = source, url = it.attr("abs:href"), title = it.text().trim(), scanlator = "Example Scan", ) }, ) }第五步:实现页面获取getPages与getPageUrl
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> { val doc = httpClient.get(chapter.url).document() return doc.select(".reader img").mapToSequentialList { MangaPage( source = source, url = it.attr("abs:src"), scanlator = "Example Scan", ) } } // 若 page.url 已是图片直链,可不覆写(基类默认返回 page.url) override suspend fun getPageUrl(page: MangaPage): String = page.url至此,一个最小可用的解析器就完成了。剩下的交给 App 的 ParserMangaRepository.kt 去缓存、切换镜像和渲染界面。
4. 用好MangaLoaderContext:解析器能调用的"工具箱"
MangaLoaderContext是 App 注入给解析器的能力集合。App 侧的实现类 MangaLoaderContextImpl.kt 展示了它背后接入了哪些系统能力,对应到解析器可调用的高阶方法:
| 上下文能力 | 实现 | 典型用途 |
|---|---|---|
httpClient/cookieJar | 自定义 OkHttpClient + Cookie 管理 | 常规请求、登录态保持 |
evaluateJs(baseUrl, script) | 隐藏 WebView 执行 JS | 破解 JS 生成的页面数据、签名 |
interceptWebViewRequests | WebView 请求拦截 | 嗅探真实图片 API 地址(MangaLoaderContextImpl.kt 可见完整参数) |
requestBrowserAction | 抛出InteractiveActionRequiredException由 App 弹浏览器 | 验证码、人机验证(配合 CloudFlareActivity.kt 处理 CF 挑战) |
encodeBase64/getPreferredLocales | 编解码与本地化 | 图片 Base64 解码、多语言页面选择 |
redrawImageResponse | 图片重绘 | 去掉 CDN 防盗水印 |
⚠️ 遇到 CloudFlare 防护时不要硬抓,直接借助上下文的 CF 处理链路——App 已有现成的 CloudFlareInterceptor.kt 兜底。
5. 调试技巧:用内置 Test 源验证流程
App 内置了一个专为解析器开发准备的"试验田":
- 调试入口类 TestMangaRepository.kt 的每个方法都留了
TODO("...")注释,等于一份可运行的开发清单:先实现getList→ 再实现getDetails→ 最后补getPages; - 在 Debug/Nightly 构建中打开Settings → Debug,由 DebugSettingsFragment.kt 的
test_parser选项打开TestMangaSource的列表页,即可不依赖真实网站测试整条数据链路; - 仓库还附带了 androidTest 测试资产(如 full.json),供集成测试校验列表、章节、边界的解析正确性。
推荐的开发循环:
- 在 parsers 仓库的 JVM 单元测试中跑通纯抓取逻辑(速度快,无需设备);
- 本地将 App 的
parsersVersion指向你刚构建的 commit 版本进行真机验证; - 按 CONTRIBUTING.md 的原则提交:性能优先于代码美观、避免新增不必要依赖(APK 体积很重要)。
6. 进阶:让解析器"更好用"的加分项
- 多镜像域名:在
onCreateConfig中为configKeyDomain提供多个候选域名,App 的 MirrorSwitcher.kt 会在主域失败时自动切换(逻辑见 ParserMangaRepository.kt 的withMirrors); - 登录源:实现
MangaParserAuthProvider,App 会通过 SourceAuthActivity.kt 引导用户登录; - 高级筛选:覆写
getFilterOptions与filterCapabilities,把网站上的"作者/状态/章节数"筛选同步到 App 的筛选面板(对应 FilterCoordinator.kt); - 相关推荐:覆写
getRelatedManga点亮详情页的推荐模块。
小结
| 环节 | 关键位置 |
|---|---|
| 源注册 | parsers 库MangaParserSource枚举 → MangaSourcesRepository.kt |
| 实例化 | MangaRepository.kt |
| 接口契约 | MangaRepository.kt |
| 能力工具箱 | MangaLoaderContextImpl.kt |
| 调试清单 | TestMangaRepository.kt |
| 贡献规范 | CONTRIBUTING.md |
写解析器的本质就是"把网站的漫画、章节、图片三样东西,稳定地翻译 App 认识的Manga/MangaChapter/MangaPage三个对象"。抓住这条主线,其余的缓存、镜像、防护拦截都会由框架替你处理。现在就去 parsers 仓库,写出属于你的第一个漫画源吧 🚀
【免费下载链接】Kotatsu-Redo
Manga reader for Android
相关推荐
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考