产品重命名与后台运行功能实现

January 2, 2026
6 min read
By devshan

Table of Contents

This is a list of all the sections in this post. Click on any of them to jump to that section.

涉及模块: 全局命名、Android 原生、Windows 托盘、Flutter 生命周期


一、产品命名讨论与决策

1.1 背景

项目原名 E2EEPAN(End-to-End Encrypted PAN),名称过于技术化,不利于品牌推广。需要一个更简洁、更有辨识度的名字。

1.2 命名迭代过程

第一轮:VaultS3

最初确定 VaultS3 作为产品名:

  • 优点:Vault(保险库)体现安全性,S3 体现存储后端
  • 缺点
    • 小写 vaults3 不美观(数字+字母混合)
    • 易联想 HashiCorp Vault(同名产品)
    • 代码中 vaults3 作为标识符视觉效果差

第二轮:探索替代方案

我不喜欢小写+数字的写法,决定重新探索。

约束条件

  1. 不造新单词(如 Vaultix、Vaultr 等)
  2. 与 S3/对象存储有一定关联
  3. 简短,但不必是单个单词
  4. 视觉美观

候选方案

名称含义评价
Cipher密码太通用
CryptStore加密存储9字母,略长
Safe3安全+3仍有数字
DataArk数据方舟与 S3 关联弱
pboxPrivate Box与 S3 关联弱
StoreX存储X简洁,Store 体现存储

最终决定:StoreX

  • Store:直接体现”存储”核心功能
  • X:代表加密(eXtended/eXcrypted 的暗示)
  • 6 字母,简短
  • 大小写混合 StoreX 美观,纯小写 storex 也可接受

二、全局重命名实现

2.1 修改范围

类别修改项旧值新值
Flutter 项目pubspec.yaml namevaults3_clientstorex_client
pubspec.yaml descriptionVaultS3 - E2E…StoreX - E2E…
README.md 标题VaultS3 ClientStoreX Client
Androidnamespacecom.vaults3.clientcom.storex.client
applicationIdcom.vaults3.clientcom.storex.client
AAR 引用vaults3-mobile.aarstorex-mobile.aar
MainActivity 包路径com/vaults3/clientcom/storex/client
MethodChannel 名vaults3/corestorex/core
MethodChannel 名vaults3/video_thumbnailstorex/video_thumbnail
Flutter 代码数据库文件名vaults3.dbstorex.db
锁文件名vaults3-desktop-core.lockstorex-desktop-core.lock
EXE 文件名vaults3-core.exestorex-core.exe
Bucket 提示例如:vaults3例如:storex
Go 后端go.mod 模块名vaults3storex
所有 import 路径vaults3/internal/…storex/internal/…
默认 bucketvaults3storex
S3 元数据路径.vaults3/.storex/
文件魔数VAULTS31STOREX01
验证标记VAULTS3_VERIFYSTOREX_VERIFY

2.2 涉及文件

Flutter 端

  • client/pubspec.yaml
  • client/README.md
  • client/android/app/build.gradle.kts
  • client/android/app/src/main/kotlin/com/storex/client/MainActivity.kt(新建)
  • client/lib/core/services/native_core_service.dart
  • client/lib/core/services/video_thumbnail_service.dart
  • client/lib/core/database/database.dart
  • client/lib/core/state/app_state.dart
  • client/lib/ui/s3_config_page.dart

Go 端

  • core/go.mod
  • core/cmd/server/main.go
  • core/mobile/mobile.go
  • core/internal/config/config.go
  • core/internal/storage/s3.go
  • core/internal/crypto/crypto.go
  • core/internal/api/server.go
  • core/internal/api/utils.go
  • core/internal/api/e2e.go
  • core/internal/api/files.go
  • core/internal/api/folders.go
  • core/internal/api/orphan.go
  • core/internal/api/send.go
  • core/internal/api/thumbnail.go

2.3 关键技术决策

为什么改 S3 存储路径?

我明确决定开发早期不做数据兼容,直接修改 .vaults3/.storex/

受影响的路径

  • .storex/salt - 密钥盐值
  • .storex/verify - 密码验证标记
  • .storex/meta.enc - 加密元数据
  • .storex/send/sessions.enc - 发送会话
  • .storex/send/messages/ - 发送消息

为什么改文件魔数?

便携式加密文件的魔数从 VAULTS31 改为 STOREX01

  • 8 字节固定长度
  • 用于识别文件格式
  • 开发早期无需兼容旧格式

三、后台运行功能实现

3.1 问题分析

现状

  • Android:文件界面根目录按返回键 → SystemNavigator.pop() → 应用退出
  • Android:其他界面按返回键 → 返回上一页 → 应用保持运行
  • Windows:点击关闭按钮 → 应用完全退出,无托盘

期望

  • Android:统一行为,返回键将应用放到后台
  • Windows:关闭按钮最小化到托盘,托盘菜单可真正退出

3.2 Android 实现

3.2.1 原生端修改

MainActivity.ktstorex/core MethodChannel 中添加新方法:

"moveToBackground" -> {
    moveTaskToBack(true)
    result.success(null)
}

moveTaskToBack(true) 是 Android Activity 的原生方法,将当前任务移到后台,类似按 Home 键。

3.2.2 Flutter 端修改

NativeCoreService 添加方法:

static Future<void> moveToBackground() async {
  await _channel.invokeMethod<void>('moveToBackground');
}

files_page.dart 修改返回键处理:

if (state.isAtRoot) {
  if (io.Platform.isAndroid) {
    NativeCoreService.moveToBackground();
  } else {
    SystemNavigator.pop();
  }
}

3.3 Windows 托盘实现

3.3.1 依赖选择

说明选择
tray_manager托盘管理✓ 使用
window_manager窗口管理✓ 使用
system_tray另一个托盘库未选用

选择 tray_manager + window_manager 组合:

  • tray_manager:管理托盘图标、菜单、点击事件
  • window_manager:管理窗口显示/隐藏、拦截关闭事件

3.3.2 TrayService 设计

创建 client/lib/core/services/tray_service.dart

class TrayService with TrayListener, WindowListener {
  // 单例模式
  static final TrayService _instance = TrayService._();
  factory TrayService() => _instance;
  
  Future<void> init() async {
    // 1. 初始化窗口管理器
    await windowManager.ensureInitialized();
    
    // 2. 设置窗口选项
    await windowManager.waitUntilReadyToShow(...);
    
    // 3. 监听窗口事件
    windowManager.addListener(this);
    
    // 4. 拦截关闭事件(关键!)
    await windowManager.setPreventClose(true);
    
    // 5. 设置托盘图标和菜单
    await trayManager.setIcon('windows/runner/resources/app_icon.ico');
    await trayManager.setContextMenu(menu);
    await trayManager.setToolTip('StoreX');
    
    // 6. 监听托盘事件
    trayManager.addListener(this);
  }
  
  // 隐藏到托盘
  Future<void> hideToTray() async {
    await windowManager.hide();
  }
  
  // 从托盘恢复
  Future<void> showFromTray() async {
    await windowManager.show();
    await windowManager.focus();
  }
  
  // 拦截关闭事件
  @override
  void onWindowClose() async {
    final isPreventClose = await windowManager.isPreventClose();
    if (isPreventClose) {
      await hideToTray();  // 隐藏而非退出
    }
  }
  
  // 托盘菜单点击
  @override
  void onTrayMenuItemClick(MenuItem menuItem) {
    switch (menuItem.key) {
      case 'show':
        showFromTray();
      case 'exit':
        windowManager.destroy();  // 真正退出
    }
  }
}

3.3.3 关键技术点

  1. setPreventClose(true):拦截窗口关闭事件,让 onWindowClose 被调用
  2. windowManager.hide():隐藏窗口,但进程保持运行
  3. windowManager.destroy():真正销毁窗口,退出应用
  4. 托盘图标:使用现有的 app_icon.ico

3.3.4 main.dart 初始化

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // ... 其他初始化 ...
  
  // Windows: 初始化系统托盘
  if (io.Platform.isWindows) {
    await TrayService().init();
  }
  
  // ... 启动应用 ...
}

3.4 托盘菜单设计

┌─────────────┐
│ 显示窗口 │
├─────────────┤
│ 退出 │
└─────────────┘
  • 显示窗口:恢复窗口到前台
  • 退出:真正关闭应用

3.5 交互流程

Android

在文件根目录按返回键
PopScope.onPopInvokedWithResult 触发
检测 Platform.isAndroid
调用 NativeCoreService.moveToBackground()
MethodChannel 调用原生 moveTaskToBack(true)
应用进入后台(进程保持运行)

Windows

点击窗口关闭按钮 (X)
WindowListener.onWindowClose 触发
检测 isPreventClose == true
调用 windowManager.hide()
窗口隐藏,托盘图标保留
可点击托盘图标恢复窗口
或点击托盘菜单"退出"真正退出

四、验证结果

4.1 编译检查

# Go 后端
$ go vet ./...
# 无错误
 
# Flutter 客户端
$ flutter analyze
# 仅 3 个历史遗留的 info 级别警告(use_build_context_synchronously)

4.2 后续手动事项

  1. 构建 AAR:生成后需重命名为 storex-mobile.aar
  2. 构建 EXE:生成后需重命名为 storex-core.exe
  3. 清空旧数据:S3 桶中的 .vaults3/ 目录、本地 vaults3.db 文件

五、经验总结

5.1 命名决策

  • 产品命名需综合考虑:技术含义、品牌独特性、代码美观性
  • 小写+数字组合(如 vaults3)在代码中视觉效果差
  • 开发早期不需考虑数据兼容,可大胆重命名

5.2 跨平台后台运行

  • Android 使用 moveTaskToBack 放后台,而非 SystemNavigator.pop 退出
  • Windows 需要 window_manager 拦截关闭 + tray_manager 托盘支持
  • 托盘退出需显式调用 windowManager.destroy()

5.3 全局重命名

  • 涉及:模块名、包名、路径、常量、通道名、文件名
  • 开发早期可直接改存储路径,不需兼容层
  • 魔数修改需更新版本号(STOREX01 中的 01

六、相关文件索引

文件用途
client/lib/core/services/tray_service.dartWindows 托盘服务
client/lib/core/services/native_core_service.dartAndroid 原生通道
client/lib/ui/files_page.dart返回键处理
client/android/.../MainActivity.ktAndroid 原生实现
core/internal/api/server.go存储路径定义
core/internal/crypto/crypto.go文件魔数定义