93cf3e359af45665d837313173fab50fd199fdbf
现象:App 停在主会话时,次级会话落了新消息 ⇒ 抽屉红点不出现,必须打开一次抽屉 (那次静默 refresh)才补上。 根因:message_persisted 只在 sessionId == activeSessionId 时才推进 / 更新,非激活 会话那一帧直接 return;而会话列表更新的触发点此前只有抽屉打开 / 新建 / 删除 / init 四处,于是「别的会话攒了新消息」没有任何人会去发现。 为什么是客户端自愈而不是等后端:这一帧不带 unreadCount(契约面缺一个增量),而本端 没有逐条时间线 —— 自己累加出来的数会与编辑 / 删除 / 别端操作漂移,那是造数不是修复。 帧壳本身已经说清「某个会话有新消息落库」这件事,所以本端只是据此去问一次服务端, 走的是与抽屉打开时那次完全相同的路径(refresh(silent = true),含已读游标遮罩), 只是不需要用户先开一次抽屉。后端把增量补上之后这条路可以退成不必走,但在此之前它 是唯一能把红点点亮的路。 节流窗口 3 秒,理由: - 够快:3 秒落在「看起来就是刚发生的」这个感知里;5 秒往上就开始像卡了一下才补上; - 够粗:一次静默重拉是两个请求(refresh 里还跟着一次角色目录),而播出方是按每条 消息推帧的 —— 3 秒能把人手连打 5 条(约 2~4 秒)或一轮回复的连续落库收进同一个 窗口,收到 1 秒就会漏成两三次请求; - 不另立尺子:3 秒正是本项目已有的「等够久才值得为一次等待开口」那个量级 (ChatScreen 的 SHARE_WAIT_NOTICE_MILLIS)。 trailing 怎么保证:窗口内的第二条起不拉,但排一次「窗口到点补拉」(等待时长 = 窗口 - 已过时间),到点直接拉一次并把窗口重开;同一时刻最多一个补拉任务(已排上时 后续成因只更新账本、不重排),所以最后一条之后必定还会再拉一次,红点不会停在旧值。 另外「正好等于窗口」算窗口已过(>= 而不是 >),补拉到点那一刻不会被自己刚开的窗口 挡回去。 哪几种情况不拉:当前打开的那个会话落新消息(那条路本来就有「看到即已读」,多拉一次 列表就是白花两个请求);同一窗口内的后续消息;窗口内且补拉已经排上。另外切会话那条 路刻意不接进来 —— select 紧接着就发推进已读游标的请求,此时重拉会与还没落地的游标 抢跑、把刚清掉的角标闪回来(抽屉打开时那一次紧跟在游标之后,本来就会补上这一拉)。 判据抽成纯函数(新增 InactiveSessionPull.kt):persistedRoute 决定这一帧走哪条路, silentRefreshStep 决定这一刻该不该拉、拉完账本怎么走;时钟由调用方喂,不用 sleep 也就能穷举边界。 顺带把身份变化(重登成另一个账号 / 换了服务器地址)也接进这条路径,用 ContextChanged 绕过节流窗口 —— 那份列表换了主人,等满一个为「别的会话又攒了几条」而设的窗口没有 道理;登出时把账本与补拉任务一起作废(否则补拉会带着一个已经没有令牌的身份发请求)。 抽屉打开时那次 refresh、activeSessionId 的推进、已读游标的既有行为都没动。 验证:gradlew.bat assembleDebug testDebugUnitTest —— BUILD SUCCESSFUL; 1171 条单测 failures 0 / errors 0 / skipped 0,其中新增 InactiveSessionPullTest 13 条。
Releases
5
Alpha_v0.1.0_4
Latest