281 lines
9.8 KiB
C#
281 lines
9.8 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using DataEyeAnalytics;
|
||
using Newtonsoft.Json;
|
||
using UnityEngine;
|
||
|
||
namespace BallKingdomCrush
|
||
{
|
||
public class BIManager : MonoBehaviour
|
||
{
|
||
private static BIManager _instance;
|
||
private static readonly object _initLock = new object();
|
||
|
||
// 状态标志
|
||
private bool _initialized = false; // 仅在初始化成功后为 true
|
||
private bool _initializing = false; // 防止并发多次尝试初始化
|
||
|
||
|
||
private string ntpServer = "time.windows.com";
|
||
private string defaultChannel = "gp";
|
||
private int maxInitRetries = 6;
|
||
private float retryIntervalSeconds = 0.5f;
|
||
|
||
public static BIManager Instance
|
||
{
|
||
get
|
||
{
|
||
if (_instance == null)
|
||
{
|
||
var go = new GameObject("BIManager");
|
||
DontDestroyOnLoad(go);
|
||
_instance = go.AddComponent<BIManager>();
|
||
}
|
||
|
||
return _instance;
|
||
}
|
||
}
|
||
|
||
private void Awake()
|
||
{
|
||
// 单例保护:如果场景里已有实例,销毁重复的
|
||
if (_instance == null)
|
||
{
|
||
_instance = this;
|
||
DontDestroyOnLoad(gameObject);
|
||
}
|
||
else if (_instance != this)
|
||
{
|
||
Destroy(gameObject);
|
||
return;
|
||
}
|
||
}
|
||
|
||
private void Start()
|
||
{
|
||
// Start 调用 InitBI 是安全的:InitBI 本身是幂等且线程安全的
|
||
InitBI();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 对外统一初始化入口(幂等、线程安全)
|
||
/// </summary>
|
||
public void InitBI()
|
||
{
|
||
// 快速检查
|
||
if (_initialized) return;
|
||
|
||
lock (_initLock)
|
||
{
|
||
if (_initialized || _initializing)
|
||
{
|
||
// 已经初始化或正在初始化,直接返回
|
||
return;
|
||
}
|
||
|
||
_initializing = true;
|
||
}
|
||
|
||
// 使用协程执行初始化(必要时可以做重试)
|
||
StartCoroutine(DoInitCoroutine());
|
||
}
|
||
|
||
private IEnumerator DoInitCoroutine()
|
||
{
|
||
try
|
||
{
|
||
int attempt = 0;
|
||
Exception lastException = null;
|
||
|
||
while (attempt < maxInitRetries)
|
||
{
|
||
attempt++;
|
||
try
|
||
{
|
||
// 这里不主动调用 DataEyeAnalyticsAPI.Init(...) 假设你使用的是场景里的 DataEyeAnalytics 预制体,
|
||
// SDK 会在预制体 Awake/Start 里初始化。如果你的流程是手动 Init,请在这里调用 Init(appId)。
|
||
|
||
// 1) 校准时间(默认实例)
|
||
DataEyeAnalyticsAPI.CalibrateTimeWithNtp(ntpServer);
|
||
|
||
// 2) 设置超参数
|
||
var superProperties = new Dictionary<string, object>
|
||
{
|
||
{ "channel", defaultChannel }
|
||
};
|
||
DataEyeAnalyticsAPI.SetSuperProperties(superProperties);
|
||
|
||
// 3) 开启自动采集
|
||
DataEyeAnalyticsAPI.EnableAutoTrack(AUTO_TRACK_EVENTS.ALL);
|
||
|
||
// 如果上面没有抛异常,认为初始化成功
|
||
_initialized = true;
|
||
Debug.Log($"[BIManager] InitBI succeeded on attempt {attempt}");
|
||
yield break;
|
||
}
|
||
catch (ArgumentNullException ane)
|
||
{
|
||
// 典型:DataEye 内部 instance 还没创建(appId 为 null) -> retry
|
||
lastException = ane;
|
||
Debug.LogWarning(
|
||
$"[BIManager] DataEye not ready (attempt {attempt}), will retry. msg={ane.Message}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 其他异常,记录并决定是否重试
|
||
lastException = ex;
|
||
Debug.LogWarning($"[BIManager] InitBI attempt {attempt} threw: {ex.Message}");
|
||
}
|
||
|
||
// 等待后重试
|
||
yield return new WaitForSeconds(retryIntervalSeconds);
|
||
}
|
||
|
||
// 重试用尽仍失败 —— 打日志(不再抛异常以免影响游戏流程)
|
||
Debug.LogError("[BIManager] InitBI failed after retries. Last exception: " +
|
||
(lastException?.ToString() ?? "null"));
|
||
}
|
||
finally
|
||
{
|
||
lock (_initLock)
|
||
{
|
||
_initializing = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
// ================= 通用埋点 =================
|
||
public void TrackEvent(string eventName, Dictionary<string, object> properties = null)
|
||
{
|
||
// if (properties == null)
|
||
// properties = new Dictionary<string, object>();
|
||
//
|
||
// DataEyeAnalyticsAPI.Track(eventName, properties);
|
||
// DataEyeAnalyticsAPI.Flush();
|
||
//
|
||
// Debug.Log($"[BI] 事件上报: {eventName}, 属性={JsonConvert.SerializeObject(properties)}");
|
||
}
|
||
|
||
// ================= 预制事件封装 =================
|
||
|
||
/// <summary>
|
||
/// 上报广告事件
|
||
/// </summary>
|
||
/// <param name="eventName"></param>
|
||
/// <param name="adType">Native,RewardedVideo,Banner,Interstitial,Splash</param>
|
||
/// <param name="placementId">聚合广告位id</param>
|
||
/// <param name="networkFirmId">广告网络ID</param>
|
||
public void TrackAdEvent(string eventName, string adType, string placementId = null,
|
||
string networkFirmId = null, double revenue = 0, string scene = null, string currency = "USD")
|
||
{
|
||
var props = new Dictionary<string, object>
|
||
{
|
||
{ "ad_type", adType }
|
||
};
|
||
|
||
if (!string.IsNullOrEmpty(placementId))
|
||
props["PlacementId"] = placementId;
|
||
|
||
if (!string.IsNullOrEmpty(networkFirmId))
|
||
props["NetworkFirmId"] = networkFirmId;
|
||
|
||
if (revenue > 0)
|
||
{
|
||
props["Revenue"] = revenue;
|
||
props["Ecpm"] = revenue * 1000;
|
||
}
|
||
|
||
if (!string.IsNullOrEmpty(scene))
|
||
props["scene"] = scene;
|
||
|
||
if (!string.IsNullOrEmpty(currency))
|
||
props["Currency"] = currency;
|
||
|
||
|
||
TrackEvent(eventName, props);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 上报内购事件
|
||
/// </summary>
|
||
/// <param name="revenue">收入</param>
|
||
/// <param name="currency">币种</param>
|
||
/// <param name="payType">支付方式 0=商店支付,1=三方支付</param>
|
||
/// <param name="itemId">sku</param>
|
||
/// <param name="status">order:下单 paid:付费成功 paid_err:扣款失败</param>
|
||
/// <param name="itemName">商品名称</param>
|
||
/// <param name="msg">如果遇到异常或相关情况,将err_msg进行上报</param>
|
||
public void TrackPurchase(double revenue, string currency, string payType, string itemId,
|
||
string status, string itemName = null, string msg = null)
|
||
{
|
||
var props = new Dictionary<string, object>
|
||
{
|
||
{ "Revenue", revenue },
|
||
{ "Currency", currency },
|
||
{ "type", payType },
|
||
{ "item_id", itemId },
|
||
{ "purchase_status", status }
|
||
};
|
||
|
||
if (!string.IsNullOrEmpty(itemName))
|
||
props["item_name"] = itemName;
|
||
if (!string.IsNullOrEmpty(msg))
|
||
props["msg"] = msg;
|
||
|
||
TrackEvent(BIEvent.PURCHASE, props);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 上报页面浏览
|
||
/// </summary>
|
||
public void TrackPageView(string pageName)
|
||
{
|
||
var props = new Dictionary<string, object>
|
||
{
|
||
{ "page_name", pageName } // 例如 "loading"、"game"
|
||
};
|
||
|
||
TrackEvent(BIEvent.PAGE_VIEW, props);
|
||
}
|
||
|
||
/// <summary>
|
||
/// AB 分组埋点
|
||
/// </summary>
|
||
public void TrackABConfig(int responseTime)
|
||
{
|
||
var props = new Dictionary<string, object>
|
||
{
|
||
{ "response_time", responseTime }
|
||
};
|
||
|
||
TrackEvent(BIEvent.AB_CONFIG, props);
|
||
}
|
||
}
|
||
}
|
||
|
||
public static class BIEvent
|
||
{
|
||
// 自动采集(SDK 开启自动采集即可,不需要手动埋点)
|
||
public const string APP_INSTALL = "app_install";
|
||
public const string APP_START = "app_start";
|
||
public const string APP_END = "app_end";
|
||
public const string APP_VIEW = "app_view";
|
||
public const string APP_CRASH = "app_crash";
|
||
|
||
// 内部预制事件
|
||
public const string AD_REQUEST = "ad_request"; // 广告请求
|
||
public const string AD_INVENTORY = "ad_inventory"; // 广告填充的时候
|
||
public const string AD_IMP = "ad_imp"; // 广告展示
|
||
public const string AD_CLICK = "ad_click"; // 广告点击
|
||
|
||
public const string PURCHASE = "purchase"; // 内购
|
||
|
||
// 页面相关
|
||
public const string PAGE_VIEW = "page_view_customize";
|
||
|
||
// 其他业务事件(AB 测试等)
|
||
public const string AB_CONFIG = "config"; // AB
|
||
} |