using Cysharp.Threading.Tasks; using Newtonsoft.Json; using PhxhSDK; using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using Assets.PhxhSDK.AOT.BI; using Framework.BI; public partial class BIManager : Singlenton, IInitable, IUpdatable { /// /// 基础属性 /// private BIBaseInfo baseInfo; /// /// 一次性事件缓存类 /// private StorageEvent _storageEvent; /// /// 是否初始化 /// private bool _isInit; public PublicDictionaryPool DictionaryPool { get; } = new PublicDictionaryPool(); /// /// 参数字典 /// private Dictionary _tempEventDictionary = new(12); /// /// 服务器时间提供者 /// private IServerTimeProvider _serverTimeProvider; #region 初始化 /// /// 注册服务器时间提供者 /// /// public void RegisterServerTimeProvider(IServerTimeProvider provider) { _serverTimeProvider = provider; } /// /// 游戏启动初始化 /// public void Init() { baseInfo = new BIBaseInfo(); _storageEvent = StorageMgr.Instance.GetStorage(Framework.Constants.Storage.BI_FTE_EVENT); _storageEvent ??= new StorageEvent(); InitDictPool(); _networkAvailable = true; _useQueueMode = _sendInterval > 0; InitService(); _isInit = true; // 初始化统一的信息提供者 PhxhBIInfoProvider.Initialize(); SetUserPropertyBeforeLogin(); baseInfo.PrintAllBeforeLoginValues(); } /// /// 初始化登陆后基本属性 /// public void InitLoginBaseInfo() { try { baseInfo.SetUserInfo(); baseInfo.PrintAllAfterLoginValues(); if (!_isInit) { DebugUtil.Log("BIManager InitLoginBaseInfo 未初始化"); return; } // 登录后更新统一信息提供者 PhxhBIInfoProvider.UpdateAfterLogin(baseInfo.actor_id, baseInfo.third_platform, baseInfo.third_id); MultiPlatformBIService.SetUser(baseInfo.actor_id); SetUserProperty(); DebugUtil.Log("BIManager InitLoginBaseInfo 冲刷AOT阶段缓存事件"); // BIManager接管后,冲刷AOT阶段缓存的事件 BIToolinAOT.Instance.FlushWaitQueueIfAny(); // 发送积累事件 SendQueuedEvents(); } catch (Exception e) { DebugUtil.LogError($"BIManager.InitLoginBaseInfo error :{e}"); } } /// /// 初始化第三方打点平台 /// private void InitService() { MultiPlatformBIService.InitServices(); } /// /// 登陆前设置用户属性 /// private void SetUserPropertyBeforeLogin() { SetUserProperty("game_id", baseInfo.app_name); SetUserProperty("channel", baseInfo.channel); SetUserProperty("model", baseInfo.model); SetUserProperty("uuid", baseInfo.uuid); SetUserProperty("resolution", baseInfo.resolution); SetUserProperty("platform", baseInfo.platform); SetUserProperty("os", baseInfo.os); SetUserProperty("version", baseInfo.version); SetUserProperty("build_code", baseInfo.build_code); SetUserProperty("assets_version", baseInfo.assets_version); SetUserProperty("language", baseInfo.language); SetUserProperty("device_memory", baseInfo.storage_info); SetUserProperty("device_cpu", baseInfo.cpu); } /// /// 登陆后设置用户属性 /// public void SetUserProperty() { SetUserProperty("actor_id", baseInfo.actor_id); SetUserProperty("third_platform", baseInfo.third_platform); SetUserProperty("third_id", baseInfo.third_id); #if SDK_TALKINGDATA // _biService?.SetUserProperty(TalkingDataProfileParamName.Param1, baseInfo.model); // _biService?.SetUserProperty(TalkingDataProfileParamName.Param2, baseInfo.storage_info + "MB"); SetUserProperty("storage_info", baseInfo.storage_info + "MB"); #endif } public void SetUserProperty(string name, string value) { MultiPlatformBIService.SetUserProperty(name, value); } #endregion #region 释放 public void Release() { _tempEventDictionary = null; _dictPool = null; // 释放对象池 if (_isInit && _networkAvailable && eventQueue.Count > 0) { SendQueuedEvents(); } } #endregion #region 周期/队列 发送 /// /// 事件队列 - 增加时间戳信息 /// private readonly Queue<(string eventName, Dictionary param, long localTime, ulong serverTime)> eventQueue = new(); /// /// 是否使用队列模式(无网络或设置了发送周期) /// private bool _useQueueMode; /// /// 发送周期 默认随打随发 /// private float _sendInterval = 0f; /// /// 网络状态 通过心跳包检测 /// private bool _networkAvailable; /// /// 计时器 /// private float _timer = 0f; public void Update(float deltaTime) { if (!_isInit) return; // 检查网络状态 //CheckNetworkStatus(); //TODO 网络状态不由心跳包检查 if (eventQueue.Count <= 0 || !_networkAvailable) return; // 周期发送 if (_sendInterval > 0) { _timer += Time.deltaTime; // 达到发送周期 if (_timer >= _sendInterval) { _timer = 0; SendQueuedEvents(); } } else { SendQueuedEvents(); } } /// /// 检查网络状态 /// private void CheckNetworkStatus() { // 获取当前网络状态 var currentNetworkStatus = //NetworkManager.Instance.NetworkAvailable;//由于未登录时没有心跳包,改为使用Unity的网络状态检测 Application.internetReachability != NetworkReachability.NotReachable; // 如果网络状态发生变化 if (_networkAvailable == currentNetworkStatus) return; if (currentNetworkStatus) { // 网络恢复 _networkAvailable = true; _useQueueMode = _sendInterval > 0; // 如果有设置发送周期,仍然使用队列模式 // 如果没有设置发送周期,立即发送所有队列中的事件 if (!_useQueueMode && eventQueue.Count > 0) { SendQueuedEvents(); } } else { _networkAvailable = false; _useQueueMode = true; } } /// /// 设置事件发送周期 /// /// 发送周期,单位秒,设为0则立即发送 public void SetSendInterval(float interval) { _sendInterval = Mathf.Max(0, interval); _useQueueMode = !_networkAvailable || _sendInterval > 0; } #endregion #region 发送事件 /// /// 添加事件到队列 - 增加时间戳参数 /// private void EnqueueEvent(string eventName, Dictionary param, long localTime, ulong serverTime) { // 检查队列是否已满 if (eventQueue.Count >= MAX_QUEUE_SIZE) { DebugUtil.LogWarning("BI事件队列已满"); return; } // 从对象池获取字典并复制数据 var poolDict = GetDictFromPool(); if (param != null) { foreach (var item in param) { poolDict[item.Key] = item.Value; } } eventQueue.Enqueue((eventName, poolDict, localTime, serverTime)); } /// /// 发送队列中的所有事件 /// private void SendQueuedEvents() { if (!_networkAvailable) return; // 只要队列有事件就全部处理 while (eventQueue.Count > 0) { //将缓存的事件上报 var (eventName, param, localTime, serverTime) = eventQueue.Dequeue(); MultiPlatformBIService.TrackEvent(eventName, param); TrackEventPhxh(eventName, param, localTime, serverTime); } } private bool IsRecorded(string eventName) { return _storageEvent.EventList.Contains(eventName); } /// /// 上报自定义事件 /// public void TrackEvent(string eventName, string param1Key = null, object param1 = null, string param2Key = null, object param2 = null, string param3Key = null, object param3 = null, string param4Key = null, object param4 = null) { if (!_isInit) return; try { var localTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); var serverTime = _serverTimeProvider?.GetServerTime() ?? 0; _tempEventDictionary.Clear(); var userParam = _tempEventDictionary; if (!string.IsNullOrEmpty(param1Key) && param1 != null) userParam.Add(param1Key, param1); if (!string.IsNullOrEmpty(param2Key) && param2 != null) userParam.Add(param2Key, param2); if (!string.IsNullOrEmpty(param3Key) && param3 != null) userParam.Add(param3Key, param3); if (!string.IsNullOrEmpty(param4Key) && param4 != null) userParam.Add(param4Key, param4); if (_useQueueMode) { DebugUtil.Log("缓存发送事件: {0}, 事件参数: {1}", eventName, JsonConvert.SerializeObject(userParam)); EnqueueEvent(eventName, userParam, localTime, serverTime); } else { MultiPlatformBIService.TrackEvent(eventName, userParam); TrackEventPhxh(eventName, userParam, localTime, serverTime); } } catch (Exception e) { DebugUtil.LogError("Track event fail! EventName:" + eventName + ",\n error msg: " + e.Message); } } /// /// 上报自定义事件 /// public void TrackEventWithRecycleDic(string eventName, RecycleDictionary recycleDictionary) { if (!_isInit) return; try { var localTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); var serverTime = _serverTimeProvider?.GetServerTime() ?? 0; _tempEventDictionary.Clear(); var userParam = _tempEventDictionary; foreach (var item in recycleDictionary.dict) { if (item.Value != null) { userParam.Add(item.Key, item.Value); } } DictionaryPool.ReturnDictToPool(recycleDictionary); if (_useQueueMode) { EnqueueEvent(eventName, userParam, localTime, serverTime); } else { MultiPlatformBIService.TrackEvent(eventName, userParam); TrackEventPhxh(eventName, userParam, localTime, serverTime); } } catch (Exception e) { DebugUtil.LogError("Track event fail! EventName:" + eventName + ",\n error msg: " + e.Message); } } /// /// 上报一次性自定义事件 /// public void TrackEventOnce(string eventName, string param1Key = null, object param1 = null, string param2Key = null, object param2 = null, string param3Key = null, object param3 = null) { if (IsRecorded(eventName)) { DebugUtil.Log("已经上报过 {0} 关卡事件,不会再次上报", eventName); return; } _storageEvent.EventList.Add(eventName); StorageMgr.Instance.SyncForce = true; TrackEvent(eventName, param1Key, param1, param2Key, param2, param3Key, param3); } #endregion #region PHXH 服务器打点 private void TrackEventPhxh(string eventName, Dictionary param, long localTime, ulong serverTime) { if (!_isInit) return; // 直接调用通用服务,它会从 PhxhBIInfoProvider 获取所有信息 PhxhBIService.TrackEvent(eventName, param, localTime, serverTime); } #endregion #region 网络队列对象池 /// /// 字典对象池大小 /// private const int DICT_POOL_SIZE = 32; /// /// 对象池 /// private List> _dictPool; /// /// 对象池索引 /// private int _dictPoolIndex = 0; /// /// 事件队列最大容量 /// private const int MAX_QUEUE_SIZE = 1000; /// /// 初始化字典对象池 /// private void InitDictPool() { _dictPool = new List>(DICT_POOL_SIZE); for (var i = 0; i < DICT_POOL_SIZE; i++) { _dictPool.Add(new Dictionary(16)); } } /// /// 从对象池获取一个字典 /// private Dictionary GetDictFromPool() { if (_dictPool == null || _dictPool.Count == 0) { InitDictPool(); } var dict = _dictPool[_dictPoolIndex]; dict.Clear(); _dictPoolIndex = (_dictPoolIndex + 1) % DICT_POOL_SIZE; return dict; } #endregion #region 外部打点字典对象池 /// /// 对象池字典,请使用BIManager内部的PublicDictionaryPool获取对象 /// public class RecycleDictionary { public Dictionary dict; public RecycleDictionary() { dict = new Dictionary(); } public void Add(string key, object value) { if (dict.ContainsKey(key)) { dict[key] = value; } else { dict.Add(key, value); } } public void Clear() { dict.Clear(); } } /// /// 外部可访问的字典对象池类,用于打点事件传入参数 /// public class PublicDictionaryPool { private const int DICT_POOL_SIZE = 16; // 池大小可根据实际需求调整 private List _dictPool; private int _dictPoolIndex = 0; public PublicDictionaryPool() { InitDictPool(); } private void InitDictPool() { _dictPool = new List(DICT_POOL_SIZE); for (int i = 0; i < DICT_POOL_SIZE; i++) { _dictPool.Add(new RecycleDictionary()); } _dictPoolIndex = 0; } public RecycleDictionary GetDictFromPool() { if (_dictPoolIndex >= DICT_POOL_SIZE) { // 池已用完,临时分配一个新的字典 // TODO 也可以扩容池 return new RecycleDictionary(); } var dict = _dictPool[_dictPoolIndex]; _dictPoolIndex++; dict.Clear(); return dict; } public void ReturnDictToPool(RecycleDictionary dict) { if (_dictPoolIndex == 0) return; _dictPoolIndex--; _dictPool[_dictPoolIndex] = dict; } } #endregion }