Chrome Apps 的 onRestarted 事件实战:restarted-demo 教你如何在浏览器重启后恢复应用状态
Chrome Apps 的 onRestarted 事件实战restarted-demo 教你如何在浏览器重启后恢复应用状态【免费下载链接】chrome-extensions-samplesChrome Extensions Samples项目地址: https://gitcode.com/gh_mirrors/ch/chrome-extensions-samples本指南以 chrome-extensions-samples 仓库中_archive/apps/samples/restarted-demo示例为蓝本深入讲解 Chrome 打包应用Packaged App中chrome.app.runtime.onRestarted事件的作用机制当应用因浏览器意外终止如崩溃、重启而被系统恢复时如何借助chrome.storage.local持久化数据把界面状态完整还原到崩溃前的样子。读完本文你将掌握「启动即恢复」状态机的基本设计思路、onLaunched与onRestarted的职责划分以及一份可直接运行的计数器恢复应用完整源码。onRestarted 事件为“意外终止后的恢复”而生Chrome 打包应用的生命周期由chrome.app.runtime提供的事件驱动。普通启动走onLaunched而本示例聚焦的是另一个容易被忽略的事件 ——onRestarted当应用在被意外终止之后被恢复时例如浏览器重启系统会向应用发送一个onRestarted事件应用应当借此把自身恢复到上次运行时的状态。「意外终止」与「正常关闭」的关键区别在于正常关闭时应用有机会自行清理窗口与数据而崩溃或浏览器整体重启时应用进程被直接销毁所有内存状态包括内存中的对象、未落盘的计数都会丢失。onRestarted正是系统提供给应用的后悔药入口让开发者有机会把持久化在磁盘上的数据重新加载回来。该事件属于chrome.app.runtimeAPImanifest 中对应app: {background: ...}声明的应用后台页与chrome.storage、chrome.app.window一起构成了本示例的三大 API 支柱。示例概览与最小 manifestrestarted-demo实现了一个简单却完整的语义闭环应用在启动时创建一个新计数器而在应用重启时恢复所有已存在的计数器。整个示例只包含四个文件_archive/apps/samples/restarted-demo/background.js全部业务逻辑计数器模型 事件监听_archive/apps/samples/restarted-demo/main.html应用窗口界面_archive/apps/samples/restarted-demo/manifest.json应用清单_archive/apps/samples/restarted-demo/sample_support_metadata.json示例元数据其 manifest.json 完整内容如下注意这是 Manifest V2 时代打包应用的经典结构{ name: Restarted Event Sample, version: 2, manifest_version: 2, app: { background: { scripts: [ background.js ] } }, permissions: [ storage ] }两个值得注意的点权限只需要一个storage用于访问chrome.storage.localchrome.app.runtime与chrome.app.window属于打包应用内建 API无需额外权限声明后台脚本通过app: {background: {scripts: [...]}}声明这正是onLaunched/onRestarted等应用级事件得以注册的载体。状态持久化的核心Counter 模型与防抖式写入恢复状态的前提是有状态可恢复因此示例把计数器抽象成了一个带持久化能力的Counter对象。构造函数如下function Counter(id, clicks, startedBy) { this.id id; this.clicks clicks; this.startedBy startedBy; this.saving false; this.listeners []; this.save(); Counter.all.push(this); }每个计数器有四个关键属性id存储键、clicks当前点击数、startedBy标记来源是launched还是restarted、saving写入中的防重入标志同时维护一个listeners订阅者列表用于界面刷新。防抖与合并写入的save()save()是持久化的核心实现了一个轻量的写入中防重入 快照对比重存机制Counter.prototype.save function() { if (this.saving) return; this.saving true; var self this; (function(clicks) { var counters {}; counters[self.id] {clicks: clicks}; var data {counters: counters}; chrome.storage.local.get(counters, function(data) { if (!data.counters) { data.counters {}; } data.counters[self.id] {clicks: clicks}; chrome.storage.local.set(data, function() { self.saving false; if (self.clicks ! clicks) { // self.clicks changed while we were saving, so save again. self.save(); } }); }); })(this.clicks); };这段代码解决了chrome.storage.local异步写模型下的两个典型问题写入合并saving标志保证同一时刻只有一个写入在途用户连续快速点击不会触发 N 次串行写入快照对比重存闭包捕获写入发起时的clicks快照写入完成后若发现this.clicks已经变化写入期间又发生了点击则递归再调一次save()补存最新值确保落盘数据最终一致。关闭时的清理与界面联动当窗口被关闭时close()会从chrome.storage.local中删除该计数器的记录并把自身从全局列表移除避免已关闭窗口的计数器在下次重启时被错误恢复Counter.prototype.close function() { this.clearListeners(); var self this; chrome.storage.local.get(counters, function(data) { delete data.counters[self.id]; chrome.storage.local.set(data); }); // Remove self from global list. var i Counter.all.indexOf(this); if (i ! -1) Counter.splice(i, 1); };界面联动则由attachToWindow()完成把窗口的onClosed绑定到close()把计数变化通过监听器同步到页面上started-by与number两个元素并给clickButton绑定increment()、给logLocalStorage绑定存储查看函数。main.html中对应的正是Started by、Clicks两个表格字段与Click、Log local storage两个按钮。onLaunched 与 onRestarted两条启动路径的分工示例的精华在于用两个事件把新会话与恢复会话彻底分离onLaunched从零开始chrome.app.runtime.onLaunched.addListener(function() { if (Counter.all.length 0) { // We might have left over state from a previous hard shutdown. chrome.storage.local.clear(); } chrome.storage.local.get(nextId, function(data) { if (!data.nextId) data.nextId 0; var id data.nextId; var counter new Counter(id, 0, launched); runApp(counter); chrome.storage.local.set(data); }); });这里有一个值得仔细品味的细节当Counter.all为空时说明当前内存中没有任何活跃计数器会先执行chrome.storage.local.clear()清掉历史残留。注释点明了意图——我们可能残留了上次硬关机留下的状态。配合nextId自增序号每次普通启动都会创建一个startedBy launched、点击数为 0 的新计数器。onRestarted还原现场chrome.app.runtime.onRestarted.addListener(function() { chrome.storage.local.get(null, function(data) { for (var id in data.counters) { var clicks data.counters[id].clicks; var counter new Counter(id, clicks, restarted); runApp(counter); } }); });两条路径的对比一目了然维度onLaunchedonRestarted触发时机用户显式启动应用意外终止后被系统恢复数据来源新建clicks 0从chrome.storage.local读回startedBylaunchedrestarted是否清理残留无活跃计数器时清空不动数据逐条恢复存储键自增nextId复用原id注意onRestarted中chrome.storage.local.get(null, ...)会取出整个存储对象随后遍历data.counters为每个持久化过的计数器按原id、原clicks重建Counter实例——这正是恢复到上次运行时的状态的落地实现。界面上 main.html 的第一行说明文字 This app starts with a click count of zero when launched, but preserves that count when restarted. 也精确概括了这一行为。窗口恢复runApp 与窗口 id 复用计数器重建之后还需要把窗口开回来。runApp()负责创建应用窗口function runApp(counter) { chrome.app.window.create(main.html, { id: counter.id , innerBounds: { width: 800, height: 600 } }, function(win) { win.contentWindow.onload function() { counter.attachToWindow(win, win.contentWindow.document); }; }); }关键点是id选项Chrome Apps 的chrome.app.window.create支持为窗口指定持久化 id同名 id 的窗口在被恢复时会保留上次的位置与尺寸状态。这里直接把计数器的id作为窗口 id既让每个计数器拥有独立窗口也保证了重启恢复的窗口能尽量还原原布局。窗口创建完成后通过contentWindow.onload把计数器挂载到窗口文档上完成数据与界面的最终绑定。验证流程如何在真实浏览器中观察恢复行为要亲身体验onRestarted可按以下步骤验证需 Chrome 及打包应用支持环境进入_archive/apps/samples/restarted-demo目录在chrome://extensions开启开发者模式通过加载已解压的扩展程序载入该目录启动应用多次点击Click按钮将计数增加到任意值如 5此时数据已写入chrome.storage.local点击Log local storage按钮可在控制台看到类似local storage: {nextId: 1, counters: {0: {clicks: 5}}}的输出确认持久化内容通过浏览器重启或应用右键菜单中的 Simulate Browser Restart模拟浏览器重启强制触发恢复流程重启后应用自动以Started by restarted标识重新出现且Clicks恢复为 5 —— 而非从 0 开始。上图中的界面正展示了该验证场景标题栏显示 Restarted demo字段值为 Started by restarted、Clicks为 5右键菜单中 Simulate Browser Restart 高亮可见。示例的 sample_support_metadata.json 还记录了该示例的跨平台验证信息ios: {works: true, comments: Restart must be done via Safari remote debugging.}即 iOS 环境下应用可正常工作但重启触发需通过 Safari 远程调试完成。实现要点回顾与适用前提回顾整个示例可以提炼出四条可复用的设计经验状态必须主动落盘所有需要跨崩溃保留的数据点击数、id都通过chrome.storage.local持久化内存对象不承担任何恢复责任事件分工明确onLaunched处理全新会话onRestarted只做读回 重建两者互不干扰写入要考虑异步竞态save()中的saving标志与快照对比重存是应对chrome.storage.local异步回调的实用范式窗口 id 用于布局恢复chrome.app.window.create的id参数让恢复的窗口尽量还原原位置尺寸。需要说明的是本示例位于仓库的_archive/apps归档目录下属于 Chrome 打包应用Chrome Apps时代的示例chrome.app.runtime、chrome.app.window等 API 现已不再演进但它所展示的事件驱动恢复 本地持久化 防抖写入模式对于理解现代扩展中chrome.storage的持久化用法、Service Worker 生命周期下的状态恢复设计仍然具有直接的借鉴价值。如果你在浏览器重启后需要恢复扩展自身状态完全可以参照onRestarted的这套读回数据 → 重建对象 → 重建界面的流程来组织自己的代码。延伸阅读示例 README官方对onRestarted事件与示例行为的说明_archive/apps/samples/restarted-demo/background.js本文全部核心代码_archive/apps/samples/restarted-demo/main.html应用界面_archive/apps/samples/restarted-demo/manifest.json应用清单仓库中 _archive/apps/samples/storage 与 _archive/apps/samples/window-state 等示例可进一步了解chrome.storage与窗口状态的更多用法【免费下载链接】chrome-extensions-samplesChrome Extensions Samples项目地址: https://gitcode.com/gh_mirrors/ch/chrome-extensions-samples创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

5分钟一文搞懂service unavailable是什么意思及实战避坑指南

5分钟一文搞懂service unavailable是什么意思及实战避坑指南

5分钟一文搞懂service unavailable是什么意思及实战避坑指南 复制来的后端代码跑不通,接口一调就报错 503,心里没底不知道咋调?别慌,很多老手初学时也栽在这。今天不整虚的,带你一文搞懂 service…

2026/9/21 19:23:59 阅读更多 →
Flink SQL ALTER 语句完全指南:ALTER TABLE / VIEW / DATABASE / FUNCTION / CATALOG 实战与原理

Flink SQL ALTER 语句完全指南:ALTER TABLE / VIEW / DATABASE / FUNCTION / CATALOG 实战与原理

Flink SQL ALTER 语句完全指南:ALTER TABLE / VIEW / DATABASE / FUNCTION / CATALOG 实战与原理 【免费下载链接】flink 项目地址: https://gitcode.com/gh_mirrors/fli/flink 导读 在 Flink Table / SQL 生态中,ALTER 语句用于修改一个已经在…

2026/9/21 19:23:59 阅读更多 →
马尔代夫莉莉岛避坑指南:一文搞懂报名与证书区别

马尔代夫莉莉岛避坑指南:一文搞懂报名与证书区别

马尔代夫莉莉岛避坑指南:一文搞懂报名与证书区别 看了一堆教程还是不会写项目?别慌,这种“眼高手低”的挫败感,老手都经历过。很多人卡在细节里出不来,不是代码写不好,而是连基本的准入规则、材料清单都没搞透,导致前期精力全浪费在无效操作上。今天咱…

2026/9/21 19:23:59 阅读更多 →

最新新闻

欲练此功必先自宫:后端开发最佳实践与面试避坑指南

欲练此功必先自宫:后端开发最佳实践与面试避坑指南

欲练此功必先自宫:后端开发最佳实践与面试避坑指南 面试被问原理答不上来,是不是觉得脑子里一片浆糊?别慌,这不是你笨,而是你一直只记结论,没摸透底层逻辑。很多新人学编程,就像练绝世武功,光背招式口诀,连内力运行路线都没搞清,遇到变招直接卡壳。…

2026/9/21 20:04:17 阅读更多 →
3步搞定Abbyy14序列号激活,源码解析避坑指南

3步搞定Abbyy14序列号激活,源码解析避坑指南

3步搞定Abbyy14序列号激活,源码解析避坑指南 报错堆满屏幕?StackTrace 像天书一样滚过去,光标在 Abbyy.FineReader.Engine 那一行闪烁,你盯着 LicenseException: Invalid…

2026/9/21 20:04:17 阅读更多 →
新手避坑:Python爬虫被拒的5个致命原因与修复方案

新手避坑:Python爬虫被拒的5个致命原因与修复方案

新手避坑:Python爬虫被拒的5个致命原因与修复方案 面试被问到爬虫原理,你只记得用 requests 库发请求,却被反问“为什么对方服务器直接返回 403 禁止访问?”瞬间大脑空白。这种窘境不是个例,很多初学者把爬虫当成简单的…

2026/9/21 20:04:17 阅读更多 →
搞定张国荣动图:版本升级API全变了?看这份完整示例

搞定张国荣动图:版本升级API全变了?看这份完整示例

搞定张国荣动图:版本升级API全变了?看这份完整示例 版本升级后 API 全变了,以前跑通的代码现在直接报错,这种崩溃感谁懂?别慌,这篇 张国荣动图 手写实现的 完整示例 ,就是为你准备的救命稻草。…

2026/9/21 20:04:17 阅读更多 →
鸿蒙Flutter响应式状态管理:用rxdart_ext重构复杂事件流的完整实践

鸿蒙Flutter响应式状态管理:用rxdart_ext重构复杂事件流的完整实践

在鸿蒙设备上调试 Flutter 项目的这段时间,我踩过的最大一个坑不是系统适配,而是把响应式状态管理想得太简单。搜索框输入、列表分页、筛选联动、下拉刷新……这些事件单个看不复杂,凑在一个页面里就是一团乱麻。后来我把 rxdart_ext 引进来&…

2026/9/21 20:03:16 阅读更多 →
3步搞定小娜怎么关闭,程序员从入门到精通避坑指南

3步搞定小娜怎么关闭,程序员从入门到精通避坑指南

3步搞定小娜怎么关闭,程序员从入门到精通避坑指南 复制来的代码跑不通,报错红一片,你是不是也对着屏幕抓狂?别慌,这种“小娜怎么关闭”式的系统级配置问题,往往不是代码逻辑错误,而是环境或权限的错位。很多开发者在从入门到精通的过程中,最容易卡在…

2026/9/21 20:03:16 阅读更多 →

日新闻

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程 【免费下载链接】agentic-awesome-skills AAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and …

2026/9/21 0:00:01 阅读更多 →
gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析

gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析

gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析 【免费下载链接】gin-vue-admin 🚀ViteVue3Gin拥有AI辅助的基础开发平台,企业级业务AI开发解决方案,内置mcp辅助服务,内置skills管理,…

2026/9/21 0:00:01 阅读更多 →
Wox 全功能插件开发实战指南:基于 Python / Node.js 宿主与 WebSocket 的持久化插件体系

Wox 全功能插件开发实战指南:基于 Python / Node.js 宿主与 WebSocket 的持久化插件体系

桌面应用AI 应用插件系统 【免费下载链接】Wox A cross-platform launcher that simply works 项目地址: https://gitcode.com/gh_mirrors/wo/Wox 点击查看 免费下载 全功能插件(Full-featured Plugin)是 Wox 三类插件实现方式中能力最完整的…

2026/9/21 0:00:01 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/21 3:13:20 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/21 2:19:36 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/21 4:51:05 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/21 15:36:51 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/21 15:36:51 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/19 23:35:34 阅读更多 →