Repository navigation
Releases: BaiFeng940/inkscroll
Release list
inkScroll 0.3.6
inkScroll 0.3.6
针对「网盘库里文件多了之后加载很慢」做了一轮实测优化:大库导入快约 3.8 倍。
瓶颈在哪(逐项量出来的)
| # | 问题 | 代价 |
|---|---|---|
| 1 | 扫描子目录串行(一个目录一次 PROPFIND) | 100 个文件夹光等就几十秒 |
| 2 | 每本书都新建连接(TCP + TLS 握手) | 100 本 = 100 次握手 |
| 3 | 每本嗅探 16 张图的尺寸 | 每本多十几倍往返 |
| 4 | 书架写入是 O(n²)(每本重写整个书架 JSON) | 越到后面越卡 |
| 5 | 启动时给整个书架生成封面 | 每本下载一整张原图 |
| 6 | 重复导入产生重复的书 | 书架越用越乱 |
改了什么
- 扫描并行 5 路,结果按原始下标放回,目录自然序不乱
- 整批复用同一个连接,最后统一关闭
- 嗅探样本 16 → 4:同一话里的图几乎总是同尺寸,中位数兜底足够
- 批量写入书架:一次读一次写;导入期间只攒元数据,整批结束才刷新 UI
- 封面按需生成:书架卡片可见时才请求;启动时只补本地库
- 按 (连接, 目录) 去重,重复添加会明确提示「都已在库里」
- HTTP 连接池固定
maxConnectionsPerHost=6,长连接复用
实测数据(A/B 基准,tool/bench_scan.dart)
12 本 × 20 页,用模拟服务器注入固定 RTT:
60ms RTT(较好的隧道)
| 阶段 | 优化前 | 优化后 | 加速 |
|---|---|---|---|
| 扫描 | 940 ms | 252 ms | 3.73x |
| 单本嗅探 | 302 ms | 77 ms | 3.92x |
| 整库 | 4.6 s | 1.2 s | 3.88x |
150ms RTT(较差的公网)
| 阶段 | 优化前 | 优化后 | 加速 |
|---|---|---|---|
| 扫描 | 2053 ms | 517 ms | 3.97x |
| 单本嗅探 | 645 ms | 170 ms | 3.79x |
| 整库 | 9.8 s | 2.6 s | 3.83x |
收益与 RTT 成正比 —— 符合「消除往返次数」的预期:网络越慢,提升越大。
顺带修掉的两个真 bug
upsertBooks会覆盖错书:最初「边遍历边 insert(0)」,头部插入让已记录的下标整体错位,按旧下标更新会写到别的书上 —— 书架出现重复项且书名错乱。这个问题是新加的单元测试抓出来的。- 测试服务器是串行的:
await for里直接 await 处理请求,用它跑基准会得出「并行没有用」的错误结论(实测只快 1.12x)。改为每请求独立 task 后才测出真实差距。
下载
| 架构 | 适用机型 |
|---|---|
inkscroll-0.3.6-arm64-v8a.apk |
绝大多数现代手机(推荐) |
inkscroll-0.3.6-armeabi-v7a.apk |
较老的 32 位设备 |
inkscroll-0.3.6-x86_64.apk |
模拟器 |
analyze 0 issue,测试 73 全通过;已在 Android 13 真机(V2073A)验证导入、封面、去重。
inkScroll 0.3.5
inkScroll 0.3.5
三件事:封面改用真实首图、文案去 NAS 化并泛化到各类 WebDAV 网盘、远程访问指引重写。
1. 封面 = 第一张图片
之前在线库的书没有封面,只显示一个「书名首字 + 配色」的占位块。
现在导入时会从第一张图片生成缩略图并落盘,列表直接读本地文件。
三个关键设计:
- 导入时生成,不在列表里实时拉 —— 在线库的书首页在网盘上,
等列表滚到才去拉会变成一屏 6 个并发请求,每次冷启动都要重来 - 批量导入期间推迟 —— 否则每存一本就插一次网络请求,导入会慢数倍;
整批结束后在后台顺序补(并发会把网盘打满甚至限流) - 内存安全 —— 不先解码原图再缩放,而是把目标宽度交给解码器,
单页即使是 800×6000 的长图,峰值内存也只有缩略图那么大
启动时也会自动给历史数据补封面。
2. 去掉「NAS」,泛化为通用 WebDAV
| 之前 | 现在 |
|---|---|
| 在线库(NAS / WebDAV) | 在线库(WebDAV 网盘) |
| 连接 NAS | 连接 WebDAV |
| 添加 NAS 连接 | 添加 WebDAV 网盘 |
| 还没有添加任何 NAS | 还没有添加任何网盘 |
| 从 NAS 导入 | 从网盘导入 |
| 本地与 NAS 漫画阅读器 | 本地与网盘漫画阅读器 |
支持范围写明为:私有云(飞牛 / 群晖 / 威联通 / TrueNAS)
与商用网盘(坚果云 / Nextcloud / Alist 等)。
⚠️ 需要说清楚:百度网盘、阿里云盘这类网盘本身不提供 WebDAV。
要用它们,得先用 Alist 之类的网关把网盘挂载并转成 WebDAV,
再把 Alist 的 WebDAV 地址填进来。App 这边不限服务商,
只要是标准 WebDAV 就能连。
3. 远程访问指引重写
原来只讲「公网 IP + 路由器端口映射」。但光猫 + 路由器双层 NAT
或运营商 CGNAT 的情况下这条路根本走不通(错误提示还会误导成"地址写错")。
现在改成先判断有没有公网 IP,再分两条路:
- A. 有公网 IP —— 路由器端口映射 + DDNS
- B. 没有 —— chmlfrp / frp / Cloudflare Tunnel 隧道穿透,
并给出三个关键配置:端口类型选 TCP、本地端口填 5006、加密压缩都关
验证(Android 13 真机,V2073A)
| 项目 | 结果 |
|---|---|
| 安装启动、权限 | ✅ |
| 界面文案无 NAS 字样 | ✅ |
| 连接 NAS(WebDAV) | ✅ 列出 /111/ |
| 添加为库 | ✅ 在线库出现「111 / 1 页」 |
| 封面 = 首图 | ✅ 卡片显示从 NAS 取的真实图片 |
analyze 0 issue,测试 72 全通过。
下载
| 架构 | 适用机型 |
|---|---|
inkscroll-0.3.5-arm64-v8a.apk |
绝大多数现代手机(推荐) |
inkscroll-0.3.5-armeabi-v7a.apk |
较老的 32 位设备 |
inkscroll-0.3.5-x86_64.apk |
模拟器 |
Full Changelog: v0.3.4...v0.3.5
inkScroll 0.3.4
inkScroll 0.3.4
让 NAS 功能真正可用的最后一个 bug 修掉了。
症状
0.3.3 修好联网权限后,连接能到达 NAS,但列目录依然失败。
根因:飞牛 fnOS 拒绝带请求体的 PROPFIND
用 curl 逐项对比才发现:
| 请求 | 结果 |
|---|---|
PROPFIND 不带请求体 |
207 + 完整文件列表 ✅ |
PROPFIND 带 XML 请求体 |
400 Bad Request ❌ |
而"标准写法"正是发 <propfind><prop>...</prop></propfind> ——
也就是说任何标准 WebDAV 客户端在飞牛上都列不出目录。
400 这个错误码极具误导性:看起来像"请求格式错",实际极易被误判成
地址写错、权限不足、端口不对。这个坑绕了很久。
修复
RFC 4918 §9.1 规定:PROPFIND 不带请求体等价于 allprop,是合法的。
所以改成默认发空体,仅当服务器明确回 400/415 时才回退到带体写法,
兼容那些反过来要求必须带体的严格实现。
端到端验证(真机 + 公网隧道,非局域网)
用 chmlfrp 隧道 https://8.162.5.68:64815 连家里的 NAS:
| 步骤 | 结果 |
|---|---|
| 列根目录 | 207,正确列出 /111/ |
| 图片尺寸嗅探(Range) | 2559×1439 |
| 下载整张 4.7MB 图片 | 271ms(约 17 MB/s) |
| App 内导入为在线库 | 成功,显示「111 / 1 页」 |
| 打开阅读 | 图片正常渲染 |
至此「不在家也能看 NAS 漫画、且不装额外软件」的完整链路跑通。
飞牛用户配置要点
- 别用管理员账号 —— fnOS 的 WebDAV 对管理员返回 401(认证阶段就拒绝),
请单独建一个普通账号 - 该账号需要有可见文件夹,否则根目录返回 404(不是权限错误,是没内容可映射)
- 远程访问推荐 chmlfrp / frp 之类的 TCP 隧道:
- 端口类型选 TCP(不是 HTTP/HTTPS),这样 TLS 端到端保留
- 本地端口填 5006(WebDAV HTTPS),不是 5666(网页端口)
- 数据加密/压缩都关掉(TLS 已经加密;JPEG 压不动)
- 双层 NAT / 运营商 CGNAT 都不用管,隧道直接绕过
下载
| 架构 | 适用机型 |
|---|---|
inkscroll-0.3.4-arm64-v8a.apk |
绝大多数现代手机(推荐) |
inkscroll-0.3.4-armeabi-v7a.apk |
较老的 32 位设备 |
inkscroll-0.3.4-x86_64.apk |
模拟器 |
若装过旧版本,可能因签名不同无法覆盖安装,请先卸载。
Full Changelog: v0.3.3...v0.3.4
inkScroll 0.3.3
inkScroll 0.3.3
修掉本项目最严重的 bug:release 包完全无法联网。
症状
从 0.2.0 起,NAS 相关功能在手机上一次都没成功过,一律提示"连接不上服务器"。
看起来像地址写错、网络不通、双层 NAT、端口映射未生效 —— 全都不对。
真正的报错是:
SocketException: Connection failed
(OS Error: Operation not permitted, errno = 1)
errno = 1 是 EPERM —— Android 直接禁止了 App 联网,与网络环境无关。
根因
Flutter 模板只在 src/debug/AndroidManifest.xml 里声明了 INTERNET 权限
(为了热重载),src/main/AndroidManifest.xml 里没有。于是:
| 包类型 | 联网 |
|---|---|
| debug | ✅ 正常 |
| release | ❌ 被系统拒绝 |
排查时我一直用 flutter build apk --debug 在模拟器上测试,
完美掩盖了这个问题;而用户装的都是 release 包。
修复
src/main/AndroidManifest.xml补上INTERNET与ACCESS_NETWORK_STATE- 新增
res/xml/network_security_config.xml:Android 9+ 默认禁止明文 HTTP,
而家用 NAS 的 WebDAV 常见是http://192.168.x.x:5005。显式放开,
否则填 http 地址也会被系统拦掉
验证(这次是 release 包)
在真机上用 0.3.3 release 包连接 chmlfrp 隧道 https://8.162.5.68:64815:
| 修复前 | 修复后 | |
|---|---|---|
| 报错 | Operation not permitted (errno=1) |
HTTP 401 |
| 含义 | Android 禁止联网 | 网络通畅,仅账号未通过 |
| 进程日志 | EPERM 异常 | 无异常 |
用假账号测试得到 401 —— 说明隧道、双层 NAT、HTTPS、自签名证书全链路都通。
教训
涉及联网的功能,必须在 release 包上验证。 debug 包自带 INTERNET 权限,
会让这类问题 100% 隐形。已写进代码注释。
下载
| 架构 | 适用机型 |
|---|---|
inkscroll-0.3.3-arm64-v8a.apk |
绝大多数现代手机(推荐) |
inkscroll-0.3.3-armeabi-v7a.apk |
较老的 32 位设备 |
inkscroll-0.3.3-x86_64.apk |
模拟器 |
⚠️ 如果你装过旧版本,可能因签名不同而无法覆盖安装,请先卸载再装。
Full Changelog: v0.3.2...v0.3.3
inkScroll 0.3.2
inkScroll 0.3.2
这一版针对「不在家时也能看 NAS 漫画,且不装任何额外软件」。
好消息:你不需要装任何中转软件
只要家里的宽带分到了公网 IP,在路由器上做一次端口映射就能直接连 ——
不需要 Tailscale、ZeroTier 之类的东西。
App 里新增了完整指引:设置 → 在线库 → 「不在家时怎么连?(远程访问)」,
4 步讲清楚:
- 确认有没有公网 IP(
100.64.x.x开头就是运营商大内网,这条路不通) - 主路由器 → 端口转发:外部端口 →
192.168.x.x:5006 - 配一个 DDNS 域名(公网 IP 会变;飞牛 OS 自带 DDNS)
- 在 App 里填
https://你的域名:外部端口,和局域网地址并存两条连接
安全提醒(很重要)
把 NAS 暴露到公网有风险,请务必:
- 在 NAS 上建一个只读的 WebDAV 账号给阅读器
- 用非标准外部端口
- 保持 HTTPS
其它改动
- 协议探测超时 8s → 15s:外网下一次 TLS 握手 + 鉴权往返经常要好几秒,8s 会误判成"连不上"
- 清掉 UI 文案里残留的字面量
**
关于「用分享链接直接看」
没有做,而且这次确认了做不了:share.fnnas.net/s/<token> 是 FN Connect 的纯前端 SPA,
任何路径都只返回同一个 851 字节的页面,没有取文件的接口;真正的访问要走
/api/v1/fn/con 的隧道中继协议,逆向它等于重写整套 WebSocket 隧道,
随版本变化就会坏。端口映射才是正路。
下载
| 架构 | 适用机型 |
|---|---|
inkscroll-0.3.2-arm64-v8a.apk |
绝大多数现代手机(推荐) |
inkscroll-0.3.2-armeabi-v7a.apk |
较老的 32 位设备 |
inkscroll-0.3.2-x86_64.apk |
模拟器 |
Full Changelog: v0.3.1...v0.3.2
inkScroll 0.3.1
inkScroll 0.3.1
一个会把好事变坏事的 bug 修掉了:明明能连上的 NAS,却提示"连接不上"。
核心修复:自动识别 http / https
如果你填地址时没写 https://(比如只填 192.168.10.105:5006),
之前会自动补成 http://。而飞牛 OS 的 5006 是纯 HTTPS 端口,
HTTP 请求会被服务器直接断开 —— 于是报"连接不上服务器",
完全误导:地址、网络、账号可能全都没问题。
实测对比:
| 你填的 | 实际请求 | 结果 |
|---|---|---|
192.168.10.105:5006 |
http://... |
Connection closed → "连接不上" |
https://192.168.10.105:5006 |
https://... |
到达服务器 → 401 密码错 |
修复内容:
- 默认协议改成
https(NAS 普遍开 HTTPS) - 新增真的去探测:没写协议就先 https 后 http;写了协议但连不上也会自动换另一种
- 判定标准是"服务器有没有回应":拿到 401 / 207 / 404 都算协议对了,
不会把"密码错"误报成"连不上" - 连接失败时把技术细节显示出来(
HTTP 401/Connection closed),
不再只给一句"连接不上" - 地址栏提示改为「只填 IP:端口 也可以」
如果你之前被这个 bug 拦住了,升级后再试一次 —— 这次会给出准确的错误原因。
清理
- 删除
lib/ui/widgets/adaptive_media_box.dart—— 它早就被"只给宽度、高度交给解码器"的方案取代,但 README 里还写着"实现:新增它",属于误导性死代码 - 清掉
.idea/.iml等 IDE 垃圾 - 验证截图从 131 个精简到 6 张关键图
其它
- CI 里的 APK 文件名不再写死版本号,改为从
pubspec.yaml读取 - 测试 67 → 70
下载
| 架构 | 适用机型 |
|---|---|
inkscroll-0.3.1-arm64-v8a.apk |
绝大多数现代手机(推荐) |
inkscroll-0.3.1-armeabi-v7a.apk |
较老的 32 位设备 |
inkscroll-0.3.1-x86_64.apk |
模拟器 |
注意
- 想跑满 120Hz,仍需在系统设置里为 inkScroll 手动开启高刷新率
- release APK 仍使用 Flutter 模板的 debug 签名,仅供测试
- NAS 密码做的是混淆而非加密;建议单独建一个只读 WebDAV 账号
Full Changelog: v0.3.0...v0.3.1
inkScroll 0.3.0
inkScroll 0.3.0
界面按使用习惯重做了一遍,NAS 入口搬到该在的地方,并且支持一次性导入整个漫画库。
首页改版
- 删掉顶部标题栏。原来那条 AppBar 里是「inkScroll」文字加一个只会清空搜索框的假搜索按钮,纯占高度。
- 搜索框移到页面顶部,右侧新增设置按钮。
- 三个 Tab(全部 / 最近 / 收藏)换成 本地库 / 在线库 —— 按内容来源分比按时间分更符合实际使用。
- 「导入」按钮只在本地库显示。
NAS 入口搬到设置
「从 NAS 导入」从导入菜单里删掉了:它本质不是「导入一本书」,而是配置一个内容源,放菜单里语义就是错的。
现在在 设置 → 在线库(NAS / WebDAV)→ 添加 WebDAV 库。
一键导入整个库
选一个目录后,会扫描它下面的所有子文件夹,每个含图片的都建成一本书进在线库,不含图片的自动跳过。
实测(5 个子目录):第01话 6 页 / 第02话 6 页 / 第03话 4 页 / 封面 2 页 全部正确,空的文件夹被跳过。
修掉的三个 bug(都是实测才暴露)
- 未捕获的异步异常 ——
_openLibrary里没有 try/catch,网络异常变成 unhandled async error,界面毫无反应。 - href 编码归一化 —— 有些 WebDAV 服务器直接返回未编码的 href(
/dav/第01话 火影/),客户端拼出的 URL 含空格和中文,Uri.parse失败 → 子目录内容全读不到且没有任何提示。现在resolveHref先解码再编码,对已编码/未编码都幂等。 pubspec.yaml被写成带 BOM,已修正。
测试 65 → 67。
下载
| 架构 | 适用机型 |
|---|---|
inkscroll-0.3.0-arm64-v8a.apk |
绝大多数现代手机(推荐) |
inkscroll-0.3.0-armeabi-v7a.apk |
较老的 32 位设备 |
inkscroll-0.3.0-x86_64.apk |
模拟器 |
注意
- 想跑满 120Hz,仍需在系统设置里为 inkScroll 手动开启高刷新率
- release APK 仍使用 Flutter 模板的 debug 签名,仅供测试
- NAS 密码做的是混淆而非加密;建议单独建一个只读 WebDAV 账号
Full Changelog: v0.2.0...v0.3.0
inkScroll 0.2.0 — 从 NAS 在线看漫画
inkScroll 0.2.0
本次最大的变化:可以直接连 NAS 在线看漫画了。
新功能 · 从 NAS 导入(WebDAV)
支持 飞牛 OS / 群晖 / 威联通 / Alist / Nextcloud 等所有支持 WebDAV 的服务。
- 在 NAS 上开启 WebDAV(飞牛 OS:设置 → 文件服务 → WebDAV,默认端口
5005,路径/dav) - App 里 导入 → 从 NAS 导入(WebDAV) → 填
http://192.168.1.10:5005/dav+ 账号密码 - 列出目录 → 进入放图片的文件夹 → 点导入
核心设计:只列目录,不整本下载
| 时机 | 行为 |
|---|---|
| 导入时 | 只发一次 PROPFIND,不下载任何图片,第一页立即可见 |
| 翻页时 | 拉当前页 + 后台预取前后各 2 页 |
| 缓存 | 拉下来的页落盘,第二次看就是本地文件,断网也能看已缓存的 |
| 嗅探 | 用 Range 只取前 64KB 算图片尺寸,不必为宽高拉整张图 |
修掉的两个真 bug(都是实测才发现)
- 点开 NAS 导入直接红屏 —
_loadRaw()空存储时返回const [],而all()会对它sort,抛Cannot modify an unmodifiable list - 导入时报 PathNotFoundException — WebDAV 导入不往书目录复制文件,写
document.json.tmp时目录还不存在
其它改进
- 错误提示从 6 秒延长到 12 秒并可手动关闭(之前一闪而过根本看不到原因)
- 只新增
http+xml两个纯 Dart 依赖,不引任何原生插件 - 新增
tool/webdav_probe.dart:连不上 NAS 时可在电脑上逐项诊断 - 新增
tool/webdav_test_server.dart:没有 NAS 也能跑通整条链路 - 测试从 39 个增加到 65 个
下载
| 架构 | 适用机型 |
|---|---|
inkscroll-0.2.0-arm64-v8a.apk |
绝大多数现代手机(推荐) |
inkscroll-0.2.0-armeabi-v7a.apk |
较老的 32 位设备 |
inkscroll-0.2.0-x86_64.apk |
模拟器 |
注意
- 想跑满 120Hz,仍需在系统设置里为 inkScroll 手动开启高刷新率(Android 与国产 ROM 的限制)
- release APK 仍使用 Flutter 模板的 debug 签名,仅供测试,不可用于正式分发
- NAS 密码做的是 XOR + Base64 混淆而非加密;建议在 NAS 上单独建一个只读 WebDAV 账号
inkScroll 0.1.0
inkScroll v0.1.0
本地漫画 / 文档阅读器。下载对应架构的 APK 直接安装:
| 架构 | 适用 |
|---|---|
arm64-v8a |
绝大多数现代手机(推荐) |
armeabi-v7a |
较老的 32 位设备 |
x86_64 |
模拟器 |
安装后在系统设置里为 inkScroll 手动开启高刷新率,即可跑满 120Hz
(Android 与国产 ROM 的限制,应用无法绕过)。
详细改动见仓库根目录的 README.md。
Full Changelog: https://github.com/BaiFeng940/inkscroll/commits/v0.1.0