ADR 0002 — Decouple Reader Semantics, Image Resources, and Rendering Backends(阅读器语义、图像资源与渲染后端解耦)
- 状态:Accepted
- 日期:2026-09-16
- 实施状态:
- WebGPU 分支隔离:已完成(
feat/webgpu-reader,作为UPSTREAM-TRACKED资产) - Phase 0A 渲染器基线(Renderer Baseline):已完成(Completed,固定夹具真机实测数据见下,Compose Scene 决胜成立)
- Phase 0B 生产阅读器基准(Production Reader Benchmark):已完成(Completed,真实本地解码与两级资源状态机真机实测数据见下,ComposeScene 优势确立)
- PoC A (ReaderScene 几何抽象):已完成(Completed)
- PoC B (Webtoon 视口实验:Compose Scene vs. View Scene):已完成(Completed,根据决胜原则选定 ComposeSceneRenderer)
- Phase 1 (图像解码决策与切片瓦片引擎):
- Phase 1A(纯 Kotlin 解码决策智能):已完成(Completed)
- Phase 1B(图源与切片运行时):已完成(Completed)
- Phase 1C(渲染器集成与管线适配):已完成(Completed,Compose 瓦片无重组刷新与无缝拓扑渲染闭环)
- Phase 1D(真机超长条漫效能与长图画质基准):已完成(Completed)
- Phase 2 (生产化功能对齐与多章节视口锚定):
- Phase 2A (页面间隔、跨章节平滑扩缩窗与无感锚定、统一错误重试交互、低内存优化):已完成(Completed)
- Phase 2B (2D 契约抽象、双向锚定补偿与默认全量推广):已完成(Completed)
- Phase 2C (横向连续瀑布流场景与对称性定理:HorizontalReaderScene):已完成(Completed)
- Phase 2C2 (横向连续场景 Compose 宿主交互:ComposeSceneHorizontalReader):已完成(Completed)
- Phase 3 (分页与双页场景引擎与统一宿主):
- Phase 3A (纯 Kotlin 分页几何模型与排版调度:PagedReaderScene、PagedSpreadResolver、PagedSnapResolver):已完成(Completed)
- Phase 3B (分页影子模式校验器与等价性测试:PagedShadowValidator、PagedShadowParityTest 与运行时采样):已完成(Completed)
- Phase 3C (现代分页场景 Compose 宿主:ComposeScenePagedReader,单双页统一架构、弹性吸附、缩放与 Draw Phase 呈现):已完成(Completed)
- Phase 3D (生产路由接线、交互测试套件验证与 ADR 归档):已完成(Completed)
- Phase 3E (图像呈现 Parity 补齐与生产门控解耦:SceneImagePresentationCoordinator、可见切片请求、多 LOD 缩放、动图生命周期与独立安全门控):已完成(Completed)
- Scene Reader 功能集成:已全量覆盖 Webtoon 纵向瀑布流、横向连续流(LTR/RTL)、单页离散分页(LTR/RTL/Vertical)与双页并页模式,纯 Draw Phase 渲染、可见切片多 LOD 驱动与零跳动锚定全部落地
- 翻页动画样式(ReaderAnimation):已完成 SLIDE / COVER / CURL 三样式在场景分页宿主的 Draw Phase 实现,并以
resolveComposeReaderPageTransform为 oracle 做同输入同输出 parity(见docs/architecture/reader-scene-closure-plan-2026-09.md)
- WebGPU 分支隔离:已完成(
- 方向与门控的边界声明(ReaderMode 属 legacy 投影):
ReaderMode同时编码"布局模式"与"阅读方向" (STANDARD/REVERSED同属分页布局),这是 legacy 偏好模型的遗留耦合。Scene core 不得继承该耦合:ReaderCore只接收SceneReadingDirection参数,模式到方向的折算集中在配置层 (reader/ui/config/SceneReadingDirectionResolver.kt)。因此连续横向的 RTL 不是新增一个ReaderMode枚举值, 而是独立偏好isContinuousHorizontalReversed(AppSettings.KEY_READER_CONTINUOUS_HORIZONTAL_REVERSED), 并在阅读选项面板中仅当该模式被选中时显示。若将来整理 settings schema 为LayoutMode × ReadingDirection, 迁移点只有这一个解析函数。 - 关联分支:
feat/webgpu-reader(WebGPU 成果隔离保存与上游追踪)、devel(基线主干) - 核心准则:ReaderCore owns semantics; ImagePipeline owns image policy; Renderer owns presentation.(ReaderCore 掌管阅读语义;ImagePipeline 掌管图像策略;Renderer 掌管呈现)
一、 背景与动因
Kototoro 当前的漫画阅读器主要基于 Jetpack Compose 与 Telephoto(Zoomable/Subsampling)构建。近期在探索引入 Mihon 最新 WebGPU 渲染器(ca.mpreg:webgpuviewer)的过程中,团队深入触及了移动端漫画阅读器的深水区瓶颈与架构矛盾:
- WebGPU Continuous 的成熟化成本与上游状态:
- 上游 Mihon 的 High Quality Renderer(WebGPU)已覆盖分页与 Continuous 阅读模式,并提供更直接的 GPU 渲染路径与可编程着色器(3D 翻页动效、Catmull-Rom 缩放、LUT 色彩滤镜)。
- 但截至 2026-09-16,其 Continuous viewer 仍存在惯性滚动微弱(Mihon #3780)与快速导航闪烁(Mihon #3779)等公开问题,且上游对超长条漫与 GPU 纹理管理的迭代仍非常频繁。
- 因此,Kototoro 暂不承担在本地自行推进 Continuous WebGPU 成熟化的巨大成本,而是采取跟踪上游策略;在 Webtoon 场景下暂保持回退,待上游稳定后再行评估。
- 移动端驱动与合成层兼容代价:
- 依赖 NDK/Dawn/Vulkan 的原生阅读视口在 Android 碎片化设备(Adreno/Mali 早期驱动、MIUI/HyperOS、Android 14 SurfaceControl 打洞与合成)上面临冷启动白屏、看门狗误判、生命周期回收等极其繁重的系统级兼容负担。
- 通用 UI 列表虚拟化与二维漫画视口的抽象差异:
- Webtoon 本质是连续二维页面场景,而
LazyColumn是通用的惰性列表抽象。虽然LazyColumn已对可见项进行虚拟化,并不会为整个章节建立完整 UI 树,但其 item composition、measurement、placement、prefetch 和逐项图片/缩放生命周期仍属于通用 UI 列表模型。 - 对于只需要“视口与页面矩形求交 + 图像绘制”的漫画阅读场景,专用二维场景(2D Scene)存在进一步减少框架工作量、提高资源生命周期确定性的空间。
- 巨幅图片(如 4000×20000 像素的条漫单页)若无视口自适应分级(LOD)与按需分块解码(Tiling),整张解码将造成数百兆显存突增、频繁 GC 卡顿甚至 OOM。
- Webtoon 本质是连续二维页面场景,而
- 后端与业务状态紧耦合:
- 既有实现中,页码进度、手势判定、页面排布与具体 UI 组件/渲染器深度绑定。若每引入一个新渲染后端(如 Compose、WebGPU、Canvas)都需要重新实现一套完整的状态与图片加载逻辑,系统维护成本将不可持续。
因此,亟需确立一套高内聚、低耦合、渲染后端可插拔的现代漫画阅读器核心架构。
二、 核心决策:三层主权模型(Three-Tier Sovereignty)
阅读器体系严格划分为三层,各层拥有独立主权,严禁越界:
┌──────────────────────────────────────────────────────────────────┐
│ Kototoro Reader UI │
│ (Compose Chrome: TopBar, BottomBar, Sheets, OCR UI) │
└────────────────────────────────┬─────────────────────────────────┘
│ 低频语义通知 (PageChanged, Zoom)
▼
┌──────────────────────────────────────────────────────────────────┐
│ 1. Reader Semantics 主权 (ReaderCore) │
│ ──────────────────────────────────────────────────────────── │
│ ReaderScene / ReaderLayout / ReaderViewport / ReaderCamera │
│ ReaderProgressResolver / VisibleRegionResolver │
│ │
│ 输出纯几何/状态帧: ReaderFrame(viewport, List<VisibleNode>) │
└────────────────────────────────┬─────────────────────────────────┘
│ 预测请求 (PrefetchRequest) & 视口帧
▼
┌──────────────────────────────────────────────────────────────────┐
│ 2. Image Resource 主权 (ImagePipeline) │
│ ──────────────────────────────────────────────────────────── │
│ DecodePlanner / LodPolicy / TileManager / ResourceStore │
│ 资源预算 (Memory/Disk Cache) & 解码调度 │
│ │
│ 输出多态资源: ReaderImageAsset (BitmapAsset | TileSet | Encoded) │
└────────────────────────────────┬─────────────────────────────────┘
│ 针对各后端特化的呈现资产
▼
┌──────────────────────────────────────────────────────────────────┐
│ 3. Rendering 主权 (ReaderRenderer) │
│ ──────────────────────────────────────────────────────────── │
│ ┌──────────────────────────┬────────────────┬──────────────┐ │
│ │ ComposeSceneRenderer │ ViewScene │ WebGPU │ │
│ │ [PRIMARY CANDIDATE] │ [CONTROL] │[UPSTREAM-TRK]│ │
│ │ DrawModifierNode/Graphics│ Custom View │ Mihon 翻页/ │ │
│ │ Layer (跳过 Composition) │ onDraw(Canvas) │ 着色器特效 │ │
│ └──────────────────────────┴────────────────┴──────────────┘ │
└──────────────────────────────────────────────────────────────────┘1. 第一层:Reader Semantics 主权 (ReaderCore)
- 职责:维护二维漫画虚拟世界坐标系,负责页面几何排列、视口相交计算、阅读进度结算与运动预测。
- 输出:纯几何与语义帧
ReaderFrame,告知下游“当前视口内应当在哪些矩形位置绘制哪些页面的哪些区域”。 - 绝对禁忌:绝不知晓图片文件的下载、解码、路径与失败状态。
2. 第二层:Image Resource 主权 (ImagePipeline)
- 职责:决定“如何准备像素”。根据视口物理尺寸、设备 RAM 等级、缩放倍率计算动态 LOD 采样率,管理分块切片(Tiling)、执行预取请求及资源调度队列与内存缓存。
- 输出:多态呈现资产
ReaderImageAsset。 - 绝对禁忌:不持有任何 Android UI 组件或阅读器章节/进度状态。
3. 第三层:Rendering 主权 (ReaderRenderer)
- 职责:决定“如何把像素画在屏幕上”,掌管具体的视觉呈现。
- 战略重心更新:
- 首选主攻候选:
ComposeSceneRenderer。利用 Compose 现代高性能特性(DrawModifierNode、GraphicsLayer保留显示列表、Draw phase 状态读取),将高频变换限制在 Draw 阶段,彻底跳过 Composition 与 Layout。 - 对照参考候选:
AndroidViewSceneRenderer。作为标准单 ViewCanvas.onDraw的性能基准对照组。 - 决策决胜原则(Tie-Breaker):若 Compose Scene 与 View Scene 在关键帧指标与内存上无显著质差,坚决优先采纳 Compose Scene,以消除
AndroidView互操作、生命周期桥接、手势穿透与合成层黑盒问题。
- 首选主攻候选:
- 绝对禁忌:不得成为阅读进度、页面布局和章节语义的 Source of Truth。
三、 关键架构约束
为确保长期演进不发生设计漂移,确立以下 4 项刚性约束:
约束 1:ReaderCore 绝不知晓图片加载状态(几何稳定性与锚定修正)
ReaderCore仅消费PageGeometryHint构建连续虚拟几何体,不感知几何尺寸来自于网络 Header、本地元数据、历史缓存还是算法估算:kotlinsealed interface PageGeometryHint { data class Exact(val width: Int, val height: Int) : PageGeometryHint data class AspectRatio(val ratio: Float) : PageGeometryHint data class Estimated(val ratio: Float) : PageGeometryHint }- 图片下载中、等待解码或解码失败,Scene 几何排版骨架完全独立;若资源不可用,Renderer 仅负责在对应几何区域绘制占位块。
- 锚定修正(Anchored Correction):当估算尺寸(
Estimated)被精确尺寸(Exact)修正引起总高度变化时,通过 Viewport Anchor Compensation 保持当前主要可见页面内的绝对像素偏移,避免长图展开时按比例把读者推过新出现的内容。屏幕旋转等主动 relayout 才可以选择归一化语义锚点。
约束 2:ImagePipeline 职责细分,运动遥测与 ARR 协作
- 严禁将
ImagePipeline做成巨型上帝对象。内部明确拆分为:ImageSource:原始字节流与资源句柄;DecodePlanner:按像素用途(PixelUsage: DISPLAY_ONLY | CPU_READ_REQUIRED | REGION_TILE)精细化解码规格;ResourceStore:缓存生命周期;DecodeExecutor:执行解码产出目标资产。
- 运动遥测(ViewportMotion)的双重用途:kotlin数据流分支为:
data class ViewportMotion( val velocityX: Float, val velocityY: Float, val isDragging: Boolean, val timestampNanos: Long, )ViewportMotion -> ReaderCore/Prediction -> PrefetchRequest -> ImagePipeline(驱动预测预加载);ViewportMotion -> PlatformPresentationAdapter(协作 Android 15/16 自适应刷新率 ARR:Compose 映射至preferredFrameRate,View 映射至frameContentVelocity),在高速滑动时拉升 120Hz,静止时降频降功耗。
约束 3:Renderer 拥有“呈现状态”,而非“语义状态”
- Renderer 允许且应当拥有后端特有的绘制与保留资源状态(如 Compose 的
GraphicsLayer、DrawModifierNode,Canvas 的Paint、Matrix,WebGPU 的TextureCache)。 - 高频 Camera 变换(位移/缩放)严格约束在 Draw Phase(或
GraphicsLayer.matrix)消费,严禁逆向触发父级重组或布局。 - 严禁 Renderer 内部私自维护
currentChapter、currentPageProgress、isDoublePage等阅读语义字段。
约束 4:多态呈现资产(句柄化),拒绝为统一而二次拷贝
ReaderImageAsset采用密封接口(Sealed Interface),描述资源的访问句柄而非强行具象化到堆内存:kotlinsealed interface ReaderImageAsset { data class EncodedAsset( val source: ImageSourceHandle, // FileSource, BufferSource, StreamSource val metadata: ImageMetadata, ) : ReaderImageAsset data class BitmapAsset( val bitmap: Bitmap, val level: Int, val sourceRegion: IntRect?, ) : ReaderImageAsset data class TileSet( val overview: Bitmap?, val tiles: TileProvider, ) : ReaderImageAsset }- 各 Renderer 可向 Pipeline 声明其
preferredRepresentation(如 WebGPU 声明ENCODED,Canvas/Compose 声明BITMAP或TILE_SET)。严禁将 EncodedAsset 设计为单纯的ByteArray,以避免大图在 Java Heap 与 JNI 之间发生数十兆的冗余拷贝。
四、 代码组织规划(Package 先行,暂缓拆 Module)
初期避免 Gradle 模块爆炸,直接在 app/src/main/kotlin/org/skepsun/kototoro/reader/ 下建立清晰的目录隔离:
reader/
├── core/ // 纯 Kotlin,零 Android 依赖(禁用 android.graphics.*、androidx.compose.* 等几何与 UI 类)
│ ├── FloatRect.kt // 跨平台自包含几何基础类型
│ ├── IntSize.kt
│ ├── PageId.kt
│ ├── PageGeometryHint.kt
│ ├── ReaderViewport.kt
│ ├── ReaderScene.kt
│ ├── VisibleNode.kt
│ ├── ReaderFrame.kt
│ ├── ViewportMotion.kt
│ ├── VisibleRegionResolver.kt
│ └── ReaderProgressSnapshot.kt
│
├── image/ // 图像资源决策与流水线
│ ├── ReaderImageAsset.kt
│ ├── ReaderImagePipeline.kt
│ ├── ReaderLodPolicy.kt
│ ├── ReaderTileManager.kt
│ ├── PixelUsage.kt
│ └── DecodePlanner.kt
│
├── render/ // 可插拔渲染器实现
│ ├── ReaderRenderer.kt
│ ├── compose/ // Telephoto 现有实现 + 新 ComposeSceneRenderer (DrawModifierNode/GraphicsLayer)
│ ├── canvas/ // 对照组 AndroidViewSceneRenderer (View onDraw)
│ └── webgpu/ // 隔离的 WebGPU 适配器
│
└── ui/ // Activity, ViewModel, Chrome, Menu, Settings测试原则:
core/下的几何相交计算(VisibleRegionResolver)、锚定修正(AnchoredCorrection)、页面排布与进度结算必须具备高分支覆盖度与边界值单元测试,不追求全包盲目的 100% 形式主义覆盖率。
五、 验证路线图与评判准则
阶段规划
- 分支隔离(已完成):
- 将现有 13 个 WebGPU 提交完整封存在
feat/webgpu-reader分支,状态标记为UPSTREAM-TRACKED。 devel恢复纯净,消除 NDK 依赖与冷启动监控对主干的干扰。
- 将现有 13 个 WebGPU 提交完整封存在
- Phase 0:多维基准建立(Benchmark Baseline):
- Phase 0A:渲染器基线(Renderer Baseline,已完成):
- 在 ARR / 120Hz 物理测试机上运行固定渲染夹具(100 页条漫,隔离 UI/Layout/Renderer 抽象开销);
- 依据真机实测数据执行 Tie-Breaker 决策:
ComposeSceneRendererP99 Overrun 达到 -5.2ms,彻底消除通用列表掉帧,胜出 View 对照组。
- Phase 0B:生产阅读器基准(Production Reader Benchmark,已完成):
- 升级至完整生产链路:包含 72 页真实局部确定性图片集、真实 Coil 解码与页面变换(PageTransformation)、双向资源状态机(
PRESENTATION_READY↔SOURCE_READY降级与驱逐)及 120Hz 刷新率意图协同; - 执行两套模式:
- Primary Mode:
CompilationMode.Partial(BaselineProfileMode.UseIfAvailable),拟合生产安装与 Baseline Profile 场景; - Diagnostic Mode:
CompilationMode.Full,消除 JIT 噪音,观察纯理论上限;
- Primary Mode:
- 涵盖突发与持续多轮遍历测试:
- Burst Benchmark(24 次连续大跨度 Fling):考察 P99 逾期、Frame CPU 耗时、显存峰值与 Presentation 资产界限;
- Sustained Benchmark(2 轮双向 72 页全章节往复遍历,1000+ 帧):记录平稳态内存(
MemoryUsageMetric.Last)、Native RSS Anon 衰减与资源双向降级回收行为。
- 升级至完整生产链路:包含 72 页真实局部确定性图片集、真实 Coil 解码与页面变换(PageTransformation)、双向资源状态机(
- Phase 0A:渲染器基线(Renderer Baseline,已完成):
- PoC A:Scene 几何抽象:
- 实现纯几何的
ReaderScene,让现有阅读器与新 Scene 并行计算,验证页面几何、可见集合及阅读语义与现有行为等价(浮点几何允许定义明确的 epsilon 容差;若发现旧实现缺陷,以显式行为变更记录处理而非机械迁就旧 bug)。
- 实现纯几何的
- PoC B:Webtoon 视口实验(战略 A/B 对照):
- 保持功能极简(仅垂直滚动、无缩放、无 OCR、固定图集),构建两个极小渲染器:
- Candidate A(首选主力):
ComposeSceneRenderer- 测试 Immediate(
DrawModifierNode/drawBehind)与 Retained(单页独立GraphicsLayer)两种机制;
- 测试 Immediate(
- Candidate B(参考对照):
AndroidViewSceneRenderer(单 ViewonDraw(Canvas));
- Candidate A(首选主力):
- 归因分析:验证性能红利是否本质源自“脱离通用 LazyColumn 抽象与建立专属 2D Scene”,而非“必须退回 Android View”。
- 保持功能极简(仅垂直滚动、无缩放、无 OCR、固定图集),构建两个极小渲染器:
评估指标与 Go/No-Go 准则
评判标准相对于 Phase 0 建立的 Baseline 定义,统一纳入刷新率意图(preferredFrameRate 协同):
| 评估维度 | 核心主指标 (Primary) | 辅助指标 (Secondary) | 达到标准(GO) | 放弃或退回(NO-GO) |
|---|---|---|---|---|
| 帧稳定性 | frameOverrunMs P95/P99 | frameDurationCpuMs P95/P99 | 尾部逾期时间(P99 Overrun)显著缩短,消除极端掉帧尖峰 | Overrun 无明显改善,依然频发错过 Hardware Deadline |
| 卡顿率 | Deadline Miss Rate | Worst-frame Trace | 严重丢帧率明显下降 | 掉帧率持平或恶化 |
| Java 内存 | Heap Max | Allocations / GC Count | Fling 期间垃圾回收暂停次数与瞬时分配量趋近于零 | 内存抖动未减,GC 依然打断渲染管线 |
| Native 内存 | RSS Anon Max | RSS Shmem | 内存峰值有界平稳 | 存在非托管内存堆积或泄漏风险 |
| GPU 显存 | memoryGpuKb Max | Texture Upload Trace | 纹理显存平稳有界,无主线程 Stall | 突发显存占用过大导致 OOM |
| 功耗与持续性 | Energy / Battery Drain | Thermal / Headroom Drift | 长时间滚动能耗平稳,无剧烈热降频 | 持续功耗异常飙升,迅速触发系统温控降频 |
| 系统复杂度 | 代码增量 (LOC) / 异常率 | OEM-specific Workarounds | 逻辑清晰,无特定厂商驱动黑洞与穿孔 Bug | 代码量膨胀严重,引入新的系统级黑盒缺陷 |
Go/No-Go 判定与实测决胜结果:
- 优先要求在 P99
frameOverrunMs、GC/Allocation 次数或内存峰值中至少一项出现显著改善,且其他关键指标不存在明显回归; - 若
ComposeSceneRenderer表现与AndroidViewSceneRenderer相当,直接采纳 Compose 方案,终止 View 方案的进一步扩张。
Phase 0A 固定夹具真机实测数据(Android 16 / SDK 37, 100 页条漫夹具,Full Compilation)
| 渲染器候选 | frameDurationCpuMs P50 | P90 | P95 | P99 | frameOverrunMs P99 | 判定结论 |
|---|---|---|---|---|---|---|
lazy (通用 LazyColumn 对照组) | 1.3 ms | 5.5 ms | 5.6 ms | 7.0 ms | +0.3 ms (逾期错过 deadline) | 基线存在掉帧尖峰 |
compose_scene (ComposeSceneRenderer) | 1.9 ms | 3.2 ms | 3.6 ms | 6.8 ms | -5.2 ms (安全余量 5.2ms) | 显著改善,彻底消除 P99 逾期 |
view_scene (AndroidViewSceneView 参考组) | 1.9 ms | 3.3 ms | 3.6 ms | 4.1 ms | -8.8 ms (安全余量 8.8ms) | 极小差距,但引入 View 互操作成本 |
决胜决策(Tie-Breaker Executed):
ComposeSceneRenderer将通用列表的 P99 逾期(+0.3ms)彻底消除至安全区间(-5.2ms),且 P90/P95 帧耗时与原生 View Canvas 完全持平(3.2ms/3.6ms vs 3.3ms/3.6ms);- 依据決胜原则,正式确定 ComposeSceneRenderer 为阅读器主攻架构,ViewScene 仅作为参考对照存在,不进行进一步业务功能扩张。
Phase 0B 生产图片管线与真实解码真机实测数据(Android 16 / SDK 37, 72 页变高条漫图集)
测试机型:Xiaomi Redmi Note 12 Turbo (warsaw),高通第二代骁龙 7+ (Snapdragon 7+ Gen 2),Android 16 (SDK 37),HyperOS,请求并验证 120Hz 物理刷新率。 测试夹具:ReaderProductionBenchmarkActivity 生成 72 页确定性本地条漫图集(files/reader-benchmark/v1/),包含 800×1440、800×2400、800×1280、1200×800 等多分辨率真实尺寸抖动,走完整 Coil 磁盘解码、ComposeReaderPageTransformation 裁剪与 KototoroImagePipelineAdapter 双向状态机。 同步机制:通过首帧双 Choreographer 回调向测试套件发送跨进程广播,严格等待第一屏真实解码渲染完成才开始计量。
1. Burst 突发滑动基准(Primary Mode: CompilationMode.Partial, 5 次迭代,24 次连续 Fling)
| 指标维度 | Legacy ComposeWebtoonReader | ComposeSceneWebtoonReader | 改善幅度与结论 |
|---|---|---|---|
frameDurationCpuMs P50 | 4.30 ms | 2.70 ms | -37.2%(帧 CPU 中位耗时大幅降低) |
frameDurationCpuMs P90 | 6.70 ms | 4.60 ms | -31.3% |
frameDurationCpuMs P95 | 7.70 ms | 4.90 ms | -36.4% |
frameDurationCpuMs P99 | 10.20 ms | 6.20 ms | -39.2%(CPU P99 稳固在 120Hz 8.33ms 预算内) |
frameOverrunMs P99 | -2.40 ms | -5.90 ms | +3.50 ms 硬件截止期安全余量(抗突发卡顿鲁棒性显著提升) |
| Active Presentation Assets (Max / Last) | N/A (未做管线受控) | 10.0 / 9.0 | 严格有界(无论快速滑行多远,活跃 Bitmap 资产固定在保留窗口内) |
| GPU Memory Max (Median) | 130.1 MB | 133.7 MB | +3.6 MB(Lookahead 预测窗口预加载纹理带来合理的受控开销) |
| Heap Size Max (Median) | 92.3 MB | 91.8 MB | 基本持平(-0.5 MB) |
2. Sustained 持续多轮往复遍历基准(2 轮双向 72 页遍历,1000+ 渲染帧)
| 指标维度 | Legacy ComposeWebtoonReader | ComposeSceneWebtoonReader | 改善幅度与结论 |
|---|---|---|---|
平稳态匿名内存 (RssAnon.Last) | 331.9 MB | 213.2 MB | -35.8%(-118.7 MB 稳态驻留降低) |
峰值匿名内存 (RssAnon.Max) | 353.0 MB | 236.1 MB | -33.1%(-116.9 MB 峰值节省) |
frameDurationCpuMs P50 | 3.80 ms | 2.40 ms | -36.8% |
frameDurationCpuMs P99 | 8.80 ms | 5.60 ms | -36.4%(持续长程滑动下维持绝对平稳) |
frameOverrunMs P99 | -3.60 ms | -6.90 ms | +3.30 ms 硬件截止期安全余量 |
| Active Presentation Assets 状态机 | N/A | 峰值 11.0 → 静止 8.0 | 验证双向状态机(离开视口自动从 Presentation 降级为 Source) |
3. Diagnostic 全量预编译基准(Diagnostic Mode: CompilationMode.Full, 5 次迭代)
| 指标维度 | Legacy ComposeWebtoonReader | ComposeSceneWebtoonReader | 改善幅度 |
|---|---|---|---|
frameDurationCpuMs P50 | 2.53 ms | 1.74 ms | -31.2% |
frameDurationCpuMs P99 | 5.62 ms | 3.97 ms | -29.4% |
阶段结论(Phase 0B Conclusion):
- 帧渲染余量显著扩增:
frameDurationCpuMs衡量 UI/RenderThread 的 CPU 执行耗时,frameOverrunMs决定是否错过显示硬件 VSYNC Deadline。在 120Hz 刷新率下,旧版ComposeWebtoonReader的 P99 CPU 达到 10.20ms(逼近显示周期),P99 Overrun 为 -2.40ms;而ComposeSceneWebtoonReader将 P99 CPU 压降至 6.20ms(-39.2%),P99 Overrun 进一步拓宽至 -5.90ms(突发)与 -6.90ms(长程),为系统调度与突发波动留出了 +3.3~3.5ms 的硬件截止期安全缓冲; - 内存架构闭环验证:双向
PRESENTATION_READY↔SOURCE_READY状态机经受住了 72 页全图往复长程遍历的严苛考验。在本次 72 页双向重复遍历中,Scene Reader 的 presentation working set 始终保持在 8~11 个 asset,RSS Anon 未随遍历轮次呈现 Legacy 路径的高位驻留(Sustained 稳态 RSS Anon 降低 35.8% / 118.7 MB,峰值降低 33.1% / 116.9 MB)。
Phase 1 实施进度(图像解码决策与切片瓦片引擎)
Phase 1A:解码决策智能(Decode Intelligence,已完成,commit 4410872b2)
- 纯 Kotlin 决策核心,零 Android/UI 依赖:
IntRect/PixelUsage/DecodeAllocatorPolicy/RendererCapabilities(Unknown → 首帧 Resolved,保守回退 4096)/TilePolicy(策略与硬件能力拆分)/ImageSourceMetadata/ImageSourceGeometry(Crop contentRect + EXIF 方向的逻辑→源图坐标映射)/ReaderLodPolicy(Target-Based Sampling:targetDecodeWidthPx = min(source, ceil(display × scale × overscan)),2 的幂 sampleSize,滞后防抖)/DecodePlan(Single / SampledSingle / Tiled / AnimatedSingle +AnimatedFallback超限安全降级)/DecodePlanner(多维决策矩阵)。 - 测试:
IntRectTest/ReaderLodPolicyTest/DecodePlannerTest/ImageSourceGeometryTest全绿(纯 JVM)。
Phase 0C/P0 修复:Warm Backend Switch 首屏模糊(commit 81e5e946a)
- 根因:
getCachedAsset()把未知质量的 Coil memoryCache 位图直接包装为ComposeImage并storeAsset晋级为PRESENTATION_READY资产;资源窗口看到已有ComposeImage即跳过重解码,首屏持续低清拉伸,直至页面离开再回到 presentation window 才触发正式解码。 - 修复:拆分"几何探测"与"正式呈现资产晋级"两个职责 —— 新增
ReaderImagePipeline.probeCachedDimensions(pageId)仅供PageGeometryHint.Exact使用(零资产副作用);getCachedAsset()不再晋级 memoryCache 条目(回落Encoded),presentation 一律经acquireAsset()正式解码。新增Trace.setCounter("Reader.PresentationWidthPx")供后续质量基准沿用。 - 验证:回归测试
warm backend switch from legacy with low-res memoryCache entry does not pollute presentation assets(160×480 低清 cache →Encoded→PRESENTATION_READY触发 1080×3240 正式解码);ReaderImageAssetTest14/14 全绿;真机复测模糊现象消失。
Phase 1B:图源与切片运行时(Source + Tile Runtime,已完成)
RegionDecodeSource/TileDecodeSession(计划 1.4 命名):区域解码源与会话抽象;会话复用 = 每页仅一次原生解码器解析,任意长条图的全部瓦片共享同一TileDecodeSession。解码坐标均为原始编码空间(EXIF / Crop / Split 由几何层折算)。TileGrid:逻辑页 → 源图坐标变换(Crop contentRect → Split 左右半页 → EXIF 方向旋转)。Gutter 与 sampleSize 联动(计划 1.6):sourceGutterPx = outputGutterPx × sampleSize,源图解码区域向外扩展 gutter 消除独立采样相位差导致的黑缝;屏幕目标渲染矩形(logicalRect)严格拓扑拼接不重叠。TileKey以TileKind.LATTICE / OVERVIEW区分格点瓦片与整页 LOD0 底带,键空间零冲突。TileMemoryBudget(四级智能驱逐):保留级VISIBLE(钉驻,压力下永不驱逐)→ NEARBY(前瞻窗口)→ STANDBY(首次脱离需求)→ CACHE(二次脱离);同级按 LRU;时钟可注入(确定性测试);超预算的 VISIBLE 突发允许临时超额(宁可超预算不可丢可见像素)。ReaderTileManager:请求驱动编排 —— 会话复用(putIfAbsent注册竞态收敛)、瓦片任务LAZY启动去重、脱离需求的在飞任务取消、驱逐回调(payload 释放钩子payloadReleaser,1C 可接Bitmap.recycle())、会话打开失败去重上报且支持重试(Result包装阻断结构化并发向父级 scope 传播业务失败)。AndroidRegionDecoderFactory:BitmapRegionDecoderAPI 26~37 兼容构造(SDK ≥ S 走非废弃重载);URI 三态分发(content+zip://流式扫描 /zip://+ZipFile直读条目 / 通用contentResolver.openInputStream),与既有ZipSubSamplingImageSource/NativeSubSamplingImageSource的取流策略对齐;EXIF 旋转一次性折入ImageSourceGeometry;ARGB_8888SOFTWARE 解码对齐PixelUsage.REGION_TILE的分配策略;ReentrantReadWriteLock守护 decode 与 recycle 的关闭顺序。- 测试:
TileGridTest(13)/TileMemoryBudgetTest(9)/ReaderTileManagerTest(11)全绿,共 33 例纯 JVM(fake session 模拟 Android 解码器:会话复用、四级驱逐、在飞取消、失败重试、overview 钉驻、缺源去重上报)。
Phase 1C:渲染器集成与管线适配(Renderer Integration & Pipeline Adapter,已完成)
- TileStore 抽象与无重组刷新桥:
- 新增 [
TileStore](file:///e:/kototoro_demo/Kototoro/app/src/main/kotlin/org/skepsun/kototoro/reader/image/TileStore.kt) 接口解耦查询与监听,ReaderTileManager实现此接口。 - 新增 [
TileDrawModifierNode](file:///e:/kototoro_demo/Kototoro/app/src/main/kotlin/org/skepsun/kototoro/reader/render/compose/TileDrawModifierNode.kt):通过Modifier.tileDrawBridge(tileStore)挂载到根节点,监听onTileReady与onTileDropped事件并在Dispatchers.Main.immediate上直接调用invalidateDraw(),实现零 Recomposition / 零 Layout 的纯绘制期重绘调度。
- 新增 [
- ComposeSceneRenderer 多态分块渲染:
- 支持 [
ReaderImageAsset.Tiled](file:///e:/kototoro_demo/Kototoro/app/src/main/kotlin/org/skepsun/kototoro/reader/image/ReaderImageAsset.kt) 呈现:- LOD0 Overview 底带:在格点瓦片就绪前平铺全页低分辨率预览,避免白屏;
- 高精度格点瓦片(Lattice Tiles):通过 [
TiledPageDrawMath](file:///e:/kototoro_demo/Kototoro/app/src/main/kotlin/org/skepsun/kototoro/reader/render/compose/ComposeSceneRenderer.kt) 精确反向裁剪decodeRegion外扩的采样 Gutter,无缝拓扑拼接; - 几何与旋转兼容:根据
ImageSourceGeometry.orientationDegrees执行 pivot 旋转,支持横屏/EXIF 旋转及左右双页切分(TileSplit)。
- 支持 [
- KototoroImagePipelineAdapter 管线闭环:
- 接入
RegionDecoderFactory、DecodePlanner、actualTileManager; acquireAsset自动决策:当DecodePlanner.plan返回DecodePlan.Tiled时,生成ReaderImageAsset.Tiled并请求 LOD0 Overview,自动上报composePipeline.onImageDecoded;- 动态瓦片请求:在
updateResourceWindow中对视口内的可见 Tiled 节点折算逻辑坐标并调用adapter.requestTiles(pageId, visibleLogical); - 完善双向生命周期:离开保留窗口触发
evictAsset或降级downgradeToSource时调用actualTileManager.releasePage(pageId),及时回收会话与 Bitmaps。
- 接入
- 自动化测试验证:
- [
TileDrawModifierNodeTest](file:///e:/kototoro_demo/Kototoro/app/src/test/kotlin/org/skepsun/kototoro/reader/render/compose/TileDrawModifierNodeTest.kt)(6 例):验证节点挂载/解挂、Store 动态变更切换监听、Modifier 链构造; - [
ComposeSceneRendererTiledTest](file:///e:/kototoro_demo/Kototoro/app/src/test/kotlin/org/skepsun/kototoro/reader/render/compose/ComposeSceneRendererTiledTest.kt)(3 例):验证内层瓦片 Gutter 裁剪、sampleSize 降采样比例折算、右半页双页切分偏移计算; - [
KototoroImagePipelineAdapterTiledTest](file:///e:/kototoro_demo/Kototoro/app/src/test/kotlin/org/skepsun/kototoro/reader/image/KototoroImagePipelineAdapterTiledTest.kt)(6 例):验证ReaderPageSplit到TileSplit映射、Tiled 资产生成与 Overview 预取、多页切分、requestTiles转发、页面驱逐清理、异常降级单图; - [
ReaderImageAssetTest](file:///e:/kototoro_demo/Kototoro/app/src/test/kotlin/org/skepsun/kototoro/reader/image/ReaderImageAssetTest.kt)(14 例全绿):验证与现有单图/缓存管线及 Warm switch 完全兼容零回归。 - 全套共 29 例 Phase 1C/Asset 纯 JVM 测试 100% 通过。
- [
Phase 1D:真机超长条漫效能与长图画质基准(已完成):
- 测试环境:Xiaomi Redmi Note 12 Turbo (Snapdragon 7+ Gen 2), Android 17 (SDK 37), HyperOS, 物理 120Hz 刷新率, 12 页超长条漫图集(高度 12,000px ~ 40,000px,宽度 1080px,累计纵向跨度 290,000+ 像素,单张原始 Bitmap 展开最高达 172.8MB,超过 16384px GPU 硬件纹理极限),
CompilationMode.Full(), 5 轮迭代大跨度连续 Fling。
超长条漫实测数据对比(Ultra-Long Webtoon Benchmark: 12k ~ 40k px Strips)
| 指标维度 | Legacy ComposeWebtoonReader | ComposeSceneWebtoonReader (Tiled) | 优势与结论 |
|---|---|---|---|
GPU 显存中位峰值 (GpuMaxKb.Median) | 321,136 KB (~313.6 MB) | 121,908 KB (~119.0 MB) | -62.0% 显存压降 (-194.6 MB)!传统单图架构引发显存暴涨,Scene 仅将视口内 1024x1024 瓦片载入显存,严格受限 |
GPU 显存最大峰值 (GpuMaxKb.Max) | 335,440 KB (~327.6 MB) | 122,212 KB (~119.3 MB) | -63.6% 显存节省 (-213.2 MB) |
frameDurationCpuMs P50 | 2.72 ms | 1.32 ms | -51.5%(CPU 执行耗时减半) |
frameDurationCpuMs P90 | 4.13 ms | 2.48 ms | -40.0% |
frameDurationCpuMs P95 | 4.34 ms | 3.10 ms | -28.6% |
frameDurationCpuMs P99 | 6.43 ms | 4.48 ms | -30.3%(稳居 120Hz 8.33ms 预算的一半以内) |
frameOverrunMs P50 | -9.96 ms | -11.02 ms | 截止期余量扩大 1.06ms |
frameOverrunMs P99 | -5.52 ms | -6.22 ms | 截止期安全余量扩大 0.70ms(P99 口径:≥99% 帧未超时;不据此断言零掉帧,见 改进计划 §2) |
有效绘制吞吐 (frameCount.Median) | 248.0 帧 | 409.0 帧 | +64.9% 有效帧吞吐(手势滑动丝滑平顺,无主线程掉帧卡顿) |
| Active Presentation Assets | N/A (未跟踪) | 10.0 / 9.0 (Max / Last) | 严格有界(视口之外的格点瓦片与单图被 TileMemoryBudget 及时回收) |
持续往复遍历数据对比(Sustained Traversal Benchmark: 72 页 2 轮全图 80 次滑动,1200+ 帧)
| 指标维度 | Legacy ComposeWebtoonReader | ComposeSceneWebtoonReader | 优势与结论 |
|---|---|---|---|
稳态匿名内存 (RssAnon.Last) | 370.7 MB | 258.9 MB | -30.2% (-111.8 MB 稳态驻留降低) |
frameDurationCpuMs P50 | 2.54 ms | 1.93 ms | -24.0% |
frameDurationCpuMs P99 | 5.02 ms | 4.10 ms | -18.3% |
有效绘制吞吐 (frameCount.Median) | 1,010.5 帧 | 1,236.5 帧 | +22.4% 吞吐提升 |
| Active Presentation Assets | N/A | 峰值 10.5 → 静止 7.0 | 验证双向状态机(离开视口自动回收) |
Phase 1 总体结论: 从 Phase 1A 解码决策到 1B 切片运行时、1C 无重组刷新桥、1D 真机超长图极限压测,完整闭环证明了 ADR 0002 架构设计的正确性与卓越效能: 在应对 10,000 ~ 40,000 像素极高条漫长图时,不仅打破了 Android GPU 16384px 的硬件纹理上限天花板,同时斩获 62% 的显存节省 与 50%+ 的 CPU 帧执行耗时压降,在 120Hz 高刷物理设备上实现了无白屏的连续呈现(P99 口径下未见截止期超时;最慢约 1% 帧不在该口径内,见 改进计划 §2)。
Phase 2 实施进度(生产化功能对齐与多章节视口锚定)
Phase 2A:生产级功能对齐与跨章节无感锚定(已完成)
- 页面间隙(Page Gaps):
VerticalReaderScene构造注入pageSpacingPx: Int = 0,排版算法在连续页面间插入物理间隔,总高度精确涵盖间隙;- 二分查找视口相交时,视口落在两页间隙时平滑归属,无丢帧或索引崩溃;
ComposeSceneWebtoonReader接入isGapsEnabled与R.dimen.webtoon_pages_gap,支持动态开启与关闭。
- 跨章节平滑扩缩窗与无感锚定(Seamless Cross-Chapter Window Expansion & Preserved Anchoring):
VerticalReaderScene.updatePages(newPages, currentViewport):- 保留 Exact 几何尺寸:动态换章或窗口滑动时,保留已成功解码的精确尺寸,严禁退回 Estimated 默认估算;
- 精准滚动位移补偿:前向插入上一章节或后向扩展下一章节时,以当前视口主导阅读页面为锚点计算新旧坐标差
deltaY,由scrollState.snapBy(deltaY)实施绝对像素级补偿,用户眼前画面零跳跃、零白屏、零状态重置。
- 统一错误与重试交互(Unified Error & Retry UX):
SceneReaderLoadStatus对齐 Kototoro 统一的ReaderPageError组件;- 完整串联
onRetryError、onShowErrorDetails与resolveErrorStringId,消除占位 UI,实现与其他阅读器完全一致的错误处理与重试弹窗交互。
- 内存优化模式对齐(Reader Optimization):
KototoroImagePipelineAdapter接入isReaderOptimizationEnabled,在开启时将 Coil 图像请求的memoryCachePolicy设为DISABLED,完全由 Reader 专用状态机与TileMemoryBudget管控缓存,杜绝底层图片库的双重内存驻留。
- 自动化测试验证:
VerticalReaderSceneTest新增 3 项核心用例:lays out pages with pageSpacingPx between pagesupdatePages preserves existing exact hints and anchors prepended pages with zero jumpupdatePages appending pages returns zero deltaY
- 全套 Reader JVM 单元测试(46 例)100% 通过。
Phase 2B:2D 契约抽象、双向锚定补偿与默认全量推广(已完成)
- 2D 通用场景契约:抽离
MutableReaderScene与SceneReadingDirection(TOP_TO_BOTTOM、LEFT_TO_RIGHT、RIGHT_TO_LEFT),定义通用的AnchorCompensation(deltaX, deltaY)双向坐标补偿机制。 - 全量默认启用验证:在 Webtoon 模式下将
isExperimentalSceneReaderEnabled设为生产默认值,验证架构长久稳定性。
Phase 2C & 2C2:横向连续瀑布流场景与宿主交互(已完成)
- 横向连续场景引擎 (
HorizontalReaderScene):- 严格满足 Invariant I1,纯 Kotlin 实现无 Android 依赖;
- 支持 LTR(从左至右)与 RTL(日漫从右至左)对称布局;
- 高性能 $O(\log N)$ 二分查找视口相交区域,实时派发
ReaderFrame与ReaderProgressSnapshot; - 动态估算替换精确几何尺寸时的 X 轴零跳动锚定补偿(Zero-CLS)。
- 横向连续宿主 (
ComposeSceneHorizontalReader/ComposeHorizontalSceneRenderer):- Draw Phase 纯绘制限制,跳过 Composition/Layout;
- 物理惯性滑动、双向拉动切章与自适应刷新率(ARR)集成。
Phase 3 实施进度(现代分页场景引擎与统一宿主)
Phase 3A:纯 Kotlin 分页几何模型与排版调度(已完成)
- 核心模型:
PagedReaderScene:实现MutableReaderScene,负责分页与并页插槽管理、视口求交与平移结算;PagedSpreadResolver:实现插槽排布算法,严格遵循 Kototoro 业务规范:- 跨章隔离:不同章节页面绝对不合并在同一插槽;
- 宽页与封面独立:宽高比 > 1.15 的宽页(
WidePagePolicy)或封面偏移页独占单插槽; - 日漫 RTL 镜像排布:在 RTL 模式下,插槽内先读页居右、后读页居左,阅读顺序严格保序;
PagedSnapResolver:纯数学离散吸附解析器,根据位移比例(默认 20%)与归一化滑动速度(默认 0.5f)解析目标插槽。
- 单元测试:
PagedReaderSceneTest、PagedSpreadResolverTest、PagedSnapResolverTest100% 覆盖。
Phase 3B:分页影子模式校验器与等价性验证(已完成)
- 影子校验器 (
PagedShadowValidator):- 针对生产真实
ReaderPage列表,并行比对既有DoublePageSpreadModel与现代PagedSpreadResolver的排页、跨章隔离与锚点; - 在既有
ComposeDoublePageReader中加入无感运行时采样(sampleRuntimeParity),单次开销 < 0.1ms;
- 针对生产真实
- 等价性套件 (
PagedShadowParityTest):- 覆盖奇数/偶数章节、封面偏移开启/关闭、跨章节混合图集、日漫 RTL 翻页及 100 轮随机模糊测试(Fuzz testing),达成 100% 零差异。
Phase 3C:现代分页场景 Compose 宿主 (ComposeScenePagedReader)(已完成)
- 单双页统一架构:
- 将单页模式(Standard LTR、Reversed RTL、Vertical Paged)与双页并页模式统一由
ComposeScenePagedReader承载; - 结合
PagedReaderScene与drawFrameNodes,滚动平移与翻页过渡严格约束在 Draw Phase,彻底跳过 Composition 与 Layout。
- 将单页模式(Standard LTR、Reversed RTL、Vertical Paged)与双页并页模式统一由
- 交互与手势:
- 单指拖拽驱动插槽无缝平滑滑动;
- 手势释放由
PagedSnapResolver结算目标并触发平滑弹性吸附动画; - 双指缩放(1x ~ 5x)与双击缩放切换;
- 视口两端 Pull 手势触发上一章/下一章切换。
- 管线与零跳动补偿:
- 接入
KototoroImagePipelineAdapter,维护插槽级资源窗口(当前插槽PRESENTATION_READY,相邻插槽SOURCE_READY,移出插槽及时淘汰); - 尺寸解析时通过
AnchorCompensation实施零 CLS 视觉补偿。
- 接入
Phase 3D:生产路由接线、交互测试与 ADR 归档(已完成)
- 生产分发路由 (
ComposeReaderScreenRoot.kt):- 在
isExperimentalSceneReaderEnabled开启时,分发至现代ComposeScenePagedReader; - 在开关关闭时,100% 无损回落至既有
ComposeDoublePageReader与ComposePagedReader;
- 在
- 交互集成测试 (
ComposeScenePagedInteractionTest.kt):- 覆盖离散插槽隔离、双页并页跨章阻断、RTL 日漫左右排布、吸附阈值、过渡求交与动态更新补偿。全套测试 100% 通过。
Phase 3E:图像呈现 Parity 补齐与生产门控解耦(已完成)
- 架构抽离与去重 (
SceneImagePresentationCoordinator):- 抽取统一呈现协调器,解决分页场景超大图(如 $6000 \times 9000$)因未请求可见切片而仅渲染 LOD0 Overview 导致发糊的核心 Blocker;
- 统一 Webtoon、Horizontal 与 Paged 三大场景宿主的 Frame 可见切片计算(
coordinateVisibleTiles)与视口逆向变换数学(computeVisibleBounds),消除重复实现。
- 分辨率感知多 LOD 缩放(Resolution-aware Multi-LOD):
- 接入 150ms Camera Settle 防抖与
ReaderCameraSnapshot,高倍放大($2\times \sim 5\times$)时自动降级采样率并请求高清视口瓦片,缩放回归 1x 时自动释放过采样切片。
- 接入 150ms Camera Settle 防抖与
- 绘制生命周期闭环 (
TileDrawBridge&AnimatedDrawBridge):- 挂载
.tileDrawBridge(adapter.tileStore),后台解码切片就绪时直接触发局部 Draw-phase 重绘,跳过 Compose Recomposition; - 挂载
AnimatedDrawBridge并传入drawFrameNodes,打通 GIF / Animated WebP 的逐帧回调、可见时启动、不可见时暂停的生命周期。
- 挂载
- 设置与排版 Parity:
PagedSpreadConfig与PagedSpreadResolver全面支持ZoomMode(FIT_CENTER、FIT_WIDTH、FIT_HEIGHT、KEEP_START);- 支持
ReaderAnimation(NONE时立即 snap,动画模式平滑过渡); - 支持
readerBackground与bookBackgroundTint。
- 生产门控安全解耦:
- 新增
isExperimentalPagedSceneReaderEnabled(默认false),与成熟且已默认开启的 Webtoon / Horizontal 场景解耦; - 保证未手动开启分页实验开关的用户 100% 继续使用稳定的
Pager + Telephoto,零生产回退风险。 - 2026-09-21 更新(交付 45):门控收敛为按系列独立的两个开关,宿主选择由唯一纯函数
resolveSceneReaderEnabled(mode, isDoublePage, webtoonSceneReader, pagedSceneReader)(reader/ui/config/SceneReaderGate.kt)决定 —— 双页与单页/上下取分页开关,WEBTOON取条漫开关; 此前写成isExperimentalSceneReaderEnabled && isExperimentalPagedSceneReaderEnabled,使条漫开关 成为分页宿主的隐藏总闸(关掉条漫渲染器会连带关掉分页场景)。两个开关同时暴露在阅读器更多面板的 「阅读」页(ComposeReaderOptionsSheet),与设置页共用同一份 pref,默认值不变。CONTINUOUS_HORIZONTAL无 legacy 渲染器,属 scene-only,不参与该路由(详见SceneReaderGateKDoc)。
- 新增
Phase 3F:交互溢出、自适应背景与物理基准对齐(功能已实现,真机基准待验收)
- 基线溢出平移数学解算器 (
PagedPanBoundsResolver):- 严格保持 I1(
ReaderCore零 Android / Compose 依赖),提取纯数学解算器; - 彻底重构手势判定,将旧有的
canvasScale > 1f单纯缩放判定升级为基于transformedContentBounds > viewport的轴向精确解算; - 解决
FIT_WIDTH(高超出视口)与FIT_HEIGHT(宽超出视口)在 $1\times$ 默认比例下无法单指拖动查看边缘的交互 Blocker; - 支持单页与双页排版模式下的起始点对齐(
resolveInitialOverflowOffset),翻至新页时自然对齐至页首(LTR/Vertical 从顶部/左侧开始,RTL 从右侧开始)。
- 严格保持 I1(
- 主轴溢出手势交接 (
PagedDragState):- 先消费内容平移,到达边缘后仅将剩余位移用于翻页;反向拖动先收回翻页位移,再恢复内容平移。
- 内容平移不触发翻页 fling;交接时重置翻页速度记录,多指缩放介入时取消翻页与拉动切章状态。
- JVM 回归覆盖位移分配、方向、反向交接和松手目标计算;不替代真实指针事件与设备交互验收。
ReaderBackground.AUTO自适应背景采样:- 在
ComposeScenePagedReader中无缝消费retainedAssets内存位图(ComposeImage、AndroidBitmap、Tiled.overviewBitmap); - 调用
ReaderAutoBackground.resolve()与resolveDoublePageBackground(),零额外磁盘/网络 IO 开销完成单/双页边缘背景色采样与书籍色调合成。 - 书籍色调仅应用于
AUTO背景,固定白色等用户指定背景不受影响。
- 在
- 用户设置开关暴露:
- 在
ReaderSettingsScreen补充reader_experimental_paged_scene_engine开关项与中英文文案,允许测试人员与用户自主开启或回滚。
- 在
- 物理机基准对齐矩阵(Hardware Parity Matrix):
- 以下为待执行的验收矩阵,本阶段尚未记录 Paged 真机实测结果;Paged 实验开关保持默认关闭。
- 确立进入生产默认开启的 4 组真机对照场景:
- 普通漫画单页 1x(CPU P99 帧耗时与 Baseline Profile 稳态);
- $6000 \times 9000$ 超大图 1x(LOD0 Overview 基础切片就绪与防白屏);
- $6000 \times 9000$ 高倍放大(2.5x / 5x Settle 防抖与高清切片加载延迟及显存上限);
- 双页跨章节持续往复翻页(RSS Anon 内存泄露审计与 GC 频率)。
- 尚未完成的动画一致性:
DEFAULT、ADVANCED、SIMULATION当前共用滑动过渡,尚不具备独立动画样式。
六、 Non-Goals(第一阶段明确不做的事项)
为了控制工程范围,第一阶段严格禁止发散:
- 不重写现有阅读器的全部交互与手势;
- 不重写物理惯性引擎(
Physics,通过ViewportMotion承接); - 不在第一阶段直接开发完整的 Tile 动态切片与 LOD 高级引擎;
- 不在第一阶段废弃或替换现有的 Telephoto 单页/双页阅读模式;
- 不自行实现或重构 WebGPU 的 Continuous 条漫模式(交由上游 Mihon 推进);
- 不将
LowLatencyCanvasView(前台双缓冲/画笔向)纳入 Webtoon 实验。
七、 架构不变量(Architectural Invariants)
未来所有与阅读器相关的代码提交与代码审查(PR Review),必须严格核对是否违背以下 6 条不变量:
- I1:
ReaderCoreMUST NOT depend on Android UI toolkit or renderer APIs, nor Android platform geometry types (android.graphics.Rect/RectF/Matrix,androidx.compose.ui.geometry.*).(ReaderCore 绝不得依赖 Android UI 工具包、渲染器 API 或 Android 平台几何类,必须使用自包含的跨平台几何抽象) - I2:
ImagePipelineMUST NOT own chapter progress, reading direction, spread semantics, or viewport UI state.(ImagePipeline 绝不得持有章节进度、阅读方向、双页语义或视口 UI 状态) - I3:
RendererMUST NOT be the source of truth for reader semantics.(Renderer 绝不得成为阅读语义的真相之源) - I4: Renderer-specific resource representations ARE allowed.(允许 Renderer 拥有特化的资源表征形态)
- I5: Scene geometry MUST remain computable when image pixels are unavailable.(在图片像素不可用时,Scene 几何结构必须依然保持可计算)
- I6: Backend replacement MUST NOT require rewriting progress/layout semantics.(替换渲染后端绝不得要求重写进度或布局语义)