com.guru.unity.sdk/Runtime/Code/SDK/GuruSDK.cs

763 lines
25 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

namespace Guru
{
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Debug = UnityEngine.Debug;
public partial class GuruSDK: MonoBehaviour
{
// SDK_VERSION
public const string Version = "1.1.0";
// Const
public const string Tag = "[Guru]";
public const string ServicesConfigKey = "guru_services";
private static GuruSDK _instance;
/// <summary>
/// 单利引用
/// </summary>
public static GuruSDK Instance
{
get
{
if(null == _instance)
{
_instance = CreateInstance();
}
return _instance;
}
}
private GuruSDKInitConfig _initConfig;
private Action<bool> _onCompleteCallback;
internal static GuruSDKInitConfig InitConfig => Instance._initConfig;
internal static GuruSDKModel Model => GuruSDKModel.Instance;
private static GuruServicesConfig _appServicesConfig;
private static GuruSettings _guruSettings;
private static GuruSettings GuruSettings
{
get
{
if (_guruSettings == null) _guruSettings = GuruSettings.Instance;
return _guruSettings;
}
}
private static DateTime _initTime;
private static bool _isDebugEnabled = false;
/// <summary>
/// Debug Mode
/// </summary>
public static bool IsDebugMode
{
get
{
#if UNITY_EDITOR || DEBUG
return true;
#endif
return _isDebugEnabled;
}
}
/// <summary>
/// 初始化成功标志位
/// </summary>
public static bool IsInitialSuccess { get; private set; } = false;
/// <summary>
/// Firebase 就绪标志位
/// </summary>
public static bool IsFirebaseReady { get; private set; } = false;
/// <summary>
/// 服务就绪标志位
/// </summary>
public static bool IsServiceReady { get; private set; } = false;
#region 初始化
private static GuruSDK CreateInstance()
{
var go = new GameObject(nameof(GuruSDK));
DontDestroyOnLoad(go);
_instance = go.AddComponent<GuruSDK>();
return _instance;
}
public static GuruSDKInitConfig BuildConfig(
bool useCustomConsent = false,
bool autoLoadAds = true,
bool iapEnabled = true,
bool autoRecordFinishedLevels = true,
bool debugMode = false,
bool isBuyNoAds = false,
string bannerColor = "#00000000",
Dictionary<string, object> defaultRemoteData = null,
byte[] googleKeys = null,
byte[] appleRootCerts = null)
{
var config = GuruSDKInitConfig.Build(useCustomConsent, autoLoadAds, iapEnabled,
autoRecordFinishedLevels, isBuyNoAds, bannerColor,
debugMode, defaultRemoteData, googleKeys, appleRootCerts);
return config;
}
public static void Init(Action<bool> onComplete)
{
Init(GuruSDKInitConfig.Build(), onComplete);
}
public static void Init(GuruSDKInitConfig config, Action<bool> onComplete)
{
_initTime = DateTime.Now.ToUniversalTime();
// ----- First Open Time -----
// SetFirstOpenTime(GetFirstOpenTime()); // FirstOpenTime
LogI($"#1 ---- Guru SDK [{Version}] ----\n{config.ToString()}");
Instance.StartWithConfig(config, onComplete);
}
private static string GetFirstOpenTime()
{
string firstOpenTime = IPMConfig.FirstOpenTime;
if (string.IsNullOrEmpty(firstOpenTime))
{
firstOpenTime = TimeUtil.GetCurrentTimeStamp().ToString();
IPMConfig.FirstOpenTime = firstOpenTime;
}
return firstOpenTime;
}
/// <summary>
/// 启动SDK
/// </summary>
/// <param name="config"></param>
/// <param name="onComplete"></param>
private void StartWithConfig(GuruSDKInitConfig config, Action<bool> onComplete)
{
Model.PropBLevel.OnValueChanged += OnBLevelChanged;
Model.PropBPlay.OnValueChanged += OnBPlayChanged;
IsInitialSuccess = false;
_initConfig = config;
_onCompleteCallback = onComplete;
_isDebugEnabled = config.DebugMode;
InitAssets();
}
private void InitAssets()
{
InitUpdaters(); // Updaters
InitThreadHandler(); // 初始化线程处理器
}
void Start()
{
//---- Start All tools ----
LogI($"#2 --- InitFirebase ---");
//---------- Start Firebase ------------
FirebaseUtil.onInitComplete += OnFirebaseReady;
FirebaseUtil.OnUserAuthResult += OnUserAuthResult;
FirebaseUtil.OnFirebaseAuthResult += OnFirebaseAuthResult;
FirebaseUtil.InitFirebase(null); // 确保所有的逻辑提前被调用到 + Analytics.Init TODO之后需要改为事件驱动
LogI($"#2.1 --- InitFacebook ---");
//---------- Start Facebook ------------
FBService.Instance.StartService();
LogI($"#2.2 --- Call SDK init complete -> callback: { (_onCompleteCallback == null ? "Null" : _onCompleteCallback.ToString()) } ---");
IsInitialSuccess = true;
_onCompleteCallback?.Invoke(true);
}
private void OnUserAuthResult(bool success)
{
if (success && string.IsNullOrEmpty(IPMConfig.IPM_UID))
{
success = false;
}
Callbacks.SDK._onUserAuthResult?.Invoke(success);
if (success)
{
Model.UserId = IPMConfig.IPM_UID;
if (GuruIAP.Instance != null)
{
GuruIAP.Instance.SetUID(UID);
GuruIAP.Instance.SetUUID(UUID);
}
UpdateAllUserProperties(); // 同步所有用户属性打点
}
}
private void OnFirebaseAuthResult(bool success)
{
Callbacks.SDK._onFirebaseAuthResult?.Invoke(success);
}
/// <summary>
/// 开始各种组件初始化
/// </summary>
private void OnFirebaseReady(bool success)
{
FirebaseUtil.onInitComplete -= OnFirebaseReady;
LogI($"#3 --- On FirebaseDeps: {success} ---");
IsFirebaseReady = success;
Callbacks.SDK._onFirebaseReady?.Invoke(success);
// LogFirebaseDeps(success);
LogI($"#3.5 --- Call InitRemoteConfig ---");
// 开始Remote Manager初始化
RemoteConfigManager.Init(BuildDefaultRemoteData(_initConfig.DefaultRemoteData));
RemoteConfigManager.OnFetchCompleted += OnFetchRemoteCallback;
LogI($"#4 --- Apply remote services config ---");
// 根据缓存的云控配置来初始化参数
InitAllServices();
LogI($"#5 --- sync sdk time ---");
var sp = DateTime.Now.ToUniversalTime() - _initTime;
LogSDKInitTime(sp.TotalSeconds);
// 上报所有初始化用户属性
UpdateAllUserProperties();
}
/// <summary>
/// 注入云控参数基础数据
/// </summary>
/// <param name="dict"></param>
/// <returns></returns>
private Dictionary<string, object> BuildDefaultRemoteData(Dictionary<string, object> dict)
{
if (dict == null) dict = new Dictionary<string, object>(3);
// 注入默认的 Services 配置值
string json = Model.LoadDefaltServicesConfigJson();
if (!string.IsNullOrEmpty(json)) dict[ServicesConfigKey] = json;
return dict;
}
/// <summary>
/// 拉取云控参数完成
/// </summary>
/// <param name="success"></param>
private void OnFetchRemoteCallback(bool success)
{
LogI($"#6 --- Remote fetch complete: {success} ---");
ABTestManager.Init(); // 启动AB测试解析器
Callbacks.Remote._onRemoteFetchComplete?.Invoke(success);
}
private void Update()
{
UpdateAllUpdates(); // 驱动所有的更新器
}
#endregion
#region App Remote Update
/// <summary>
/// Apply Cloud guru-service configs for sdk assets
/// </summary>
private void InitAllServices()
{
// -------- Init Analytics ---------
InitUserProperties();
SetSDKEventPriority();
// -------- Init Noti -----------
InitNotiPermission();
bool useKeywords = false;
bool useIAP = _initConfig.IAPEnabled;
bool appleReview = false;
bool enableAnaErrorLog = false;
//----------- Set GuruServices ----------------
var services = GetRemoteServicesConfig();
if (services != null)
{
_appServicesConfig = services;
useKeywords = _appServicesConfig.KeywordsEnabled();
// 使用初始化配置中的 IAPEnable来联合限定, 如果本地写死关闭则不走云控开启
useIAP = _initConfig.IAPEnabled && _appServicesConfig.IsIAPEnabled();
enableAnaErrorLog = _appServicesConfig.EnableAnaErrorLog();
Try(() =>
{
LogI($"#4.1 --- Start apply services ---");
//----------------------------------------------------------------
// 自打点设置错误上报
if(enableAnaErrorLog) GuruAnalytics.EnableErrorLog = true;
// adjust 事件设置
if (null != _appServicesConfig.adjust_settings && null != GuruSettings)
{
// 更新 Adjust Tokens
GuruSettings.UpdateAdjustTokens(
_appServicesConfig.adjust_settings.AndroidToken(),
_appServicesConfig.adjust_settings.iOSToken());
// 更新 Adjust Events
GuruSettings.UpdateAdjustEvents(_appServicesConfig.adjust_settings.events);
}
LogI($"#4.2 --- Start GuruSttings ---");
// GuruSettings 设置
if (null != _appServicesConfig.app_settings)
{
if (_appServicesConfig.Tch02Value() > 0)
{
Analytics.EnableTch02Event = true;
Analytics.SetTch02TargetValue(_appServicesConfig.Tch02Value());
}
// 设置获取设备 UUID 的方法
if (_appServicesConfig.UseUUID())
{
IPMConfig.UsingUUID = true; // 开始使用 UUID 作为 DeviceID 标识
}
#if UNITY_IOS
// 苹果审核标志位
appleReview = _appServicesConfig.IsAppReview();
#endif
if (null != GuruSettings)
{
// 更新和升级 GuruSettings 对应的值
GuruSettings.UpdateAppSettings(
_appServicesConfig.app_settings.bundle_id,
_appServicesConfig.fb_settings?.fb_app_id ?? "",
_appServicesConfig.app_settings.support_email,
_appServicesConfig.app_settings.privacy_url,
_appServicesConfig.app_settings.terms_url,
_appServicesConfig.app_settings.android_store,
_appServicesConfig.app_settings.ios_store,
_appServicesConfig.parameters?.using_uuid ?? false,
_appServicesConfig.parameters?.cdn_host ?? "");
_appBundleId = _appServicesConfig.app_settings.bundle_id; // 配置预设的 BundleId
}
}
//---------------------------------
}, ex =>
{
UnityEngine.Debug.LogError($"--- ERROR on apply services: {ex.Message}");
});
}
//----------- Set IAP ----------------
if (useIAP)
{
// InitIAP(_initConfig.GoogleKeys, _initConfig.AppleRootCerts); // 初始化IAP
Try(() =>
{
LogI($"#4.3 --- Start IAP ---");
if (_initConfig.GoogleKeys == null || _initConfig.AppleRootCerts == null)
{
LogException("[IAP] GoogleKeys is null when using IAPService! Integration failed. App will Exit");
}
InitIAP(UID, _initConfig.GoogleKeys, _initConfig.AppleRootCerts); // 初始化IAP
}, ex =>
{
UnityEngine.Debug.LogError($"--- ERROR on useIAP: {ex.Message}");
});
}
//----------- Set Keywords ----------------
if (useKeywords)
{
// KeywordsManager.Install(Model.IsIAPUser, Model.SuccessLevelId); // 启动Keyword管理器
Try(() =>
{
LogI($"#4.4 --- Start Keywords ---");
KeywordsManager.Install(Model.IsIapUser, Model.SuccessLevelId); // 启动Keyword管理器
}, ex =>
{
UnityEngine.Debug.LogError($"--- ERROR on Keywords: {ex.Message}");
});
}
#if UNITY_IOS
if (appleReview)
{
// StartAppleReviewFlow(); // 直接显示 ATT 弹窗, 跳过 Consent 流程
Try(() =>
{
LogI($"#4.5.0 --- StartAppleReviewFlow ---");
StartAppleReviewFlow(); // 直接显示 ATT 弹窗, 跳过 Consent 流程
}, ex =>
{
Debug.LogError($"--- ERROR on StartAppleReviewFlow: {ex.Message}");
});
return;
}
#endif
//----------- Set Consent ----------------
if (!InitConfig.UseCustomConsent && !appleReview)
{
// LogI($"--- #3 Start Consent Flow ---");
// StartConsentFlow();
Try(() =>
{
StartConsentFlow();
}, ex =>
{
UnityEngine.Debug.LogError($"--- ERROR on StartConsentFlow: {ex.Message}");
});
}
IsServiceReady = true;
// 中台服务初始化结束
Callbacks.SDK._onGuruServiceReady?.Invoke();
#if UNITY_ANDROID
// Android 命令行调试
StartAndroidDebugCmds();
#endif
}
/// <summary>
/// Get the guru-service cloud config value;
/// User can fetch the cloud guru-service config by using Custom Service Key
/// </summary>
/// <returns></returns>
private GuruServicesConfig GetRemoteServicesConfig()
{
string defaultJson = GetRemoteString(ServicesConfigKey);
bool useCustomKey = false;
string key = ServicesConfigKey;
if (!string.IsNullOrEmpty(_initConfig.CustomServiceKey))
{
key = _initConfig.CustomServiceKey;
useCustomKey = true;
}
var json = GetRemoteString(key); // Cloud cached data
if (string.IsNullOrEmpty(json) && useCustomKey && !string.IsNullOrEmpty(defaultJson))
{
// No remote data fetched from cloud, should use default values
json = defaultJson;
UnityEngine.Debug.Log($"{Tag} --- No remote data found with: {key} -> Using default key {ServicesConfigKey} and local data!!!");
}
if (!string.IsNullOrEmpty(json))
{
return JsonParser.ToObject<GuruServicesConfig>(json);
}
return null;
}
private void Try(Action method, Action<Exception> onException = null, Action onFinal = null)
{
if (method == null) return;
try
{
method.Invoke();
}
catch (Exception ex)
{
LogException(ex);
// ignored
onException?.Invoke(ex);
}
finally
{
// Finally
onFinal?.Invoke();
}
}
#endregion
#region Apple 审核流程逻辑
#if UNITY_IOS
private void StartAppleReviewFlow()
{
CheckAttStatus();
}
#endif
#endregion
#region 数据
private void OnBLevelChanged(int blevel)
{
SetUserBLevel(blevel);
}
private void OnBPlayChanged(int bplay)
{
SetUserBPlay(bplay);
}
#endregion
#region Logging
internal static void LogI(object message)
{
UnityEngine.Debug.Log($"{Tag} {message}");
}
internal static void LogW(object message)
{
UnityEngine.Debug.LogWarning($"{Tag} {message}");
}
internal static void LogE(object message)
{
UnityEngine.Debug.LogError($"{Tag} {message}");
}
internal static void LogException(string message)
{
LogException( new Exception($"{Tag} {message}"));
}
internal static void LogException(Exception e)
{
UnityEngine.Debug.LogException(e);
}
/// <summary>
/// 上报崩溃信息
/// </summary>
/// <param name="message"></param>
public static void Report(string message)
{
Analytics.LogCrashlytics(message, false);
}
/// <summary>
/// 上报异常
/// </summary>
/// <param name="message"></param>
public static void ReportException(string message)
{
Analytics.LogCrashlytics(message);
}
/// <summary>
/// 上报异常 Exception
/// </summary>
/// <param name="ex"></param>
public static void ReportException(Exception ex)
{
Analytics.LogCrashlytics(ex);
}
#endregion
#region 生命周期
/// <summary>
/// 暂停时处理
/// </summary>
/// <param name="paused"></param>
private void OnAppPauseHandler(bool paused)
{
if(paused) Model.Save(true); // 强制保存数据
Callbacks.App._onAppPaused?.Invoke(paused);
}
private void OnApplicationPause(bool paused)
{
OnAppPauseHandler(paused);
}
private void OnApplicationFocus(bool hasFocus)
{
OnAppPauseHandler(!hasFocus);
}
private void OnApplicationQuit()
{
Model.Save(true);
Callbacks.App._onAppQuit?.Invoke();
}
#endregion
#region 延迟处理
/// <summary>
/// 启动协程
/// </summary>
/// <param name="enumerator"></param>
/// <returns></returns>
public static Coroutine DoCoroutine(IEnumerator enumerator)
{
return Instance != null ? Instance.StartCoroutine(enumerator) : null;
}
public static void KillCoroutine(Coroutine coroutine)
{
if(coroutine != null)
Instance.StopCoroutine(coroutine);
}
/// <summary>
/// 延时执行
/// </summary>
/// <param name="seconds"></param>
/// <param name="callback"></param>
public static Coroutine Delay(float seconds, Action callback)
{
return DoCoroutine(Instance.OnDelayCall(seconds, callback));
}
private IEnumerator OnDelayCall(float delay, Action callback)
{
if (delay > 0)
{
yield return new WaitForSeconds(delay);
}
else
{
yield return null;
}
callback?.Invoke();
}
#endregion
#region 帧更新 Updater
private List<IUpdater> _updaterRunningList;
private List<IUpdater> _updaterRemoveList;
private void InitUpdaters()
{
_updaterRunningList = new List<IUpdater>(20);
_updaterRemoveList = new List<IUpdater>(20);
}
private void UpdateAllUpdates()
{
int i = 0;
IUpdater updater;
// ---- Updater Trigger ----
if (_updaterRunningList.Count > 0)
{
i = 0;
while (i < _updaterRunningList.Count)
{
updater = _updaterRunningList[i];
if (updater != null)
{
if (updater.State == UpdaterState.Running)
{
updater.OnUpdate();
}
else if(updater.State == UpdaterState.Kill)
{
_updaterRemoveList.Add(updater);
}
}
else
{
_updaterRunningList.RemoveAt(i);
i--;
}
i++;
}
}
if (_updaterRemoveList.Count > 0)
{
i = 0;
while (i < _updaterRemoveList.Count)
{
RemoveUpdater(_updaterRemoveList[i]);
i++;
}
_updaterRemoveList.Clear();
}
}
/// <summary>
/// 注册帧更新器
/// </summary>
/// <param name="updater"></param>
public static void RegisterUpdater(IUpdater updater)
{
Instance.AddUpdater(updater);
updater.Start();
}
private void AddUpdater(IUpdater updater)
{
if (_updaterRunningList == null) _updaterRunningList = new List<IUpdater>(20);
_updaterRunningList.Add(updater);
}
private void RemoveUpdater(IUpdater updater)
{
if (_updaterRunningList != null && updater != null)
{
_updaterRunningList.Remove(updater);
}
}
#endregion
#region 中台推送管理
private static int _messageRetry = 0;
public static void SetPushNotificationEnabled(bool enabled)
{
DeviceInfoUploadRequest request = new DeviceInfoUploadRequest()
.SetRetryTimes(1)
.SetSuccessCallBack(() =>
{
_messageRetry = 0;
Debug.Log($"[SDK] --- Set Push Enabled: {enabled} success");
})
.SetFailCallBack(() =>
{
double retryDelay = Math.Pow(2, _messageRetry);
_messageRetry++;
CoroutineHelper.Instance.StartDelayed((float)retryDelay, ()=> SetPushNotificationEnabled(enabled));
}) as DeviceInfoUploadRequest;
if (request != null)
{
request.SetPushEnabled(enabled);
request.Send();
}
}
#endregion
}
}