~/ ?.log $
返回文章列表
8 min read
更新于 2026年3月6日

我不知道的 VSCode 扩展(05)— 生命周期与激活策略

VSCode 可以装几百个扩展而启动速度不崩,靠的不是硬件,而是延迟激活机制。

一、扩展不是”一启动就全跑”的

VSCode 可以装几百个扩展而启动速度不崩,靠的不是硬件,而是延迟激活机制。

很多人以为 VSCode 一启动,所有扩展就全部加载运行了。但实际上,绝大多数扩展在 VSCode 启动后处于”未激活”状态——它们的 package.json 被 VSCode 解析了(所以命令、菜单、视图已经可见),但 extension.ts 中的 activate() 函数还没有执行。

只有当特定条件满足时,VSCode 才会去加载和执行扩展代码。这个”特定条件”就是 Activation Events(激活事件)

二、激活事件:显式声明 vs 隐式推断

显式声明

package.jsonactivationEvents 数组中明确写出触发条件:

{
  "activationEvents": ["onLanguage:python", "onView:myTreeView", "onStartupFinished"]
}

常见的激活事件:

事件触发时机
onCommand:xxx命令被执行时
onLanguage:xxx打开某种语言的文件时
onView:xxx某个视图变得可见时
onFileSystem:xxx访问某种文件系统方案时
onWebviewPanel:xxx某种 Webview 面板被创建时
onStartupFinishedVSCode 启动完成后(所有窗口初始化完毕)
onTerminal终端被打开时(2025.7 新增)
workspaceContains:xxx工作区包含匹配的文件时(如 **/*.py
*立即激活(强烈不推荐)

隐式推断(VSCode 1.74+)

这里有一个很多人会忽略的细节——从 VSCode 1.74 开始,很多激活事件不需要手动声明了。VSCode 会根据扩展在 contributes 中的声明自动推断:

contributes 中声明了VSCode 自动推断的激活事件
contributes.commands 中有命令 AonCommand:A
contributes.languages 中注册了语言 LonLanguage:L
contributes.views 中注册了视图 VonView:V
contributes.customEditors打开关联文件时
contributes.authentication身份验证触发时

换句话说,如果扩展的唯一激活场景是”用户执行扩展注册的命令”,那 activationEvents 可以留空数组:

{
  "activationEvents": [],
  "contributes": {
    "commands": [{ "command": "my-ext.doSomething", "title": "Do Something" }]
  }
}

VSCode 会自动在 my-ext.doSomething 被执行时激活扩展。

但要注意:如果扩展需要在”没有用户主动操作”的情况下激活(比如启动后自动检测工作区配置),就必须显式声明 onStartupFinishedworkspaceContains,因为这些场景无法从 contributes 推断。

* 通配符:能用但不该用

* 表示”VSCode 一启动就激活”。听起来方便,但代价是拖慢所有用户的启动速度。VSCode 官方文档明确建议不要使用 *,除非扩展确实需要在启动时立即工作。

Running Extensions 视图(命令面板输入 Developer: Show Running Extensions)可以看到每个扩展的激活时间。如果一个扩展的激活时间超过 200ms,就值得考虑优化激活策略。

三、activate() — 扩展的启动入口

当激活条件满足时,VSCode 会加载扩展的入口模块并执行 activate() 函数:

export function activate(context: vscode.ExtensionContext) {
  // 注册命令
  context.subscriptions.push(
    vscode.commands.registerCommand('my-ext.doSomething', () => {
      // 命令逻辑
    }),
  );

  // 注册事件监听
  context.subscriptions.push(
    vscode.workspace.onDidSaveTextDocument((doc) => {
      // 保存时执行
    }),
  );
}

context 参数是 ExtensionContext 类型,是扩展和 VSCode 之间的桥梁。它提供几个关键能力:

属性/方法作用
subscriptions可销毁资源的收集器,扩展卸载时自动 dispose
extensionUri扩展安装目录的 URI
extensionPath扩展安装目录的绝对路径
globalState跨工作区持久化存储(类似 localStorage)
workspaceState工作区级持久化存储
secrets安全存储(用于 token 等敏感信息)
globalStorageUri全局存储目录的 URI

subscriptions:资源管理的关键

问题的关键在于——context.subscriptions 是防止内存泄漏的核心机制

VSCode 中几乎所有资源都实现了 Disposable 接口。命令、事件监听器、状态栏项目、文件监视器——注册时返回一个 disposable 对象,调用 .dispose() 可以释放资源。

把 disposable 推入 context.subscriptions 数组后,扩展卸载时 VSCode 会自动逐一调用 .dispose(),释放所有资源。

如果不加入 subscriptions,就必须在 deactivate() 中手动释放,否则会泄漏。

下面这段代码能说明问题:

// 错误写法:泄漏
export function activate(context: vscode.ExtensionContext) {
  vscode.workspace.onDidSaveTextDocument((doc) => {
    // 这个监听器永远不会被释放
  });
}

// 正确写法
export function activate(context: vscode.ExtensionContext) {
  context.subscriptions.push(
    vscode.workspace.onDidSaveTextDocument((doc) => {
      // 扩展卸载时自动释放
    }),
  );
}

globalState 和 workspaceState:轻量持久化

// 写入
context.globalState.update('lastRun', Date.now());

// 读取
const lastRun = context.globalState.get<number>('lastRun');

globalState 跨工作区共享,workspaceState 只在当前工作区生效。底层是 SQLite 存储,适合存少量配置数据,不适合存大文件。

四、deactivate() — 扩展的关闭收尾

export function deactivate() {
  // 释放 subscriptions 无法覆盖的资源
  // 保存未持久化的状态
}

deactivate() 是可选的。在以下时机被调用:

(1) VSCode 窗口关闭 (2) 扩展被禁用或卸载 (3) VSCode 重新加载窗口(Developer: Reload Window

很多人以为”只要用了 context.subscriptions 就不需要 deactivate()”,大多数情况下确实如此。但有一种场景必须用 deactivate()——异步清理

如果扩展在运行时创建了需要异步关闭的资源(比如一个 WebSocket 连接或一个子进程),deactivate() 可以返回一个 Promise,VSCode 会等待这个 Promise 完成(但有超时限制):

let ws: WebSocket | undefined;

export function activate(context: vscode.ExtensionContext) {
  ws = new WebSocket('ws://localhost:8080');
}

export function deactivate(): Promise<void> {
  return new Promise((resolve) => {
    if (ws) {
      ws.close();
      ws.on('close', resolve);
    } else {
      resolve();
    }
  });
}

说白了,context.subscriptions 处理同步资源的释放,deactivate() 处理异步资源的关闭

五、完整生命周期时序

VSCode 启动

解析所有扩展的 package.json(声明层加载)

渲染已注册的命令、菜单、视图骨架

用户触发某个激活事件(如执行命令)

VSCode 加载扩展入口模块

执行 activate(context)

扩展进入运行状态,响应用户操作

VSCode 关闭 / 扩展被禁用

执行 deactivate()

释放 context.subscriptions 中的资源

扩展生命周期结束

如果你只记住一句话:声明在安装时解析,代码在激活时执行,资源在卸载时释放——这是 VSCode 扩展的三段式生命周期。


本系列其他文章:

延伸阅读:

share.ts

// 觉得这篇文章有帮助?

// 欢迎分享给更多人

export const  subscribe  =  "/rss.xml" ;