摘要
本文档详细梳理了芯图相册PC端和移动端的扫描服务架构,涵盖状态管理、进度更新、数据读写、界面刷新等核心机制。
核心架构差异
PC端:纯JS实现,Electron主进程执行,无后台限制,数据刷新直接执行。
移动端:三层架构(前台服务 + 原生层 + JS层),使用前台服务和WakeLock确保后台执行,数据刷新采用防抖机制(500ms)。
关键特性
1. 统一接口:两端都使用 GalleryScannerService 和 UnifiedDataService 统一接口
2. 进度回调:通过 onProgress 回调更新UI,支持国际化消息
3. 缓存机制:使用内存缓存(精简信息)优化性能,按需加载详细信息
4. 多维度分类:支持11个分类维度(AI分类、城市、颜色、目录、格式、分辨率、方向、ISO、光圈、快门、焦距)
5. 流水线架构:移动端位置信息补全采用流水线并行处理,平衡CPU和IO任务
数据流程
PC端:文件系统 → JS层扫描 → IndexedDB → 缓存 → 界面
移动端:MediaStore → 原生层扫描 → SQLite → JS层处理 → 缓存 → 界面
---
目录
1. PC端扫描服务架构
2. 移动端扫描服务架构
3. 关键机制对比
---
PC端扫描服务架构
1. 扫描状态管理
#### 1.1 状态变量
- 界面层状态 (
HomeScreen.desktop.js): isScanning: React state,控制扫描UI状态globalMessage: React state,显示扫描进度消息window.isScanning: 全局变量,供其他页面检查扫描状态
- 服务层状态 (
GalleryScannerService.js): this.isScanning: 服务内部扫描状态标志this.isInitialized: 服务初始化标志this.onProgress: 进度回调函数引用
#### 1.2 状态流转
2. 进度消息更新机制
#### 2.1 进度消息生成流程
#### 2.2 进度消息去重
- PC端使用
lastProgressMessage键值去重 - 格式:
${stage}_${totalFoundThisPhase}_${processedThisPhase} - 相似度检测阶段不过滤(允许频繁更新)
#### 2.3 进度阶段定义
initializing: 初始化扫描directory_scanning: 目录扫描file_comparison: 文件比对screenshot_detection: 截图检测cache_checking: 缓存检查remote_inference: 远程推理local_inference: 本地推理similarity_detection: 相似度检测location_enrichment: 位置信息补全completed: 扫描完成
3. 数据读写机制
#### 3.1 数据写入流程(按业务流程划分)
##### 3.1.1 基础扫描(JS层:从文件系统收集数据写入本地数据库)
##### 3.1.2 本地MobileNetV3推理(JS层:可选,辅助功能)
##### 3.1.3 AI分类(JS层:从后端API获取数据写入本地数据库)
##### 3.1.4 相似度检测(JS层:可选,可单独触发)
##### 3.1.5 城市信息补全(JS层:可选,可单独触发)
#### 3.2 数据缓存与界面数据交换机制
##### 3.2.1 缓存优先读取机制
##### 3.2.2 数据分级策略(精简信息 vs 详细信息)
##### 3.2.3 按需加载详细信息
##### 3.2.5 多维度分类支持机制
##### 3.2.4 统一数据服务接口
#### 3.3 数据刷新策略
- 扫描完成时:
progress.shouldRefresh === true时刷新 - 位置信息补全: 每处理50张图片触发一次刷新
- 相似度检测: 每检测完一个相似组触发刷新
- 防抖机制: PC端无防抖,直接刷新
4. 界面数据刷新
#### 4.1 刷新触发点
#### 4.2 刷新内容
- 分类统计 (
loadCategories()) - 城市统计 (
loadCities()) - 相似组统计 (
loadSimilarityGroups()) - 最近照片 (
loadRecentImages()) - 其他分类数据(颜色、目录、格式等)
---
移动端扫描服务架构
1. 扫描状态管理
#### 1.1 状态变量
- 界面层状态 (
HomeScreen.mobile.js): isScanning: React state,控制扫描UI状态globalMessage: React state,显示扫描进度消息window.isScanning: 全局变量,供其他页面检查扫描状态
- JS服务层状态 (
GalleryScannerService.android.js): this.isScanning: JS层扫描状态标志this.currentScanId: 当前扫描任务IDthis.onProgress: 进度回调函数引用this.eventEmitter: 原生事件监听器
- 原生层状态 (
GalleryScanService.java): - 扫描任务状态(运行中/已完成/已取消)
- 扫描进度统计(已处理/总数)
#### 1.2 状态流转
2. 进度消息更新机制
#### 2.1 进度消息生成流程(三层架构)
#### 2.2 前台服务通知更新
- 服务启动:
ScanService.startScanService()启动前台服务 - 进度更新:
ScanService.updateProgress(message, processed, total, title) - 服务停止:
ScanService.stopScanService()停止前台服务 - 心跳机制: 每10秒更新一次通知,保持服务活跃
#### 2.3 进度消息去重
- 移动端不移除去重逻辑,允许所有进度更新通过
- 原因:原生层已经控制更新频率,JS层不需要再次过滤
3. 数据读写机制
#### 3.1 原生层数据写入
##### 3.1.1 基础扫描流程(独立流程)
##### 3.1.2 AI分类流程(独立流程,用户手动触发)
#### 3.2 JS层数据读取
- 与PC端相同,使用
UnifiedDataService统一接口 - 支持缓存机制,减少数据库查询
#### 3.3 数据刷新策略
- 防抖机制: 使用
loadAllDataDebounced()防抖刷新(500ms) - 刷新触发:
- 扫描完成时:清除防抖定时器,延迟600ms后刷新
- 位置信息补全:每处理50张图片触发防抖刷新
- 相似度检测:每检测完一个相似组触发防抖刷新
4. 界面数据刷新
#### 4.1 刷新触发点
#### 4.2 防抖机制
6. 移动层架构(三层)
#### 6.1 前台服务层 (ScanForegroundService.java)
- 职责: 保持应用在后台运行时继续扫描
- 机制:
- 前台通知显示扫描进度
- WakeLock防止CPU休眠
- 心跳机制(10秒)保持服务活跃
- 生命周期:
START_SCAN: 启动服务,获取WakeLockUPDATE_PROGRESS: 更新通知内容STOP_SCAN: 停止服务,释放WakeLock
#### 6.2 原生层 (GalleryScanService.java)
- 职责: 执行实际的扫描和分类任务
- 机制:
- 独立后台线程执行扫描
- MediaStore API扫描图片
- SQLite数据库批量写入
- MobileNetV3模型推理
- 事件通信:
GalleryScanProgress: 进度事件GalleryScanCompleted: 完成事件GalleryScanError: 错误事件
#### 6.3 JS层 (GalleryScannerService.android.js)
- 职责: 协调原生层和界面层
- 机制:
- 监听原生层事件
- 处理进度数据国际化
- 更新前台服务通知
- 执行JS层后续处理(位置补全、相似度检测)
- 桥接模块 (
ScanServiceModule.java): startScanService(): 启动前台服务updateScanProgress(): 更新进度通知stopScanService(): 停止前台服务
---
关键机制对比
1. 扫描状态管理
| 特性 | PC端 | 移动端 |
|---|---|---|
| 状态变量 | isScanning, window.isScanning | isScanning, window.isScanning |
| 状态设置时机 | 扫描开始时 | 扫描开始时 |
| 状态清除时机 | 扫描完成/失败时 | 扫描完成/失败时 |
| 全局状态 | window.isScanning | window.isScanning |
| 特性 | PC端 | 移动端 |
|---|---|---|
| 消息生成 | JS层 processProgressData() | JS层 processProgressData() |
| 消息去重 | 有(基于消息键值) | 无(原生层已控制频率) |
| 通知更新 | 无(PC端不需要) | 有(前台服务通知) |
| 更新频率 | 每个阶段更新 | 原生层控制频率 |
| 特性 | PC端 | 移动端 |
|---|---|---|
| 扫描实现 | JS层实现 | 原生层实现 |
| 数据库操作 | JS层直接操作 | 原生层批量操作 |
| 数据刷新 | 直接刷新 | 防抖刷新(500ms) |
| 刷新触发 | shouldRefresh 标志 | shouldRefresh 标志 |
| 特性 | PC端 | 移动端 |
|---|---|---|
| 刷新方式 | 直接调用 loadData() | 防抖调用 loadAllDataDebounced() |
| 刷新时机 | 扫描完成、位置补全 | 扫描完成、位置补全、相似度检测 |
| 防抖机制 | 无 | 有(500ms延迟) |
| 特性 | PC端 | 移动端 |
|---|---|---|
| AI分类实现 | JS层MobileNetV3 | 原生层MobileNetV3 |
| 批量处理 | JS层批次处理 | 原生层批次处理 |
| 后续处理 | 位置补全、相似度检测 | 位置补全、相似度检测 |
| 数据更新 | 直接更新数据库 | 原生层批量更新数据库 |
| 特性 | PC端 | 移动端 |
|---|---|---|
| 后台扫描 | 支持(Electron主进程) | 支持(前台服务) |
| 进程保活 | 不需要 | WakeLock + 前台服务 |
| 通知显示 | 不需要 | 前台服务通知 |
| 心跳机制 | 不需要 | 10秒心跳更新通知 |
总结
PC端特点
1. 纯JS实现: 所有扫描逻辑在JS层实现
2. 简单直接: 状态管理和进度更新简单直接
3. 无后台限制: Electron主进程可以长时间运行
4. 无防抖: 数据刷新直接执行,无需防抖
移动端特点
1. 三层架构: 前台服务 + 原生层 + JS层
2. 后台保活: 前台服务 + WakeLock确保后台执行
3. 防抖刷新: 避免频繁刷新影响性能
4. 原生性能: 原生层扫描和AI分类性能更好
共同点
1. 统一接口: 都使用 GalleryScannerService 统一接口
2. 进度回调: 都使用 onProgress 回调更新UI
3. 数据服务: 都使用 UnifiedDataService 统一数据接口
4. 状态管理: 都使用 isScanning 和 window.isScanning 管理状态