活动资源优化 - 后置加载方案
概述
为了优化进入大厅的加载速度,将部分活动资源从预加载改为后置加载。玩家进入大厅后,这些活动显示加载占位符,资源在后台下载完成后自动替换为实际入口。
当前架构(v2.0 - 事件驱动)
重构日期:2025-10-24 提交哈希:e8b62e66889 完整工作记录:WTC 资源加载优化任务跟踪
架构原则
✅ 依赖倒置 - 资源层不依赖 UI 层 ✅ 职责分离 - 资源层负责下载,UI 层负责显示 ✅ 事件驱动 - 通过事件实现完全解耦 ✅ 生命周期安全 - 无跨层引用,无野指针风险
模块结构
ActivityDownloader (src/common/resource_v2/downloaders/)
├─ 职责:纯资源下载逻辑
├─ 输入:活动信息数组 [{activityId, themeName, ...}]
├─ 输出:事件通知(DOWNLOAD_START / DOWNLOAD_COMPLETE)
└─ 特点:完全不涉及 UI,不持有任何 Node 引用
ResourceManV2 (src/common/resource_v2/)
├─ 统一资源下载入口
└─ downloadActivities(activities) - 调度 ActivityDownloader
ActivityMan (src/task/model/)
├─ getLagLoadActivities() - 返回活动信息数组
├─ downloadLagLoadActivities() - 触发下载
└─ createLagLoadActivityPlaceholders() - [已废弃] 保留兼容
ActivityEntranceGroupController (src/task/controller/activity_center/)
├─ 职责:完全接管 Placeholder 生命周期管理
├─ 监听:ACTIVITY_RESOURCE_DOWNLOAD_START
├─ 监听:ACTIVITY_RESOURCE_DOWNLOAD_COMPLETE
├─ _placeholderMap - 管理 placeholder 引用
├─ _onActivityDownloadStart() - 创建 placeholder
└─ _onActivityDownloadComplete() - 移除 placeholder,创建真实入口事件驱动流程
ActivityEntranceGroupController (UI层)
│
├─ onEnter()
│ ├─ 监听 ACTIVITY_RESOURCE_DOWNLOAD_START
│ └─ 监听 ACTIVITY_RESOURCE_DOWNLOAD_COMPLETE
│
├─ _startLagLoadActivityDownload()
│ └─ ActivityMan.downloadLagLoadActivities()
│ └─ ResourceManV2.downloadActivities(activities)
│ └─ ActivityDownloader.download(activities)
│ ├─ 🔔 发送 DOWNLOAD_START 事件
│ ├─ 执行下载
│ └─ 🔔 发送 DOWNLOAD_COMPLETE 事件
│
├─ _onActivityDownloadStart(event) ← 监听到事件
│ ├─ 创建 placeholder (UI操作)
│ └─ 保存到 _placeholderMap
│
└─ _onActivityDownloadComplete(event) ← 监听到事件
├─ 从 _placeholderMap 获取 placeholder
├─ 移除 placeholder (UI操作)
├─ 创建真实入口 (UI操作)
└─ 触发布局更新事件定义
位置:src/common/events/CommonEvent.js
// 单个活动资源下载开始(UI层监听)
ACTIVITY_RESOURCE_DOWNLOAD_START: "activity_resource_download_start"
// 单个活动资源下载完成(UI层监听)
ACTIVITY_RESOURCE_DOWNLOAD_COMPLETE: "activity_resource_download_complete"事件数据格式:
// DOWNLOAD_START 事件数据
{
activityInfo: {
activityId: string,
activityName: string,
themeName: string,
activityTag: number
}
}
// DOWNLOAD_COMPLETE 事件数据
{
activityInfo: {
activityId: string,
activityName: string,
themeName: string,
isSuccess: boolean // 下载是否成功
}
}TypeScript 类型支持
// ActivityDownloader.d.ts
interface ActivityInfo {
activityId: string;
activityName: string;
themeName: string;
activityTag?: number;
isSilentLoad?: boolean;
lagLoadPriority?: number;
}
class ActivityDownloader extends BaseDownloader {
constructor();
download(activities: ActivityInfo[]): void;
}
// ResourceManV2.d.ts
class ResourceManV2 {
downloadActivities(activities: ActivityInfo[]): void;
}关键代码位置
| 模块 | 路径 | 关键方法 | |------|------|---------| | ActivityDownloader | src/common/resource_v2/downloaders/ActivityDownloader.js | download(), _onDownloadComplete() | | ResourceManV2 | src/common/resource_v2/ResourceManV2.js | downloadActivities() | | ActivityMan | src/task/model/ActivityMan.js | getLagLoadActivities(), downloadLagLoadActivities() | | ActivityEntranceGroupController | src/task/controller/activity_center/ActivityEntranceGroupController.js | _onActivityDownloadStart(), _onActivityDownloadComplete() | | 事件定义 | src/common/events/CommonEvent.js | ACTIVITY_RESOURCE_DOWNLOAD_START, ACTIVITY_RESOURCE_DOWNLOAD_COMPLETE | | 类型定义 | src/common/resource_v2/downloaders/ActivityDownloader.d.ts | TypeScript 接口 |
配置说明
活动标记
| 字段 | 类型 | 说明 |
|---|---|---|
isSilentLoad | Boolean | 是否静默加载(不显示占位符) |
activityTag | Number | 入口节点的 tag 值 |
lagLoadPriority | Number | 后置加载优先级(数值越小,优先级越高) |
优先级规范
配置位置:res_oldvegas/flavor/js_src/task/model/ActivityConfig.json
| 优先级范围 | 分类 | 典型活动 |
|---|---|---|
| 90 | 核心系统活动 | SystemCasinoChallenge, SystemDailyMission, SystemMedalRush |
| 101-102 | 高优先级活动 | BingoIsland, Badges, GrowUpBounty, PigPower |
| 110-120 | 重要活动 | CollectCity, HotSpin, SurprisePackage, PosterCenter |
| 130-150 | 普通活动 | LuckyDraw, Season, AlbumCatcher 等 |
| 160-190 | 低优先级活动 | VideoReward, Guide 等辅助功能 |
配置建议:
- 核心系统活动优先级最高(90),确保最先加载
- 用户高频交互的活动设置高优先级(101-120)
- 辅助功能和引导类活动设置低优先级(160-190)
- 预留优先级区间,便于后续插入新活动
使用示例
// 需要占位符的活动
var activity = {
activityId: "badges_2025",
activityName: "Badges Event",
themeName: "badges_2025",
isSilentLoad: false, // 显示占位符
activityTag: 1001,
lagLoadPriority: 150
};
// 静默加载的活动
var silentActivity = {
activityId: "guide_activity",
activityName: "Guide Activity",
themeName: "guide",
isSilentLoad: true, // 不显示占位符
activityTag: 1002,
lagLoadPriority: 200
};补单延迟加载机制
背景
活动采用延迟加载策略后(initActivity 和 startActivity 分离),补单(verify purchase)可能在活动资源未加载时触发,导致补单界面无法正常显示。
解决方案
通过新增专用事件和延迟回调机制,确保补单时活动资源已加载。
核心组件
1. 新增事件
位置:src/task/enum/ActivityNoticeType.js
// 延迟加载活动的补单专用事件
NOTICE_VERIFY_PURCHASE_FOR_LOG_LOAD_ACTIVITY: 1122. 延迟加载补单回调
位置:src/task/entity/BaseActivity.js
/**
* 延迟加载补单回调(资源加载完成后调用)
* @param {*} args - 补单参数
*/
onVerifyPurchaseWithLagLoaing: function(args) {
'use strict';
// 1. 确保资源已加载
this.startActivity();
// 2. 调用原有补单回调
this.onVerifyPurchase(args);
}3. 事件监听器保护
位置:src/task/model/ActivityMan.js
// 数据刷新时保护补单事件监听器
_refreshActivityData: function() {
'use strict';
// 移除旧事件监听器(排除补单事件)
this._eventTarget.removeAllListeners(function(eventType) {
return eventType !== ActivityNoticeType.NOTICE_VERIFY_PURCHASE_FOR_LOG_LOAD_ACTIVITY;
});
// 重新注册活动事件监听器
// ...
}工作流程
StoreMan 补单触发
│
├─ 发送 NOTICE_VERIFY_PURCHASE_FOR_LOG_LOAD_ACTIVITY 事件
│ └─ noticeParams: {activityId, ...}
│
↓
BaseActivity 接收事件
│
├─ onVerifyPurchaseWithLagLoaing(args)
│ ├─ 1. this.startActivity() → 确保资源加载
│ │ └─ 如果未加载,触发资源加载流程
│ │
│ └─ 2. this.onVerifyPurchase(args) → 调用原有补单逻辑
│ └─ 显示补单界面
│
└─ 补单完成关键代码位置
| 模块 | 路径 | 关键方法 |
|---|---|---|
| StoreMan | src/store/model/StoreMan.js | verifyPurchase() - 发送补单事件 |
| BaseActivity | src/task/entity/BaseActivity.js | onVerifyPurchaseWithLagLoaing() - 延迟加载补单回调 |
| ActivityNoticeType | src/task/enum/ActivityNoticeType.js | NOTICE_VERIFY_PURCHASE_FOR_LOG_LOAD_ACTIVITY - 事件定义 |
| ActivityMan | src/task/model/ActivityMan.js | _refreshActivityData() - 事件监听器保护 |
兼容性说明
- ✅ 向后兼容:旧的
NOTICE_VERIFY_PURCHASE事件仍然存在 - ✅ 数据兼容:优化了
isCustomDataValid()的 funcType 判断,兼容历史活动数据(funcType 为空) - ✅ 双事件共存:延迟加载活动使用新事件,普通活动仍使用旧事件
调试技巧
关键日志:
// StoreMan
"[StoreMan] verifyPurchase: Triggering lag-load activity purchase verify"
// BaseActivity
"[BaseActivity] onVerifyPurchaseWithLagLoaing: Start loading resources for activity:"
"[BaseActivity] onVerifyPurchaseWithLagLoaing: Resources loaded, calling original callback"常见问题排查
- 补单界面无法显示 → 检查活动是否正确配置了
lagLoadPriority - 补单事件被清理 → 检查
ActivityMan._refreshActivityData()是否正确保护了补单事件 - funcType 验证失败 → 检查活动数据中的 funcType 字段是否正确
维护建议
日常维护
- ✅ 新活动需明确配置
isSilentLoad标志 - ✅ UI 层(ActivityEntranceGroupController)负责监听下载事件
- ✅ 资源层(ActivityDownloader)只负责下载,不涉及 UI
- ✅ 测试时注意观察事件触发和 placeholder 生命周期
调试技巧
关键日志:
// ActivityEntranceGroupController
"[ActivityEntranceGroupController] Starting lag-load activity download, count:"
"[ActivityEntranceGroupController] Creating placeholder for:"
"[ActivityEntranceGroupController] Replacing placeholder with real entrance:"
// ActivityDownloader
"[ActivityDownloader] download: Start batch download, total activities:"
"[ActivityDownloader] _onDownloadComplete: Success, themeName:"常见问题排查
- 占位符未创建 → 检查
isSilentLoad配置和事件监听 - 下载完成后未替换 → 检查
ACTIVITY_RESOURCE_DOWNLOAD_COMPLETE事件是否正确触发 - 活动未激活 → 检查
ActivityMan._activateLagLoadActivity()是否被调用
大厅 Flagstone 入口刷新机制
背景
部分活动(如 CloverClash、WinnerSlots、TowerTrials 等)拥有大厅 Flagstone 入口。当这些活动配置为延迟加载时,需要在资源加载完成后动态刷新大厅入口列表。
支持的活动
| 活动名称 | 活动类 | 优先级 |
|---|---|---|
| WinnerSlots | WinnerSlotsActivity | 160 |
| TowerTrials | TowerTrialsActivity | 170 |
| CloverClash | CloverClashActivity | 150 |
| SpinBattleRoyale | SpinBattleRoyaleActivity | - |
| Badges | BadgesActivity | 102 |
核心机制
1. 事件定义
位置:src/common/events/CommonEvent.js
// 延迟加载活动下载完成后触发大厅刷新
LAGLOAD_ACTIVITY_DOWNLOAD_ENDED: "lagload_activity_download_ended"2. FlagStoneTableView 接口
位置:src/common/custom_node/FlagStoneTableView.js
// 获取所有拥有 flagstone 入口的活动列表
getHasFlagStoneActivityList: function() {
var list = [];
// 按顺序检查各活动:WinnerSlots, TowerTrials, CloverClash, SpinBattle, Badges
// 返回当前开放的活动数组
return list;
}
// 按名称获取单个活动
getHasFlagStoneActivity: function(activityName) {
// 返回活动对象或 null
}3. 智能刷新逻辑
位置:ClassicLobbyViewController.js / LobbyViewController.js
refreshActivityLobbyFlagstoneList: function() {
// 1. 检查是否在 Main Lobby(非 Main Lobby 直接返回)
if (window.ClassicLobbyTabType !== SubjectClassify.SUBJECT_ALL) return;
// 2. 检查是否在动画/滚动中(如果是则延迟刷新)
if (tableView.isDragging() || !tableView._hasScrollEnd) {
this.schedule(this.updateLoop, 0.0); // 轮询等待
return;
}
// 3. 获取需要刷新的活动列表
var activityList = this.flagstoneTable.getHasFlagStoneActivityList();
// 4. 保存当前滚动位置,刷新列表
var originOffset = tableView.getContentOffset();
this.initFlagStone();
// 5. 计算新增入口宽度,恢复滚动位置
var offsetX = 0;
for (var i = 0; i < activityList.length; i++) {
offsetX += activityList[i].getEntranceNodeWidth();
}
tableView.setContentOffset(cc.p(originOffset.x - offsetX, originOffset.y));
}工作流程
ActivityLoader 资源加载完成
│
├─ 发送 LAGLOAD_ACTIVITY_DOWNLOAD_ENDED 事件
│
↓
LobbyViewController 接收事件
│
├─ refreshActivityLobbyFlagstoneList()
│ │
│ ├─ 检查是否在 Main Lobby
│ ├─ 检查是否在动画/滚动中
│ │ └─ 如果是,延迟刷新(schedule 轮询)
│ │
│ ├─ 获取活动列表 getHasFlagStoneActivityList()
│ ├─ 保存滚动位置
│ ├─ 刷新 flagstone 列表 initFlagStone()
│ └─ 恢复滚动位置(加上新增入口宽度)
│
└─ 刷新完成,用户无感知配置说明
在 ActivityConfig.json 中配置延迟加载:
{
"WinnerSlots": {
"templateName": "WinnerSlotsActivity",
"themeName": "winner_slots",
"lagLoadPriority": 160,
"isSilentLoad": true
},
"TowerTrials": {
"templateName": "TowerTrialsActivity",
"themeName": "tower_trials",
"lagLoadPriority": 170,
"isSilentLoad": true
}
}关键代码位置
| 模块 | 路径 | 关键方法 |
|---|---|---|
| ClassicLobbyViewController | src/common/controller/classic/ClassicLobbyViewController.js | refreshActivityLobbyFlagstoneList() |
| LobbyViewController | src/common/controller/LobbyViewController.js | refreshActivityLobbyFlagstoneList() |
| ClassicFlagStoneTableView | src/common/custom_node/ClassicFlagStoneTableView.js | getHasFlagStoneActivityList() |
| FlagStoneTableView | src/common/custom_node/FlagStoneTableView.js | getHasFlagStoneActivityList() |
| CommonEvent | src/common/events/CommonEvent.js | LAGLOAD_ACTIVITY_DOWNLOAD_ENDED |
性能指标
- 首次进入大厅加载时间:减少约 30-50%(取决于后置加载活动数量)
- 后台下载时间:约 2-5 秒(取决于网络和资源大小)
- 并发下载数:由 ResourceManV2 控制(默认 3-5 个)
历史架构说明
v1.0 架构(已废弃)
v1.0 架构存在以下问题,已在 v2.0 中完全重构:
- ❌ 资源层(ActivityResourceDownloadManager)直接持有 UI 层的 Node 引用
- ❌ 资源层负责创建和管理 placeholder(违反单一职责)
- ❌ 存在跨层引用和生命周期管理风险
当前状态:
- ⚠️ 不建议继续使用
createLagLoadActivityPlaceholders()接口 - ⚠️ 该接口已废弃,保留仅为向后兼容
- ✅ 新代码应使用
downloadLagLoadActivities()接口
详细的架构演进过程和技术决策,请参考:WTC 资源加载优化任务跟踪
最后更新:2025-11-26 架构版本:v2.1(事件驱动 + Flagstone 刷新) 维护者:WTC Team