浏览器扩展 MV3 的 Service Worker 生命周期与保活
MV3 把常驻的 background page 换成了会被回收的 service worker,很多在 MV2 下能跑的逻辑到了 MV3 就"时好时坏"。这篇记录我踩过的几个坑和最后的处理方式。
Manifest V3 最容易被低估的变化,是后台脚本从常驻的 background page 变成了 event page 式的 service worker。它会被浏览器在空闲一段时间后回收,下一次事件到来时重新启动。这意味着任何存在内存里的状态、任何 setInterval 排下的定时任务,都会在某次回收之后悄无声息地消失。
几个典型的错误写法
- 用 setInterval 做轮询:worker 被回收后定时器不再恢复,表现为"用一会儿就不工作了"。
- 把登录态、计数器这类状态放在 worker 的全局变量里,重启后一律归零。
- 在 worker 里维持一条到服务端的长连接,期望它一直活着。
正确的处理方式
先把"常驻"这个需求本身去掉。绝大多数场景真正需要的是"事件到达时能拿到最新状态",而不是"进程一直活着"。把状态落到 chrome.storage,每次事件触发时先读状态、再决定做什么,worker 是不是被回收就不重要了。
确实需要周期性执行的,用 chrome.alarms。它在 worker 被回收后依然会被浏览器唤醒,是目前 MV3 下唯一被官方支持的定时手段。代价是周期不能太短,实际可用的最小间隔由浏览器控制,做秒级轮询是不现实的。
// 周期性任务:交给 alarms,不要用 setInterval
chrome.alarms.create('sync', { periodInMinutes: 5 });
chrome.alarms.onAlarm.addListener(async (alarm) => {
if (alarm.name !== 'sync') return;
const state = await chrome.storage.local.get('cursor');
await syncFrom(state.cursor);
});另一个容易忽略的点是消息通道。popup 或 content script 发消息时,worker 可能正处于启动过程中,直接用 sendMessage 有一定概率拿不到响应。稳妥的做法是在调用侧做一次重试,并把超时当作"未就绪"而不是"失败"。
小结
把 MV3 的 service worker 当成"随时会重启的进程"来写:状态外置、任务用 alarms、消息允许重试。按这个前提设计之后,稳定性问题和 MV2 时代基本没有区别。