Skip to content

Releases

v1.10.4:局域网发现 + 邀请加入

Choose a tag to compare

@Xinjigalaxy Xinjigalaxy released this 13 Oct 11:40

本次更新

局域网发现:不必再手填主机地址。从机在同步页点「搜索局域网」,主机自己应答,列出名字、地址与是否已配对,点一下填入地址。

邀请加入:主机也能反过来邀请设备。设备收到邀请弹一次确认框;加入 = 写地址(密钥指纹不一致时要求填共享密钥)+ 开定时自动同步 + 立刻同步一轮;忽略则记进偏好,同一台不再打扰。

变更

  • App:同步页新增「搜索局域网」;主机模式开着时后台应答别人的探测(报出端口与密钥指纹)
  • App:应用根上挂一次性邀请确认框(邀请可能在任何页面到达)
  • 中心(server/):新增 --discover、--invite IP|DEVICE_ID、--invite-all、--no-discovery;配置项 discovery(默认开)、invite_on_discovery(默认关)
  • 协议:UDP 8766,三种报文 probe / hello / invite;只收私网来源、报文上限 1 KB、同来源 2 秒限速、kn 不为 1 丢弃
  • 安全:发现报文不含共享密钥,只带 key_hint = sha256(key + "\n" + device_id) 前 8 位(判断「是不是我配对过的那台」);真正的把关仍是 /sync 上的共享密钥 + 用户那一次点击
  • 修复:同步地址解析接受从 Termux ip addr 粘来的 192.168.1.29/24 这类带掩码写法(先前整串当主机名,必失败)
  • 修复:探测同时发有限广播 + 组播组 + 每张网卡的定向广播(只发 255.255.255.255 时多网卡主机上出不去、零应答)
  • 修复:发现监听 socket 曾因 apply 作用域里 port 取到自身 getPort()(未绑定为 -1)而从未绑上

验证

项目 结果
中心测试 75/75(PC 与 Termux 真机同数)
App 单元测试 76/76
仪器化测试 Android 13 64/64
真机发现 PC 问一圈答出手机上的中心(角色 hub、端口 8765、指纹);开着主机模式的安卓机回角色 app 并报出自己地址
邀请弹窗 真机弹出一次窗(标题带主机名、正文给主机地址、指纹不一致则要求填共享密钥、按钮 忽略/加入);点「忽略」后落盘,再发一次不再弹

升级

  • App:覆盖安装即可,数据保留
  • 中心:替换 knownote_hub.py 后重启(bash start-hub.sh restart);devices 与密钥不用改
  • 端口:发现走 UDP 8766,同步仍是 TCP 8765;不需要新增任何权限(广播无需运行时权限)
  • 限制:广播与组播不跨网段;开了客户端隔离(AP isolation)的网络里发现会失败,手填地址仍然可用

v1.10.3 · 修「自动同步从不触发」

Choose a tag to compare

@Xinjigalaxy Xinjigalaxy released this 07 Oct 06:42

1.10.3 · 修「自动同步从不触发」

修复

  • 症状:手机上开着「定时自动同步」,界面上的时间永远停在打开开关那一刻,之后一次也不跑
  • 根因一(设备侧):周期任务要求 UNMETERED(不计费网络),而手机热点、被系统标成计费的 Wi-Fi 全都不满足 ——
    任务长期停在「等约束满足」。两台设备的 WorkSpec 都是 period_count = 0,
    JobScheduler 报 Unsatisfied constraints: CONNECTIVITY,界面上却什么都看不出来
  • 根因二(失败退避):失败时返回 retry(),指数退避会把下一次执行推到几小时以后
  • 根因三(中心侧):配置里的设备地址是样例值 192.168.1.7,不在本机网段,
    于是「中心主动轮询」520 次全部超时,日志里全是同一条
  • 修复:设备侧去掉网络类型约束;失败一律不退回退(周期任务到下个周期自己再来);
    卡片显示后台任务状态与超期提示;中心在配置地址连不上时改用该设备最近接入过的地址

变更

  • 设备侧不再限制网络类型:局域网直连本就不产生流量费用,该拦的是「对端不在同一个局域网」,
    由 LanGuard / OffLan 负责(会说明原因)
  • 同步卡片可诊断:新增「后台任务:已排程(每 N 分钟)/ 正在执行 / 未排程」一行;
    距上次执行超过两个间隔时提示可能被省电或后台限制压住
  • 跨网段失败说人话:对端与本机不在同一网段时,失败原因写成两边各自的网段,
    而不是系统的 failed to connect to /192.168.1.7 ... after 4000ms。
    判定只在能确定时下结论(域名、枚举不到本机网卡时不猜)
  • 中心地址自学习:配置地址连不上 → 按「该设备最近接入过」的地址依次试(最多 3 个):
    上次成功同步记下的自称名优先,其次配置名一致,再次唯一候选,都对不上就按最近接入顺序试
    (配置里写「Y700」、设备自报「LENOVO TB322FC」这类名字对不上的情况就靠这一档;候选都是已配对过的自家设备)
  • 配置地址不在本机网段时直接跳过它(不白等一次连接超时),启动横幅与 --status 点出来;
    每轮试了哪些地址、最后连上谁,日志都写明;全部失败时错误里列出试过的每个地址
  • 中心日志节流:同一台设备同一原因连续失败,只在第 1~3 次与之后每 12 次(约 1 小时)记一条,
    原因变了立刻记(v1.10.2 在 20 小时里攒了 500 多条一模一样的超时)

安装

文件 说明
KnowNote-1.10.3-debug.apk Android App。debug 签名,versionCode 17 / versionName 1.10.3,minSdk 26 / targetSdk 36(Android 8.0+)。覆盖安装不丢数据。四语言:简中 / 繁中 / English / 日本語
knownote-hub-1.10.3-termux.zip 同步中心,纯 Python 3 标准库:零依赖、零编译,Termux 下 pkg install python 即可运行,不需要 root;也可跑在 PC 上(start-hub.bat)
unzip knownote-hub-1.10.3-termux.zip && cd knownote-hub-1.10.3
python3 knownote_hub.py --init-config hub.conf.json   # 生成配置;共享密钥留空会拒绝启动
./start-hub.sh                                        # 前台;后台用 ./start-hub.sh bg

协议未变(仍是 3),App 与中心可以分开升级:先升级哪一端都不会互相回 426。

App 侧:「更多 → 局域网同步」填中心地址与同一串共享密钥,即可作为从机同步;也支持 App 当主机、中心反向拉取。
默认只在局域网内工作(两端都拦):非私有来源一律 403,手机在移动数据上会被 App 自身拦住。

测试

开发期在本机与真机上跑出的结果;测试代码不在仓库与压缩包内。

项 结果
定位证据 两台设备 WorkSpec period_count = 0、JobScheduler Unsatisfied constraints: CONNECTIVITY、当前网络 Metered hint: true;中心 520/520 次轮询超时、last_ok_at = 0
App 单测 65/65(新增 LanSubnetTest 3 例:按真实前缀长度比网段、判不了时不误报、说明里带上两侧网段)
App 仪器化(Android 13 模拟器) 59/59(4 例新增 / 改写:无网络类型约束、任务状态可查、跨网段按 skipped: 记并写明原因、失败不再要求退避)
同步中心(PC) 60/60(新增 10 例:网段判定、本机网段解析、候选按最近接入排序、上限 3 个、不抢别的设备占用的地址、逐个往下试到连上、全失败时列出试过的每个地址、无历史时不猜、自称名对齐、失败日志节流)
同步中心(真 Termux:Android 11 设备,Python 3.14.6 / arm64) 60/60(22.7 秒)
真机实测(计费热点) 两台测试机当时都是 Metered hint: true —— 原缺陷的触发条件。装上 1.10.3 并启动一次后,系统里的任务约束只剩 TIMING_DELAY(原为 TIMING_DELAY CONNECTIVITY 且要求 NOT_METERED);触发后两台都 sync_auto_last_ok = true(一台推 36 条、一台拉 1 条)
地址自学习 单测覆盖候选排序 / 上限 / 不抢别的设备的地址 / 逐个往下试到连上 / 全失败时列出每个试过的地址;真中心升到 1.10.3 后配置仍留着样例地址(跳过它,按最近接入顺序试三台曾接入的设备,日志逐条写明)

隐私

仓库只放程序源码(开发脚本、单元 / 仪器化测试、演示数据脚本均不入库,历史中一并移除);
发布附件与说明均已脱敏:示例地址、示例域名、通用设备描述;仓库内无任何密钥,样例配置的 key 为空串。

许可

MIT。

v1.10.2 · 同步改为双向库存比对(协议 3)

Choose a tag to compare

@Xinjigalaxy Xinjigalaxy released this 05 Oct 08:59

1.10.2 · 同步改为双向库存比对(协议 3)

修复

  • 症状:老设备连新对端时只同步到零星几条笔记
  • 根因:增量由水位线 last_sync_at 推断,而水位线的语义是「与上一个对端同步到哪」——换对端后算出的增量为空

变更

  • 每轮请求与响应都带全量库存:guid + updated_at + 墓碑位 + 内容指纹(FNV-1a 32 位),不含正文
  • 两端跑同一个纯函数算差集(Kotlin SyncDiff 与 Python 中心逐位一致):对方没有 / 我更新 / 打平但指纹不同 → 我发;我没有 / 对方更新 → 我要;两边相同(含双方持有的同一枚墓碑)→ 不动
  • 正文只在被点名时传输:对方在响应里用 want_guids 列出所需 guid,下一轮按 guid 发送正文
  • 轮数:无差别 1 轮,比差集 + 发正文 2 轮,有上限兜底
  • last_sync_at 降级为「省一轮往返的快路径 + 界面显示」,正确性不再依赖它
  • 同批修复:协议 3 的 inventory / want_guids 只加进请求解析、漏在响应的 from_json(症状是每轮重传整库,日志显示 5 轮);补「线结构体往返」与「轮数断言」两条回归

升级顺序(协议 2 → 3 为断点)

  1. 先装新 APK(覆盖安装,数据不动)
  2. 再重启同步中心(旧进程仍在说协议 2)

v1.10.1 的 App 与协议 3 的中心互回 426 Upgrade Required。

附件

文件 说明
KnowNote-1.10.2-debug.apk Android App。debug 签名,versionCode 16 / versionName 1.10.2,minSdk 26 / targetSdk 36(Android 8.0+)。覆盖安装不丢数据(包名、签名、数据库 schema 均未变)。四语言:简中 / 繁中 / English / 日本語
knownote-hub-1.10.2-termux.zip 同步中心,纯 Python 3 标准库:零依赖、零编译,Termux 下 pkg install python 即可运行,不需要 root;也可跑在 PC 上(start-hub.bat)

运行(Termux)

unzip knownote-hub-1.10.2-termux.zip && cd knownote-hub-1.10.2
python3 knownote_hub.py --init-config hub.conf.json   # 生成配置;共享密钥留空会拒绝启动
./start-hub.sh                                        # 前台;后台用 ./start-hub.sh bg

App 侧:「更多 → 局域网同步」填中心地址与同一串共享密钥,即可作为从机同步;也支持 App 当主机、中心反向拉取。
默认只在局域网内工作(两端都拦):非私有来源一律 403,手机在移动数据上会被 App 自身拦住不发请求。

测试

开发期在本机与真机上跑出的结果;测试代码不在仓库与压缩包内。

项 结果
App 单测 62/62
App 仪器化(Android 13 / 15 模拟器各一遍) 57/57
同步中心(PC) 50/50
同步中心(真 Termux:Android 11 设备,Python 3.14.6 / arm64) 50/50
真机跨实现:Termux 中心 ⇄ PC 中心(协议 3) 空库第一轮拉 3 条 → 稳态 0/0(1 轮)→ 新建一条后推 1 条(2 轮)
真机跨实现:PC 中心 ⇄ 模拟器上的真 App(协议 3) App 点名要 2 条并落库(直接读 App 数据库核实),稳态 0/0
两端逐位一致处 指纹固定值 + 一整串黄金 JSON,Kotlin / Python 两侧各钉一半

隐私

仓库只放程序源码(开发脚本、单元/仪器化测试、演示数据脚本均不入库,历史中一并移除);发布附件与说明均已脱敏:示例地址(192.168.1.x / 203.0.113.7)、示例域名(example.com)、通用设备描述;历史旧提交一并重写。仓库内无任何密钥,样例配置的 key 为空串。

许可

MIT。

v1.9.0 · 图片参与局域网同步

Choose a tag to compare

@Xinjigalaxy Xinjigalaxy released this 13 Sep 07:16

1.9.0 · 图片参与局域网同步

协议升到 2(两端须同版本)

旧版主机遇新版从机回 426 Protocol version mismatch。云端无数据,两台设备版本不一致只导致同步不通,不会损坏数据;两端各装本版本即可。

两个设计取舍

  • 图片不绑在笔记变更日志上:否则升级后首次同步会因「笔记一条未变」而永远传不过去。判据改为文件级存在性(本机有、对端集合中无即传),与变更日志无关,可反复重试
  • 谁缺什么由对端当次广播(images_i_have),服务端不保存任何对端状态;本地只把「对端已有图片」当缓存(key 带 host:port,多台对端各存各的),以缓存换掉一次数据库迁移

传输

  • 图片与笔记在同一次请求内走(base64 承载),不新增端点、不额外鉴权
  • 单批上限 6 张 / 4MB;超量由协调器自动多轮传完(最多 4 轮,其余留给下次同步)
  • 同一张只传一次:每台设备记住对端已有集合,重复同步不重传
  • 收图时已存在的跳过(不覆盖、不重复计数);落盘用 .part 临时文件 + rename,不留半截文件
  • 同步日志与结果包含图片收发张数与轮数

验证

  • 仪器化测试 40(37 → 40),其中三条为真回环(真 ServerSocket + 真 HttpURLConnection):① 从机 → 主机传图,内容逐字节一致且第二次同步不重传;② 主机 → 从机反方向;③ 8 张图自动分多轮传完
  • 回环测试中两台「设备」各持一份互不共享的内存图片仓库(共享同一目录会让测试无条件通过)
  • 单元测试 55,全部通过

已知边界

导出(.json / .csv / .db)只带图片引用、不带图片文件;后续可将导出改为 zip(正文 + 图片一起打包)。

安装

下载 KnowNote-1.9.0-debug.apk 覆盖安装(不要先卸载,卸载会清掉本地笔记)。

v1.8.0 · 正文插入图片(图片独占整行)

Choose a tag to compare

@Xinjigalaxy Xinjigalaxy released this 13 Sep 07:15

1.8.0 · 正文插入图片(图片独占整行)

存储

  • 相册所选的图拷进应用私有目录,正文只存文件名:![说明](img:文件名)
    • 不存相册 URI:读权限会过期、原图被删即断链、换设备失效
    • 不存绝对路径:私有目录路径随设备与重装变化
  • 插入时等比缩到 1600px 内 + 重编码 JPEG(相册原图可达十几兆,直接存既占空间又易 OOM);照片方向由 ImageDecoder 自动纠正(API 28+)
  • 数据库结构未变(schema v3),导出 / 同步 / 回收站均不受影响

渲染

  • 正文按「文字段 / 图片块」切分渲染:图片独占整行、按宽度铺满;行内穿插的图片也单独成行
    • 用块切分而非 AnnotatedString 行内图片:后者随文字换行,无法保证独占一行
  • 渲染时按需降采样 + LruCache(上限为八分之一堆内存),解码在 IO 线程,避免大图卡住页面
  • 图片文件缺失时(清过数据、或笔记来自其他设备)显示「图片已丢失:文件名」,不留空白

摘要与检索

  • 卡片摘要显示 [说明] 占位(无说明时 [图片]),不暴露文件名;全文索引保留说明文字(可搜到)

编辑器

  • 工具栏新增「插入图片」,走系统相册选择器 PickVisualMedia(不需要读相册权限)

验证

  • 单元测试 55(+13:块切分 7 条 + 摘要 / 索引的图片处理等);仪器化测试 37,全部通过
  • 真机(Android 13):图片横向铺满整行、上下文字各自成段;行内插入的图片同样独占一行;列表卡片显示 [示例图];编辑器工具栏「插入图片」在位

安装

下载 KnowNote-1.8.0-debug.apk 覆盖安装(不要先卸载,卸载会清掉本地笔记)。数据库 schema 未变,仍为 v3。

v1.7.0 · 阅读页可调 + 编辑器行内格式

Choose a tag to compare

@Xinjigalaxy Xinjigalaxy released this 12 Sep 15:39

1.7.0 · 阅读页可调 + 编辑器行内格式

阅读页显示(只作用于阅读页,不动设置页的全局字号)

  • 顶栏新增「阅读显示」菜单(位于「编辑」左侧):字号滑块 0.8×–1.8×,旁边实时显示换算后的 sp 值
  • 存的是倍率而非绝对值:全局字号改变后,阅读页仍按同一比例,两处设置不冲突
  • MD 模式 / 文本模式切换:文本模式原样显示(含 <color> / <size> 标记本身),便于手改标记
  • 该设置由底部常驻条收进菜单,长笔记不再被压住正文

编辑器行内格式

  • 选中文字后可加:粗体 / 斜体 / 三档字号 / 六色(红黄绿青蓝紫)
  • 粗体、斜体为开关式(再点一次取消);颜色与字号为换值式,识别两种已有标记形态(连标记一起选中的整段、只选中标记内文字且标记紧贴选区外侧),直接替换而不嵌套 —— 否则红改蓝会叠成 <color=blue><color=red>x</color></color>
  • 标记写进纯文本正文(<color=red>…</color>、<size=1.25>…</size>),数据库结构未变(schema v3),导出 / 同步 / 回收站均不受影响
  • 字号用相对倍率(em),可与阅读页滑块叠加;每种颜色带明暗两套取值

渲染器

  • 支持 <color> / <size>,并递归解析行内内容:**<color=blue>x</color>**(粗体在外、颜色在内)正确输出粗体 + 蓝色
  • 未闭合或未支持的标记原样显示,不猜颜色、不吞文字

列表与卡片摘要只显示可读文字

  • 摘要去掉 <color=…>、<size=…> 与 Markdown 记号(**粗体**、*斜体*、~~删除~~、`代码`),只留文字
  • 两处防误伤边界:2*3*4 不被当作斜体、my_file_name.txt 不被下划线斜体削掉
  • 全文索引同样不收录自定义标记,避免搜 color 有结果却没有可高亮的位置
  • 正文本身未改动;查看原文切「文本模式」

验证

  • 单元测试 46(+30:选区格式化 13、标记解析 9、摘要去记号 8 等);仪器化测试 37,全部通过
  • 真机(Android 13):滑块 14 sp → 25 sp 并落盘;长按选词后「加粗 + 蓝色 + 特大」三步叠加,落库为 **<color=blue><size=1.5>标记</size></color>**,阅读页三效叠加生效;列表与瀑布流摘要残留记号 0 处

安装

下载 KnowNote-1.7.0-debug.apk 覆盖安装(不要先卸载,卸载会清掉本地笔记)。数据库 schema 未变,仍为 v3。

v1.6.0 · 可调阅读显示 + 检索范围可选

Choose a tag to compare

@Xinjigalaxy Xinjigalaxy released this 12 Sep 15:00

1.6.0 · 可调阅读显示 + 检索范围可选

阅读显示(设置页新增「阅读」区)

  • 字号五档:小 / 标准 / 大 / 特大 / 超大。整棵 Typography 等比缩放(只乘 fontSize 与 lineHeight),标题与正文的相对层级保持不变
  • 文字颜色七种:跟随主题 / 深墨 / 纯黑 / 暖褐 / 墨绿 / 深蓝 / 酒红;每种带明暗两套取值,深色模式下不会深字压深底
  • 实现要点:只覆盖 LocalContentColor 无效 —— 页面里 60 多处 Text 显式写了颜色,会盖掉兜底。改为覆盖主题色角色(onSurface / onBackground / onSurfaceVariant / tertiary)才生效;primary(高亮、标签)与 outline(时间戳)刻意不动

检索范围可选

  • 可单独勾选 标题 / 正文 / 标签,默认全选;全部取消时自动回到全选(否则一条都搜不到)
  • 范围筛选放在结果侧:FTS 的列限定(title:xxx)需要 SQLITE_ENABLE_FTS3_PARENTHESIS,Android 的 FTS4 未编译该选项,结果侧过滤让 FTS 与兜底两条路径语义一致;范围收窄时候选集上限放宽 4 倍
  • 选择写入 UiPrefs,下次打开保持

检索忽略大小写

  • 索引侧与查询侧统一 lowercase(Locale.ROOT),避免土耳其语环境把 I 折成 ı 导致两侧不一致
  • 无全文引擎时的兜底由 SQL LIKE 改为 Kotlin 侧比较:SQLite 的 LIKE / LOWER() 只折叠 ASCII(É 与 é 不算同词)
  • 搜索历史按忽略大小写合并同词,不再出现 Android / android 两条

验证

  • 单元测试 16(+8:范围与大小写判定的纯逻辑);仪器化测试 37(+2:三字段大小写无关命中、搜索历史大小写合并),全部通过
  • 真机(Android 13):gradle 全范围 1 条 → 只留标签 0 条 → 恢复全范围 1 条;GRADLE / GrAdLe / hashmap / HASHMAP 均命中;暖褐文字实测 RGB(94,74,59),与定义的 #5B4636 相符

安装

下载 KnowNote-1.6.0-debug.apk 覆盖安装(不要先卸载,卸载会清掉本地笔记)。数据库 schema 未变,仍为 v3。

v1.5.0 · 四语言界面 + 新图标

Choose a tag to compare

@Xinjigalaxy Xinjigalaxy released this 11 Sep 07:50

1.5.0 · 四语言界面 + 新图标

首个不再带 -demo 后缀、可直接日常使用的版本。

新增

  • 四语言界面:跟随系统 / 简体中文 / 繁體中文 / English / 日本語;Android 13+ 同步到系统「应用语言」设置
  • 应用命名:KnowNote · 碎片笔记(桌面显示「碎片笔记」)
  • 自适应图标 + 单色层:Android 13+ 开启「主题图标」后跟随壁纸取色

修复

  • 译文里的 Kotlin 模板被原样写进字符串资源,切到外语后界面出现 $days 之类的未展开字面量

已有能力

笔记 / 标签 / 分组 · 中文子串全文检索(FTS5 → FTS4 → LIKE 三级降级) · Markdown 预览 ·
回收站与定时清理 · 局域网一主多从同步(设备间直传,不经过任何服务器) · 导出 JSON / CSV / DB

安装

  • 附件为 debug 签名构建,供自测直装,非商店发布版
  • 应用 ID 为 com.xinjigalaxy.knownotes.debug,可与正式包共存

测试

  • 单元测试 8/8
  • 仪器化测试 35/35:Android 13(SQLite 3.32.2)与 Android 15(SQLite 3.44.3)
  • 真机验证:迁移、检索降级自愈、二级页面、同步回环、主题与语言切换(明细见 README 验证记录)

English version: README.en.md