状态:实施中,尚未接入区域解码。2026-09-26 对照仓库源码、Coil 3.6.3、 Telephoto 0.19.0 与本机 Komikku 校正。进度与尺寸修复已提交为
9f6f87872; 首个存储切片接入 BookHelp 的有效性检查、原子发布及等待者安全的文件锁。目标:分页缩放时按需读取原图细节;阅读与后续漫画下载共用同一份有效文件, 已有文件不重复获取,已保留的章节断网可读。 “清晰”以源图分辨率为上限,不承诺放大超过原图仍有新增细节; “复用”不等于不再解码,preview、瓦片和重开后的解码仍是正常工作。
MangaReaderScreen 的预取与展示都以 URL 请求图片,共用显式解码尺寸。
条漫按窗口宽度与侧边留白解码;分页适应高度按高度解码,拉伸同时满足宽高,
原始尺寸保留原图,旋转适配保守覆盖交换后的窗口尺寸。CoverFetcher 返回网络流或内存 Buffer;即使来自 OkHttp HTTP 缓存,也没有
Telephoto 所需的 Coil 磁盘键或可映射的本地文件模型。分页通常仍走整图回退。MemoryCacheService
会验证尺寸、采样状态和 precision;稳定目标减少布局引起的差异,但不提供缩放细节。MangaChapterPageLoader 目前只加载章节文本和图片 URL,未向阅读页提供缓存图片文件。BookHelp.getImage(book, src) 的路径是
externalFiles/book_cache/<bookFolderName>/images/<md5Encode16(src)>.<suffix>。
flowImages 与下载使用同一章节基地址解析图片地址,应该沿用完整解析后原始 src
(包含书源选项)作为身份,不能用带临时 token 的最终网络 URL 替代。isImageExist 仍只看 exists,保留旧 UI/队列的低成本提示语义,不是下载完成证明。
首个存储切片让 saveImage 在文件锁内检查文件格式/尺寸,字节与流写入都先写临时文件、
校验后原子替换;不再发布新坏图或降级为直接复制到可见目标。
历史文件目前只检查结构,不具备完整性/书源版本的持久有效记录。saveImage 的锁现在按实际书籍文件路径建立,引用数包括等待者,最后一个退出才移除。
当前是串行获取:成功后的等待者复用文件;首位取消时下一位接手获取。
尚未实现独立于任一订阅者的共享任务,也未接入阅读 Fetcher;不能宣称完整跨消费者去重已完成。clearInvalidCache 在应用启动及主页面清理时执行;clearComicCache 按阅读章节窗口删除图片。
现有规则不识别显式下载、活动阅读和瓦片解码中的文件。MangaSettings.autoOfflineCache 已存在,不要新增同名设置。当前主要控制章节预取,
不是图片已持久化或章节可离线的完成证明。Telephoto 0.19.0 的 Coil3ImageSource.kt 在成功结果为 BitmapImage 时,尝试:
result.diskCacheKey 打开 Coil DiskCache snapshot;dataSource == DISK || MEMORY_CACHE 时,把 结果请求的 data 映射为本地 URI,
构造文件/content URI 区域解码源;内存命中时还检查文件存在。之后还要通过 canBeSubSampled,否则回退普通 painter。拿不到源时会重试一次请求,
第二次关闭内存读取;自定义 Fetcher 不会因设置 diskCachePolicy 自动接上 Coil 磁盘缓存。
EngineInterceptor 只从文件型 ImageSource 提取 diskCacheKey,任意 Buffer 不会自动写入磁盘。
因此 ImageRequest.data = File(path) 配合默认 FileUriFetcher,可以自动走区域解码,
无需手写瓦片组件。这里复用的文件必须是完成书源解密但未应用灰度/EInk 等展示变换的原图。
来源:本机相应版本 sources.jar; Telephoto 0.19.0 Coil3ImageSource。
对照仓库 D:/Project/komikku 的下列实际调用链(只读参考,不修改该工程):
HttpPageLoader.internalLoadPage:优先查 ChapterCache,miss 才请求网络;
putImageToCache 完成后才设置 page.stream 与 Ready。加载顺序可借鉴:完整文件在先,渲染在后。HttpPageLoader 的 PriorityBlockingQueue:当前页/重试优先于普通预取,线程数有限。
可借鉴调度语义,不照搬独立的 readerThreads 设置与全局协程管理。ChapterCache:保存章节 page list 和原图,DiskLruCache editor commit 后才发布。
在线缓存具有明确字节容量,支持重开、识别已淘汰图片并重新排队。Downloader.getOrDownloadImage:已有下载优先,否则使用 ChapterCache 字节,最后才联网。
copyImageFromCache 把缓存复制到下载目录并删缓存文件;因此这是获取复用,不是同一文件存储,
也不能把“使用了缓存”当作无复制、无额外磁盘占用的证明。ReaderPageImageView:静态图使用 SubsamplingScaleImageView,动画图单独渲染,
设置 onReady/onImageLoadError 再更新阅读器状态。可参考就绪与解码失败的阶段划分。结论:plan 的文件优先/区域解码方向与成熟阅读器一致;“阅读与下载永远共用同一份文件” 是本项目自己的增强目标,Komikku 没有替我们证明它。不能照搬其在线 LRU 清理来删除保留下载, 也不在本项目引入 SSIV、Voyager、SQLDelight 或其 source extension 架构。
所有分页阅读都先取得可用于随机读取的文件,再交给 ZoomableAsyncImage。
自动离线开关不控制是否生成解码文件,只控制章节预取与长期保留。
关闭开关仍应清晰缩放,不能退回现在的 URL/Buffer 整图路径。
沿用 BookHelp 图片路径,文件内容为存储真相;增加最小的章节索引与保留记录,管理用途和有效性。 阅读、自动离线和显式下载使用同一获取入口、同一去重机制、同一文件,不复制到第二个 Coil 缓存。 不要为此新建 Gradle 模块或通用存储框架。
| 用途 | 获取方式 | 保留与淘汰 |
|---|---|---|
| 普通在线阅读 | 当前页优先,有限预取;完成后发布本地文件 | 可淘汰,受阅读缓存预算限制 |
| 自动离线 | 同一入口补齐配置范围内章节及元数据 | 受明确的自动离线保留窗口约束 |
| 用户显式下载 | 同一入口补齐指定章节 | 持久保留,不受阅读 LRU 或阅读位置窗口删除 |
| 活动渲染/获取 | 持有文件使用凭证 | 使用期间不可删除,不等于永久保留 |
同一张图可关联多个章节与用途。取消一个用途不能删除仍被其他用途保留的文件。 下载成功只是保留用途升级,不再获取或再复制已有有效图片。
BookHelp 外部专属目录通常不被系统作为 cache 回收,但可能因应用清数据、卸载、 外部存储不可用或应用自己的清理而失效,不能写成“只有卸载才会删除”。
在现有 Android 数据层 manga owner 内形成获取能力;读者是漫画会话,下载者是现有图片下载调用链。 ViewModel 经现有 Gateway/Repository 使用它,不访问 BookHelp、DAO 或 File。 仅在接入真实调用者的同一切片增加窄契约,不先生成无调用方接口。
一次获取的身份至少包含书籍存储身份、书源身份与完整原始 src;最终网络 URL 只是请求结果。
落盘保持现有路径兼容,迁移时确认书籍改名/来源切换与 updateCacheFolder 的语义。
如果书源变化可能改变解密结果,必须使旧有效记录失效,而不是只靠路径存在。
获取步骤:
.tmp 文件,完成读取与解密后校验,再以原子替换发布最终文件。
不把临时文件交给 Coil;不允许退回向可见最终路径逐字节复制。“有效”至少要求文件非空、格式受支持、头部尺寸有效,且本次完整读取成功; 已知 Content-Length 应匹配,解密后重新校验。服务端未知长度不伪造百分比。 格式校验通过也不保证每台设备都能区域解码,设备解码失败属于渲染降级流程。 失败冷却/坏图诊断与有效文件分开,不能把失败占位放进下载完成判定。
remove(key)。在同一书籍目录保存版本化、原子更新的最小索引(具体模型以契约测试为准): 章节 URL/index、章节文本文件、预期图片完整 src 列表、有效文件关联、保留用途和最后访问时间。 不在元数据里保存最终带 token URL 或认证 header 明文副本;完整身份需要持久化时评估敏感选项。
下载完成 = 章节列表/正文足以重建阅读页 + 所有预期图片有效且被保留。 单个图片下载完、预取请求成功或 UI Ready 均不能单独标为整章可离线。 索引不代替真实文件;打开时发现缺失应降级为未完成/可重试,并修正下载状态。 索引损坏时重建可重建部分,不清空整本书原图,更不能无声删除已有下载。
旧图片缺少用途记录时,不能直接当阅读缓存淘汰;先保守保留,再由明确迁移规则或用户清理分类。
同一补丁必须接管 clearComicCache、clearInvalidCache 与改名/移除书籍清理,
不能新策略之外仍有旧删除旁路。
.data(File(path)) 请求。
不默认先走 URL 图片请求再切 File,避免两条获取链路与重复 preview 解码。ContentScale.None 必须单独验证:缩小 preview 不应改变原图固有尺寸或初始缩放。ZoomableAsyncImage(File) 或设置 diskCachePolicy 不能关闭该区域解码路径。
此模式暂不承诺区域解码清晰度;之后验证等价渲染滤镜再统一,不能悄悄失去二值化。SubSamplingImageErrorReporter 记录失败;对当前文件版本降级至整图,
不立即删下载或无限重新联网。重试/文件版本更新后允许再次尝试区域解码。imageRetainNum 是现有章节窗口语义,不改名为字节上限。用于自动离线时需说明保留范围,
不能影响显式下载或替代字节预算。manga_cache 仅作过渡。
是否还能利用旧 HTTP 缓存、如何避免新网络获取的双份落盘,必须按实际 AnalyzeUrl 客户端验证,
不凭修改一个容量数字宣称已消除双份存储。每个切片可独立验证;不要一次同时更换网络、数据库、DI、导航和 UI。
先写测试,再修正 BookHelp 发布与有效性语义;接入现有 saveImage/saveImages 真实调用方。 测试:取消/IO 失败无半文件、坏文件可重试、同图并发一次获取、等待者取消、锁回收竞争、 原始 src/headers/解密参数兼容、历史文件采纳、失败批次不标下载完成。
首个已实现子切片:BookImageFileStore 由 BookHelp 实际使用,负责非空/格式校验、
同目录临时写入、原子发布与按文件串行获取;下载完成检查改为实际图片有效性检查。
hasImageContent 不再边检查边删除坏图,避免检查后新文件已发布却被旧检查删除的竞争。
图片及目录清理与写入登记原子协调:存在写入者或排队等待者时跳过清理,
章节窗口清理不删除写入中的临时文件;同步 writeImage 也登记写入保护。
目前是写入保护,不代表已经保护未来瓦片解码器的读取生命周期。
此时还没有引入阅读自动落盘,所以不会增加无限增长的在线阅读存储。
仍未完成:持久有效记录/源版本失效、完整传输长度记录、共享任务订阅取消语义、 章节索引与保留用途、活动读取保护及保留感知清理。目录迁移仍需协调活动资源。 格式/尺寸检查不是整图所有像素可解码的证明; 后续渲染错误必须保持可重试,不能因已有文件无条件标 Ready。
首个子切片的验证:BookImageFileStoreTest 10 项、BookHelpImageFileTest 在 API 26/35
各 5 项 native graphics 测试、BookHelpImageCacheCompleteTest 1 项,共 21 项通过。
compileAppDebugKotlin、verifyConfigArchitecture、lintAppDebug 与 git diff --check 通过;
lint 仍有现有的 118 个警告、13 个提示,本切片没有放宽 baseline。
覆盖并发、等待者取消、传输失败、发布前取消、坏数据不覆盖原图、PNG/SVG、
历史坏文件重试、已有有效文件不解析网络 URL、写入与清理竞争。
校验通过显式关闭的输入流读取图片头部。
这些测试不替代真实外部存储、认证书源 JS 解密、断网章节恢复和瓦片生命周期的验证。
接入当前页与有限预取,替换 URL 图片请求与自动背景提色;接 File 模型、错误降级、 EInk 整图分支及活动文件保护。首版保持 preview 尺寸,不先做 1/4 优化。 这一切片启用在线落盘时必须同时提供可淘汰预算与安全清理,不能先交付无上限版本。
接章节索引/完成判定、现有 autoOfflineCache 和受保护清理,验证断网重建; 后续下载 UI/队列只补齐同一文件并升级保留用途。已有下载缓存从 A 开始保守保护, 不能等新 UI 做完才保护旧文件。
确认支持格式下 subSamplingState != null 且最终达到 full-quality 状态,
再比较 preview 尺寸、内存、首次清晰时间及是否闪烁。不把正常瓦片解码计为多余完整请求。
必须覆盖:
按修改运行真实任务:
.\gradlew.bat :app:compileAppDebugKotlin
.\gradlew.bat :app:testAppDebugUnitTest --tests <受影响测试类>
.\gradlew.bat verifyConfigArchitecture lintAppDebug --continue --no-configuration-cache
# 涉及资源、Manifest 或打包时
.\gradlew.bat :app:assembleAppDebug
git diff --check
不承诺尚未运行的真机、性能与离线兼容验证;不以“文件已经落盘”作为区域解码和下载完成证明。