Compare commits

..

5 Commits

Author SHA1 Message Date
胡宇飞 8ee9b8e9cf fix: 修改androidx.room:room-runtime 的 Deps 版本, 目前先适配 API 33
Signed-off-by: huyufei <yufei.hu@castbox.fm>
2024-07-24 17:19:56 +08:00
胡宇飞 548959dd34 update: 修改androidx.work:work-runtime 的 Deps 版本, 目前先适配 API 33
Signed-off-by: huyufei <yufei.hu@castbox.fm>
2024-07-24 16:55:56 +08:00
胡宇飞 9de443249d fix: 优化实验策略,数据读取逻辑
--story=1021087 --user=yufei.hu 【中台】【自打点】升级自打点原生库至 1.1.1,并在 Water1 中落地验证 https://www.tapd.cn/33527076/s/1159023

Signed-off-by: huyufei <yufei.hu@castbox.fm>
2024-07-24 13:26:28 +08:00
胡宇飞 6dd2574763 update: 更新打包日志,完善云控参数读取, 重构本地测试逻辑, 规范日志
Signed-off-by: huyufei <yufei.hu@castbox.fm>
2024-07-24 10:35:27 +08:00
胡宇飞 bd39fb09f1 update: 升级自打点库Native至1.1.1, 内置 AB 测试逻辑
--story=1021087 --user=yufei.hu 【中台】【自打点】升级自打点原生库至 1.1.1,并在 Water1 中落地验证 https://www.tapd.cn/33527076/s/1158888

Signed-off-by: huyufei <yufei.hu@castbox.fm>
2024-07-23 20:04:47 +08:00
63 changed files with 747 additions and 1176 deletions

View File

@ -1,8 +1,8 @@
{ {
"name": "Guru.Editor", "name": "Guru.Editor",
"rootNamespace": "",
"references": [ "references": [
"Guru.Runtime", "Guru.Runtime"
"Guru.Notification"
], ],
"includePlatforms": [ "includePlatforms": [
"Editor" "Editor"

View File

@ -41,8 +41,8 @@ namespace Guru.Editor
// Firebase 10.20.0 fixed to 10.22.0. BUT higher version do not open this ATTRIBUTE !!
// [PostProcessBuild(47)] // MAX POD Process Order [PostProcessBuild(47)] // MAX POD Process Order
public static void PostBuildFixPodDeps(BuildTarget target, string projPath) public static void PostBuildFixPodDeps(BuildTarget target, string projPath)
{ {
if (target != BuildTarget.iOS) return; if (target != BuildTarget.iOS) return;

View File

@ -11,7 +11,7 @@ namespace Guru
/// </summary> /// </summary>
public class IOSPostBuildSwift public class IOSPostBuildSwift
{ {
[PostProcessBuild(2000)] [PostProcessBuild(40)]
public static void OnPostProcessBuild(BuildTarget target, string buildPath) public static void OnPostProcessBuild(BuildTarget target, string buildPath)
{ {
if (target != BuildTarget.iOS) return; if (target != BuildTarget.iOS) return;
@ -43,7 +43,7 @@ namespace Guru
// 设置主项目的SWIFT构建支持 // 设置主项目的SWIFT构建支持
project.SetBuildProperty(mainTargetGuid, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES", "YES"); project.SetBuildProperty(mainTargetGuid, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES", "YES");
project.SetBuildProperty(frameworkTargetGuid, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES", "NO");
project.WriteToFile(projectPath); project.WriteToFile(projectPath);
} }

View File

@ -6,7 +6,6 @@
"MaxSdk", "MaxSdk",
"MaxSdk.Scripts", "MaxSdk.Scripts",
"Amazon", "Amazon",
"Amazon.Scripts",
"OpenWrapSDK", "OpenWrapSDK",
"UniWebView-CSharp", "UniWebView-CSharp",
"UnityEngine.Purchasing", "UnityEngine.Purchasing",
@ -18,9 +17,7 @@
"Google.Play.Review", "Google.Play.Review",
"Google.Play.Common", "Google.Play.Common",
"Guru.LitJson", "Guru.LitJson",
"Unity.Advertisement.IosSupport", "Unity.Advertisement.IosSupport"
"Unity.Notifications.Android",
"Unity.Notifications.iOS"
], ],
"includePlatforms": [], "includePlatforms": [],
"excludePlatforms": [], "excludePlatforms": [],

View File

@ -1,14 +1,11 @@
namespace Guru namespace Guru
{ {
using UnityEngine; using UnityEngine;
using com.adjust.sdk; using com.adjust.sdk;
using System;
using System.Collections; using System.Collections;
public class AdjustService public static class AdjustService
{ {
public const string Version = "1.6.1"; public const string Version = "1.6.1";
public const string AdjustVersion = "4.38.0"; // Adjust SDK Version public const string AdjustVersion = "4.38.0"; // Adjust SDK Version
@ -17,8 +14,6 @@ namespace Guru
public const string K_IAP_PURCHASE = "iap_purchase"; // 固定点位事件 public const string K_IAP_PURCHASE = "iap_purchase"; // 固定点位事件
public const string K_SUB_PURCHASE = "sub_purchase"; // 固定点位事件 public const string K_SUB_PURCHASE = "sub_purchase"; // 固定点位事件
private static Action<string> _onSessionSuccessCallback;
private static string _adId = ""; private static string _adId = "";
@ -50,7 +45,7 @@ namespace Guru
/// </summary> /// </summary>
/// <param name="appToken"></param> /// <param name="appToken"></param>
/// <param name="fbAppId">MIR 追踪 AppID</param> /// <param name="fbAppId">MIR 追踪 AppID</param>
public static void StartService(string appToken, string fbAppId = "", Action<string> onSessionSuccess = null) public static void StartService(string appToken, string fbAppId = "")
{ {
if (string.IsNullOrEmpty(appToken)) if (string.IsNullOrEmpty(appToken))
{ {
@ -58,8 +53,6 @@ namespace Guru
return; return;
} }
_onSessionSuccessCallback = onSessionSuccess;
InstallEvent(IPMConfig.FIREBASE_ID, IPMConfig.IPM_DEVICE_ID); // 注入启动参数 InstallEvent(IPMConfig.FIREBASE_ID, IPMConfig.IPM_DEVICE_ID); // 注入启动参数
AdjustEnvironment environment = GetAdjustEnvironment(); AdjustEnvironment environment = GetAdjustEnvironment();
@ -68,7 +61,6 @@ namespace Guru
config.setDelayStart(DelayTime); config.setDelayStart(DelayTime);
config.setPreinstallTrackingEnabled(true); // Adjust Preinstall config.setPreinstallTrackingEnabled(true); // Adjust Preinstall
config.setSessionSuccessDelegate(OnSessionSuccessCallback); // SessionSuccess
#if UNITY_ANDROID #if UNITY_ANDROID
if (!string.IsNullOrEmpty(fbAppId)) config.setFbAppId(fbAppId); // 注入 MIR ID if (!string.IsNullOrEmpty(fbAppId)) config.setFbAppId(fbAppId); // 注入 MIR ID
@ -79,7 +71,7 @@ namespace Guru
config.setLogDelegate(log => LogI(LOG_TAG, log)); config.setLogDelegate(log => LogI(LOG_TAG, log));
config.setEventSuccessDelegate(OnEventSuccessCallback); config.setEventSuccessDelegate(OnEventSuccessCallback);
config.setEventFailureDelegate(OnEventFailureCallback); config.setEventFailureDelegate(OnEventFailureCallback);
config.setSessionSuccessDelegate(OnSessionSuccessCallback);
config.setSessionFailureDelegate(OnSessionFailureCallback); config.setSessionFailureDelegate(OnSessionFailureCallback);
config.setAttributionChangedDelegate(OnAttributionChangedCallback); config.setAttributionChangedDelegate(OnAttributionChangedCallback);
#endif #endif
@ -280,10 +272,27 @@ namespace Guru
private static void OnSessionSuccessCallback(AdjustSessionSuccess sessionSuccessData) private static void OnSessionSuccessCallback(AdjustSessionSuccess sessionSuccessData)
{ {
LogI(LOG_TAG,$"{LOG_TAG} --- Session tracked successfully!"); LogI(LOG_TAG,"Session tracked successfully!");
var adid = sessionSuccessData.Adid; if (sessionSuccessData.Message != null)
_onSessionSuccessCallback?.Invoke(adid); {
LogI(LOG_TAG,"Message: " + sessionSuccessData.Message);
}
if (sessionSuccessData.Timestamp != null)
{
LogI(LOG_TAG,"Timestamp: " + sessionSuccessData.Timestamp);
}
if (sessionSuccessData.Adid != null)
{
LogI(LOG_TAG, "Adid: " + sessionSuccessData.Adid);
}
if (sessionSuccessData.JsonResponse != null)
{
LogI(LOG_TAG, "JsonResponse: " + sessionSuccessData.GetJsonResponse());
}
} }
private static void OnSessionFailureCallback(AdjustSessionFailure sessionFailureData) private static void OnSessionFailureCallback(AdjustSessionFailure sessionFailureData)

View File

@ -79,7 +79,7 @@ namespace Guru
/// <summary> /// <summary>
/// 初始化平台 /// 初始化平台
/// </summary> /// </summary>
public void Initialize(bool isDebug = false) public void Initialize()
{ {
#if UNITY_EDITOR #if UNITY_EDITOR
Debug.Log($"<color=orange>=== Amazon will not init on Editor ===</color>"); Debug.Log($"<color=orange>=== Amazon will not init on Editor ===</color>");
@ -93,9 +93,11 @@ namespace Guru
// 初始化Amazon // 初始化Amazon
Amazon.Initialize (AmazonAppID); Amazon.Initialize (AmazonAppID);
Amazon.SetAdNetworkInfo(new AdNetworkInfo(DTBAdNetwork.MAX)); Amazon.SetAdNetworkInfo(new AdNetworkInfo(DTBAdNetwork.MAX));
Debug.Log($"[Ads] --- Amazon init start isDebug:{isDebug}, AmazonID:{AmazonAppID}"); #if UNITY_EDITOR || DEBUG
Amazon.EnableTesting (isDebug); // Make sure to take this off when going live. Amazon.EnableTesting (true); // Make sure to take this off when going live.
Amazon.EnableLogging (isDebug); #else
Amazon.EnableLogging (false);
#endif
#if UNITY_IOS #if UNITY_IOS
Amazon.SetAPSPublisherExtendedIdFeatureEnabled(true); Amazon.SetAPSPublisherExtendedIdFeatureEnabled(true);

View File

@ -50,7 +50,7 @@ namespace Guru
* before it can request an ad using OpenWrap SDK. * before it can request an ad using OpenWrap SDK.
* The storeURL is the URL where users can download your app from the App Store/Google Play Store. * The storeURL is the URL where users can download your app from the App Store/Google Play Store.
*/ */
public void Initialize(bool isDebug = false) public void Initialize()
{ {
#if UNITY_EDITOR #if UNITY_EDITOR
Debug.Log($"<color=orange>=== PubMatic will not init on Editor ===</color>"); Debug.Log($"<color=orange>=== PubMatic will not init on Editor ===</color>");

View File

@ -8,18 +8,20 @@ Sample Dependencies.xml:
<androidPackage spec="androidx.core:core:1.7.0" /> <androidPackage spec="androidx.core:core:1.7.0" />
<!-- <androidPackage spec="androidx.appcompat:appcompat:1.5.1" />--> <!-- <androidPackage spec="androidx.appcompat:appcompat:1.5.1" />-->
<androidPackage spec="androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" /> <androidPackage spec="androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" />
<androidPackage spec="androidx.work:work-runtime:2.7.1" /> <androidPackage spec="androidx.work:work-runtime:2.8.1" />
<androidPackage spec="androidx.work:work-runtime-ktx:2.7.1" /> <androidPackage spec="androidx.work:work-runtime-ktx:2.8.1" />
<androidPackage spec="androidx.work:work-rxjava2:2.7.1" /> <androidPackage spec="androidx.work:work-rxjava2:2.8.1" />
<androidPackage spec="androidx.lifecycle:lifecycle-process:2.4.0" /> <androidPackage spec="androidx.lifecycle:lifecycle-process:2.4.0" />
<androidPackage spec="com.jakewharton.timber:timber:4.7.1" /> <androidPackage spec="com.jakewharton.timber:timber:4.7.1" />
<androidPackage spec="com.google.code.gson:gson:2.8.5" /> <androidPackage spec="com.google.code.gson:gson:2.8.5" />
<androidPackage spec="androidx.room:room-runtime:2.4.3" /> <androidPackage spec="androidx.room:room-runtime:2.5.1" />
<androidPackage spec="androidx.room:room-rxjava2:2.4.3" /> <androidPackage spec="androidx.room:room-rxjava2:2.5.1" />
<androidPackage spec="com.squareup.retrofit2:retrofit:2.7.1" /> <androidPackage spec="com.squareup.retrofit2:retrofit:2.7.1" />
<androidPackage spec="com.squareup.retrofit2:converter-gson:2.7.1" /> <androidPackage spec="com.squareup.retrofit2:converter-gson:2.7.1" />
<androidPackage spec="com.squareup.retrofit2:adapter-rxjava2:2.7.1" /> <androidPackage spec="com.squareup.retrofit2:adapter-rxjava2:2.7.1" />
<androidPackage spec="com.squareup.okhttp3:okhttp:4.9.3" /> <androidPackage spec="com.squareup.okhttp3:okhttp:4.12.0" />
<androidPackage spec="com.squareup.okhttp3:okhttp-dnsoverhttps:4.12.0" />
<androidPackage spec="com.google.net.cronet:cronet-okhttp:0.1.0" />
<!-- <androidPackage spec="com.mapzen:on-the-road:0.8.1" />--> <!-- <androidPackage spec="com.mapzen:on-the-road:0.8.1" />-->
<!-- <androidPackage spec="com.squareup.retrofit2:retrofit:2.7.1" />--> <!-- <androidPackage spec="com.squareup.retrofit2:retrofit:2.7.1" />-->
</androidPackages> </androidPackages>

View File

@ -7,29 +7,23 @@ GuruAnalyticsLib 的 Unity 插件库
- 插件库内的 .aar 通过 [guru_analytics](https://github.com/castbox/guru_analytics) 项目直接构建 ( 命令 `gradle publishToMavenLocal` ) - 插件库内的 .aar 通过 [guru_analytics](https://github.com/castbox/guru_analytics) 项目直接构建 ( 命令 `gradle publishToMavenLocal` )
- 构建后请改名为 `guru-analytics-{version}.aar` - 构建后请改名为 `guru-analytics-{version}.aar`
- 请将 .aar 文件放置于 `./Runtime/GuruAnalytics/Plugins/Android` 目录下 - 请将 .aar 文件放置于 `./Runtime/GuruAnalytics/Plugins/Android` 目录下
- **iOS** - **iOS**
- 插件库内的文件 通过 [GuruAnalytics_iOS](https://github.com/castbox/GuruAnalytics_iOS) 项目 - 插件库内的文件 通过 [GuruAnalytics_iOS](https://github.com/castbox/GuruAnalytics_iOS) 项目
- (1) 请将 repo 内的两个文件夹 `Assets``Classses` 拷贝至 `./Runtime/GuruAnalytics/Plugins/iOS/GuruAnalytics` 目录下: - (1) 请将 repo 内的两个文件夹 `Assets``Classses` 拷贝至 `./Runtime/GuruAnalytics/Plugins/iOS/GuruAnalytics` 目录下:
- (2) 请将部署到 Unity 内所有的 `.swift` 文件的 meta 属性内, 取消 iOS 文件属性. (因为打包时会按照 POD 导入) - (2) 请将部署到 Unity 内所有的 `.swift` 文件的 meta 属性内, 取消 iOS 文件属性. (因为打包时会按照 POD 导入)
- 注意及时更新 `GuruAnalyticsLib.podspec`文件内的更新内容 - 注意及时更新 `GuruAnalyticsLib.podspec`文件内的更新内容
```ruby
# 将 source 内的 git 属性删除, 只保留 tag 属性
# s.source = { :git => 'git@github.com:castbox/GuruAnalytics_iOS.git', :tag => s.version.to_s }
s.source = { :tag => s.version.to_s }
```
--- ---
## Change Logs ## Change Logs
### 1.12.0 ### 1.11.1
- Android 端对齐 `1.1.1` - Android 端对齐 `1.1.1`
> Hash: bdb41ae118dcf438e8efe4f27d0ec856bc3147b0 > Hash: 8791b946c441a081b80248aadbf65b515a8d2a10
- iOS 端对齐 `0.3.6` - iOS 端对齐 `0.3.6`
> Hash: 0cd5ce7aa64e12caa7413c938a3164687b973843 > Hash: 0cd5ce7aa64e12caa7413c938a3164687b973843
- Pod 库改为 本地文件引用 (配合外部发行项目) - Pod 库改为 本地文件引用 (配合外部发行项目)
### 1.11.0 ### 1.11.0
- Android 端对齐 `1.0.3` - Android 端对齐 `1.0.3`
> Hash: 1978686dbcba38b7b0421d8b6b2bef111356366b > Hash: 1978686dbcba38b7b0421d8b6b2bef111356366b

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f6d79ccb59bc4a5791121a696d6695b0
timeCreated: 1721722422

View File

@ -0,0 +1,242 @@
namespace Guru
{
using System;
using UnityEngine;
using Random = UnityEngine.Random;
using Firebase.RemoteConfig;
using System.Linq;
public class GuruAnalyticsExp
{
private const string Tag = "[SDK][ANU][EXP]";
private static bool IsDebug
{
get
{
#if UNITY_EDITOR || DEBUG
return true;
#endif
return false;
}
}
private static string SavedGuruAnalyticsExpGroupId
{
get => PlayerPrefs.GetString(nameof(SavedGuruAnalyticsExpGroupId), "");
set
{
PlayerPrefs.SetString(nameof(SavedGuruAnalyticsExpGroupId), value);
PlayerPrefs.Save();
}
}
/**
*
private const string JSON_GROUP_B =
"{\"cap\":\"firebase|facebook|guru\",\"init_delay_s\":10,\"experiment\":\"B\",\"guru_upload_ip_address\":[\"13.248.248.135\", \"3.33.195.44\"]}";
private const string JSON_GROUP_C =
"{\"cap\":\"firebase|facebook|guru\",\"init_delay_s\":10,\"experiment\":\"C\",\"guru_upload_ip_address\":[\"34.107.185.54\"],\"guru_event_url\":\"https://collect3.saas.castbox.fm\"}";
**/
/// <summary>
/// 解析 JSON 字符串
/// </summary>
/// <param name="json"></param>
/// <returns></returns>
private static GuruAnalyticsExpData Parse(string json)
{
if (string.IsNullOrEmpty(json)) return null;
return JsonParser.ToObject<GuruAnalyticsExpData>(json);
}
public const string KEY_GURU_ANALYTICS_EXP = "guru_analytics_exp";
/// <summary>
/// 默认的本地配置
/// </summary>
private static string DEFAULT_GURU_ANALYTICS_EXP = @"{
""enable"": true,
""exps"": [{
""groupId"": ""B"",
""baseUrl"": ""https://collect.saas.castbox.fm"",
""uploadIpAddress"": [""13.248.248.135"", ""3.33.195.44""]
}, {
""groupId"": ""C"",
""baseUrl"": ""https://collect3.saas.castbox.fm"",
""uploadIpAddress"": [""34.107.185.54""]
}]
}";
/// <summary>
/// 获取默认数据
/// </summary>
private static GuruAnalyticsExpData DefaultData => Parse(DEFAULT_GURU_ANALYTICS_EXP);
/// <summary>
/// 在当前版本中,随机获取线上配置的值
/// 若无法获取线上配置,则默认是 B 分组
/// </summary>
/// <param name="groupId"></param>
/// <param name="baseUrl"></param>
/// <param name="uploadIpAddress"></param>
/// <param name="isEnable"></param>
public static void GetGuruAnalyticsExpParams(out string groupId, out string baseUrl, out string[] uploadIpAddress, out bool isEnable)
{
groupId = "";
baseUrl = "";
uploadIpAddress = null;
isEnable = true;
string localGroup = "";
string remoteGroup = "";
GuruAnalyticsExpData expData = null;
GuruAnalyticsExpConfig config = null;
if(IsDebug) Debug.LogWarning($"{Tag} --- #0 Analytics EXP saved groupId :{SavedGuruAnalyticsExpGroupId}");
// 拉取云控数据
var json = "";
if(FirebaseUtil.IsFirebaseInitialized && FirebaseRemoteConfig.DefaultInstance.Keys.Contains(KEY_GURU_ANALYTICS_EXP))
json = FirebaseRemoteConfig.DefaultInstance.GetValue(GuruAnalyticsExp.KEY_GURU_ANALYTICS_EXP).StringValue;
if (string.IsNullOrEmpty(json))
{
// 没有云控值,走本地的数据配置,随机取值
if(IsDebug) Debug.LogWarning($"{Tag} --- #1 Analytics EXP json is Null -> using DefaultData");
config = GetDefaultGuruAnalyticsExpConfig();
}
else
{
if(IsDebug) Debug.LogWarning($"{Tag} --- #2 Analytics EXP Try to get remote json -> {json}");
expData = Parse(json);
if (expData == null)
{
if(IsDebug) Debug.LogWarning($"{Tag} --- #2.1 Analytics EXP Parse failed -> using DefaultData");
config = GetDefaultGuruAnalyticsExpConfig();
}
else
{
if (!expData.enable)
{
Debug.LogWarning($"{Tag} --- #2.2 Analytics EXP Disabled -> using DefaultData");
expData = DefaultData;
}
config = expData.GetFirstConfig();
}
}
if (config == null) {
config = DefaultData.GetFirstConfig(); // 默认是 B 组
if(IsDebug) Debug.LogWarning($"{Tag} --- #3 Try get config is Null -> using Default config");
}
if (config != null)
{
baseUrl = config.baseUrl;
groupId = config.groupId;
uploadIpAddress = config.uploadIpAddress;
SavedGuruAnalyticsExpGroupId = groupId;
}
else
{
isEnable = false;
}
Debug.Log($"{Tag} --- Analytics EXP params:: groupId:{groupId} baseUrl:{baseUrl} uploadIpAddress:[{string.Join(",", uploadIpAddress)}]");
}
private static GuruAnalyticsExpConfig GetDefaultGuruAnalyticsExpConfig()
{
GuruAnalyticsExpConfig config = null;
if (!string.IsNullOrEmpty(SavedGuruAnalyticsExpGroupId))
{
config = DefaultData.GetConfig(SavedGuruAnalyticsExpGroupId); // 非空则取值
}
else
{
config = DefaultData.GetRandomConfig(); // 随机获取本地的 Config
}
if(IsDebug) Debug.LogWarning($"{Tag} --- #1.1 using Default GropId: {config?.groupId ?? "NULL"}");
return config;
}
}
/// <summary>
/// 实验数据主题
/// </summary>
internal class GuruAnalyticsExpData
{
public bool enable = true; // 默认是打开的状态
public GuruAnalyticsExpConfig[] exps; // 实验列表
public string ToJson() => JsonParser.ToJson(this); // 转换成 JSON 字符串
/// <summary>
/// 获取随机分组
/// </summary>
/// <returns></returns>
public GuruAnalyticsExpConfig GetRandomConfig()
{
if (exps == null || exps.Length == 0) return null;
return exps[Random.Range(0, exps.Length)];
}
/// <summary>
/// 根据分组名称获取分组
/// </summary>
/// <param name="groupId"></param>
/// <returns></returns>
public GuruAnalyticsExpConfig GetConfig(string groupId)
{
foreach (var g in exps)
{
if (g.groupId == groupId) return g;
}
return null;
}
/// <summary>
/// 获取首个配置
/// </summary>
/// <returns></returns>
public GuruAnalyticsExpConfig GetFirstConfig()
{
if (exps != null && exps.Length > 0) return exps[0];
return null;
}
/// <summary>
/// 分组是否存在
/// </summary>
/// <param name="groupId"></param>
/// <returns></returns>
public bool IsGroupExists(string groupId)
{
foreach (var g in exps)
{
if (g.groupId == groupId) return true;
}
return false;
}
}
/// <summary>
/// 实验配置
/// </summary>
internal class GuruAnalyticsExpConfig
{
public string groupId;
public string baseUrl;
public string[] uploadIpAddress;
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a840d9218d324971b528c639ddfea270
timeCreated: 1721722549

View File

@ -14,7 +14,7 @@ namespace Guru
public class GuruAnalytics public class GuruAnalytics
{ {
// Plugin Version // Plugin Version
public const string Version = "1.10.5"; public const string Version = "1.11.1";
public static readonly string Tag = "[ANU]"; public static readonly string Tag = "[ANU]";
private static readonly string ActionName = "logger_error"; private static readonly string ActionName = "logger_error";
@ -91,9 +91,54 @@ namespace Guru
_autoSyncProperties = syncProperties; _autoSyncProperties = syncProperties;
_enableErrorLog = enableErrorLog; _enableErrorLog = enableErrorLog;
Agent?.Init(appId, deviceInfo, isDebug); Agent?.Init(appId, deviceInfo, isDebug);
}
/// <summary>
/// 根据配置初始化接口
/// </summary>
/// <param name="appId"></param>
/// <param name="deviceInfo"></param>
/// <param name="baseUrl"></param>
/// <param name="uploadIpAddress"></param>
/// <param name="isEnable"></param>
/// <param name="enableErrorLog"></param>
/// <param name="isDebug"></param>
public static void InitWithConfig(string appId, string deviceInfo, string baseUrl, string[] uploadIpAddress, bool isEnable = true, bool enableErrorLog = true, bool isDebug = false)
{
_enableErrorLog = enableErrorLog;
#if UNITY_ANDROID
Debug.Log($"{Tag} [{Version}] --- InitWithConfig ---");
if (isEnable)
{
if (Agent is AnalyticsAgentAndroid andAgent)
{
if (!string.IsNullOrEmpty(baseUrl))
{
Debug.Log($"\t baseUrl:{baseUrl}");
}
if (uploadIpAddress != null)
{
Debug.Log($"\t uploadIpAddress:{uploadIpAddress}");
}
andAgent.InitWithConfig(appId, deviceInfo, baseUrl, uploadIpAddress, isDebug);
}
}
else
{
Init(appId, deviceInfo, isDebug);
}
#else
Init(appId, deviceInfo, isDebug);
#endif
if(_enableErrorLog) InitCallbacks(); // 激活错误日志回调 if(_enableErrorLog) InitCallbacks(); // 激活错误日志回调
} }
/// <summary> /// <summary>
/// 设置视图名称 /// 设置视图名称
/// </summary> /// </summary>
@ -558,11 +603,13 @@ namespace Guru
case AnalyticsCode.ERROR_ZIP: case AnalyticsCode.ERROR_ZIP:
case AnalyticsCode.ERROR_DNS_CACHE: case AnalyticsCode.ERROR_DNS_CACHE:
case AnalyticsCode.CRONET_INTERCEPTOR: case AnalyticsCode.CRONET_INTERCEPTOR:
case AnalyticsCode.EVENT_LOOKUP:
case AnalyticsCode.EVENT_SESSION_ACTIVE:
canCatch = true; canCatch = true;
break; break;
} }
if (code > 100 && code <= 200) if (!canCatch && code is > 100 and <= 200)
{ {
// 100 < code <= 200 // 100 < code <= 200
canCatch = true; canCatch = true;
@ -608,21 +655,24 @@ namespace Guru
{ {
Unknown = -1, Unknown = -1,
DELETE_EXPIRED = 12, DELETE_EXPIRED = 12, // 删除过期事件
UPLOAD_FAIL = 14, UPLOAD_FAIL = 14, // 上报事件失败
Network_Lost = 22, Network_Lost = 22, // 网络状态不可用
CRONET_INIT_FAIL = 26, CRONET_INIT_FAIL = 26, // 开启Cronet失败
CRONET_INIT_EXCEPTION = 27, CRONET_INIT_EXCEPTION = 27, // 开启Cronet报错
ERROR_API = 101, ERROR_API = 101, // 调用api出错
ERROR_RESPONSE = 102, ERROR_RESPONSE = 102, // api返回结果错误
ERROR_CACHE_CONTROL = 103, ERROR_CACHE_CONTROL = 103, // 设置cacheControl出错
ERROR_DELETE_EXPIRED = 104, ERROR_DELETE_EXPIRED = 104, // 删除过期事件出错
ERROR_LOAD_MARK = 105, ERROR_LOAD_MARK = 105, // 从数据库取事件以及更改事件状态为正在上报出错
ERROR_DNS = 106, ERROR_DNS = 106, // dns 错误
ERROR_ZIP = 107, ERROR_ZIP = 107, // zip 错误
ERROR_DNS_CACHE = 108, ERROR_DNS_CACHE = 108, // zip 错误
CRONET_INTERCEPTOR = 109, CRONET_INTERCEPTOR = 109, // cronet拦截器
EVENT_LOOKUP = 1003,
EVENT_SESSION_ACTIVE = 1004,
} }
} }

View File

@ -15,7 +15,7 @@ namespace Guru
void SetUid(string uid); void SetUid(string uid);
bool IsDebug { get; } bool IsDebug { get; }
bool EnableErrorLog { get; set; } bool EnableErrorLog { get; set; }
void LogEvent(string eventName, string parameters, int priority = -1); void LogEvent(string eventName, string parameters, int priority = 0);
void ReportEventSuccessRate(); // 上报任务成功率 void ReportEventSuccessRate(); // 上报任务成功率
void SetTch02Value(double value); // 设置太极02数值 void SetTch02Value(double value); // 设置太极02数值
void InitCallback(string objName, string method); // 设置回调对象参数 void InitCallback(string objName, string method); // 设置回调对象参数

View File

@ -4,6 +4,7 @@ namespace Guru
{ {
using System; using System;
using UnityEngine; using UnityEngine;
using System.Collections.Generic;
public class AnalyticsAgentAndroid: IAnalyticsAgent public class AnalyticsAgentAndroid: IAnalyticsAgent
{ {
@ -16,8 +17,6 @@ namespace Guru
#endif #endif
private static bool _isDebug = false; private static bool _isDebug = false;
public static bool UseWorker = true;
public static bool UseCronet = false;
public static string BaseUrl = ""; public static string BaseUrl = "";
#region 工具方法 #region 工具方法
@ -76,14 +75,65 @@ namespace Guru
#region 接口实现 #region 接口实现
/// <summary>
/// 默认的启动参数
/// </summary>
/// <param name="appId"></param>
/// <param name="deviceInfo"></param>
/// <param name="isDebug"></param>
public void Init(string appId, string deviceInfo, bool isDebug = false) public void Init(string appId, string deviceInfo, bool isDebug = false)
{ {
_isDebug = isDebug; _isDebug = isDebug;
string bundleId = Application.identifier; string bundleId = Application.identifier;
// public static void init(String appId, String deviceInfo, String bundleId, boolean isDebug, boolean useWorker, boolean useCronet, String baseUrl) bool useWorker = true;
CallStatic("init", appId, deviceInfo, bundleId, isDebug, UseWorker, UseCronet, BaseUrl); // 调用接口 bool useCronet = false;
string baseUrl = "";
string[] uploadIpAddress = null;
CallSDKInit(appId, deviceInfo, bundleId, baseUrl, uploadIpAddress , useWorker, useCronet, isDebug); // 调用接口
} }
/// <summary>
/// 面向 Android 启动专用的 API
/// </summary>
/// <param name="appId"></param>
/// <param name="deviceInfo"></param>
/// <param name="baseUrl"></param>
/// <param name="uploadIpAddress"></param>
/// <param name="isDebug"></param>
public void InitWithConfig(string appId, string deviceInfo, string baseUrl, string[]uploadIpAddress, bool isDebug = false)
{
_isDebug = isDebug;
string bundleId = Application.identifier;
bool useWorker = true;
bool useCronet = false;
CallSDKInit(appId, deviceInfo, bundleId, baseUrl, uploadIpAddress , useWorker, useCronet, _isDebug); // 调用接口
}
/********* Android API **********
public static void init(String appId,
String deviceInfo,
String bundleId,
boolean debug,
boolean useWorker,
boolean enabledCronet,
String baseUrl,
List<String> uploadIpAddress)
*/
private void CallSDKInit(string appId,
string deviceInfo,
string bundleId,
string baseUrl = "",
string[] uploadIpAddress = null,
bool useWorker = true,
bool useCronet = false,
bool isDebug = false)
{
CallStatic("init", appId, deviceInfo, bundleId, isDebug, useWorker, useCronet, baseUrl, string.Join(",", uploadIpAddress)); // 调用接口
}
public void SetScreen(string screenName) public void SetScreen(string screenName)
{ {
if (string.IsNullOrEmpty(screenName)) return; if (string.IsNullOrEmpty(screenName)) return;
@ -125,10 +175,7 @@ namespace Guru
} }
public bool IsDebug => CallStatic<bool>("isDebug"); public bool IsDebug => CallStatic<bool>("isDebug");
public void LogEvent(string eventName, string parameters, int priority = -1) public void LogEvent(string eventName, string parameters, int priority = 0) => CallStatic("logEvent", eventName, parameters, priority);
{
CallStatic("logEvent", eventName, parameters, priority);
}
public void ReportEventSuccessRate() => CallStatic("reportEventRate"); public void ReportEventSuccessRate() => CallStatic("reportEventRate");
public void SetTch02Value(double value) => CallStatic("setTch02Value", value); public void SetTch02Value(double value) => CallStatic("setTch02Value", value);
public void InitCallback(string objName, string method) => CallStatic("initCallback", objName, method); public void InitCallback(string objName, string method) => CallStatic("initCallback", objName, method);

View File

@ -122,7 +122,7 @@ namespace Guru
public bool IsDebug => _isDebug; public bool IsDebug => _isDebug;
public void LogEvent(string eventName, string data, int priority = -1) public void LogEvent(string eventName, string data, int priority = 0)
{ {
#if UNITY_IOS #if UNITY_IOS
unityLogEvent(eventName, data); unityLogEvent(eventName, data);

View File

@ -87,7 +87,7 @@ namespace Guru
public bool IsDebug => _isDebug; public bool IsDebug => _isDebug;
public void LogEvent(string eventName, string parameters, int priority = -1) public void LogEvent(string eventName, string parameters, int priority = 0)
{ {
if (_isShowLog) if (_isShowLog)
{ {
@ -127,7 +127,7 @@ namespace Guru
} }
} }
Debug.Log($"{TAG} LogEvent: event:<color=orange>{eventName} ({priority})</color> Properties:\n{sb.ToString()}"); Debug.Log($"{TAG} LogEvent: event:<color=orange>{eventName}</color> Properties:\n{sb.ToString()}");
} }
} }
} }

View File

@ -23,7 +23,7 @@ namespace Guru
protected override void InitService() protected override void InitService()
{ {
base.InitService(); base.InitService();
InitChannels(_isDebug); // 启动各广告渠道代理 InitChannels(); // 启动各广告渠道代理
} }
#endregion #endregion
@ -37,14 +37,14 @@ namespace Guru
/// <summary> /// <summary>
/// 各渠道初始化 /// 各渠道初始化
/// </summary> /// </summary>
private void InitChannels(bool isDebug) private void InitChannels()
{ {
_adChannels = new HashSet<IAdChannel>(); _adChannels = new HashSet<IAdChannel>();
IAdChannel channel = null; IAdChannel channel = null;
_asyncLoader = null; _asyncLoader = null;
_chanelMax = new AdChanelMax(); // 默认持有MAXChannel _chanelMax = new AdChanelMax(); // 默认持有MAXChannel
_chanelMax.Initialize(isDebug); _chanelMax.Initialize();
if(_initSpec != null) _chanelMax.SetBannerBackColor(_initSpec.bannerColorHex); if(_initSpec != null) _chanelMax.SetBannerBackColor(_initSpec.bannerColorHex);
//------------ 以下为扩展的广告渠道 ------------------ //------------ 以下为扩展的广告渠道 ------------------
@ -52,7 +52,7 @@ namespace Guru
// 开启渠道需要添加对应的宏 // 开启渠道需要添加对应的宏
channel = new AdChanelAmazon(); channel = new AdChanelAmazon();
channel.Initialize(isDebug); channel.Initialize();
_adChannels.Add(channel); // Amazon _adChannels.Add(channel); // Amazon
_asyncLoader = channel as IAsyncRequestChannel; _asyncLoader = channel as IAsyncRequestChannel;
if (_asyncLoader != null) if (_asyncLoader != null)

View File

@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using Guru;
using UnityEngine;
namespace Guru namespace Guru
{ {
using System;
using UnityEngine;
using System.Collections.Generic;
public abstract class ADServiceBase<T> : IADService where T : new() public abstract class ADServiceBase<T> : IADService where T : new()
{ {
// 单利定义 // 单利定义
@ -22,8 +23,6 @@ namespace Guru
public bool IsInitialized => MaxSdk.IsInitialized() || _isServiceStarted; public bool IsInitialized => MaxSdk.IsInitialized() || _isServiceStarted;
protected bool IsNetworkEnabled => Application.internetReachability != NetworkReachability.NotReachable; protected bool IsNetworkEnabled => Application.internetReachability != NetworkReachability.NotReachable;
private const int MAX_ADS_RELOAD_INTERVAL = 6; // 广告加载最高时间为 2 的 6 次方 = 64秒
private bool _isServiceStarted; private bool _isServiceStarted;
protected Action _onSdkInitReady; protected Action _onSdkInitReady;
@ -34,12 +33,10 @@ namespace Guru
public static Action<string> OnInterstitialStartLoad; public static Action<string> OnInterstitialStartLoad;
public static Action OnInterstitialLoaded; public static Action OnInterstitialLoaded;
public static Action OnInterstitialFailed; public static Action OnInterstitialFailed;
public static Action OnInterstitialClosed;
public static Action<string> OnRewardedStartLoad; public static Action<string> OnRewardedStartLoad;
public static Action OnRewardLoaded; public static Action OnRewardLoaded;
public static Action OnRewardFailed; public static Action OnRewardFailed;
public static Action OnRewardClosed;
protected AdsModel _model; protected AdsModel _model;
protected AdsInitSpec _initSpec = null; protected AdsInitSpec _initSpec = null;
@ -53,7 +50,6 @@ namespace Guru
} }
} }
internal bool _isDebug = false;
/// <summary> /// <summary>
/// 启动广告服务 /// 启动广告服务
@ -66,23 +62,11 @@ namespace Guru
if (IsInitialized) return; // 已经初始化后, 无需再次初始化 if (IsInitialized) return; // 已经初始化后, 无需再次初始化
_initSpec = initSpec; _initSpec = initSpec;
if (_initSpec == null) _initSpec = AdsInitSpec.BuildDefault();
_isDebug = _initSpec.isDebug;
_isServiceStarted = true; _isServiceStarted = true;
_onSdkInitReady = callback; _onSdkInitReady = callback;
if(_model == null) _model = AdsModel.Create(); if(_model == null) _model = AdsModel.Create();
this.Log("AD SDK Start Init"); this.Log("AD SDK Start Init");
InitMaxCallbacks(); // 初始化 MAX 广告
InitService(); // 内部继承接口
}
/// <summary>
/// 初始化 MAX 广告组件
/// </summary>
private void InitMaxCallbacks()
{
//-------------- 初始化回调 ------------------ //-------------- 初始化回调 ------------------
MaxSdkCallbacks.OnSdkInitializedEvent += OnMaxSdkInitializedCallBack; MaxSdkCallbacks.OnSdkInitializedEvent += OnMaxSdkInitializedCallBack;
MaxSdkCallbacks.Interstitial.OnAdRevenuePaidEvent += OnAdRevenuePaidEvent; MaxSdkCallbacks.Interstitial.OnAdRevenuePaidEvent += OnAdRevenuePaidEvent;
@ -110,7 +94,10 @@ namespace Guru
MaxSdkCallbacks.Rewarded.OnAdReceivedRewardEvent += OnRewardedAdReceivedRewardEvent; MaxSdkCallbacks.Rewarded.OnAdReceivedRewardEvent += OnRewardedAdReceivedRewardEvent;
//-------------- SDK 初始化 ------------------- //-------------- SDK 初始化 -------------------
MaxSdk.SetExtraParameter("enable_black_screen_fixes", "true"); // 修复黑屏 if (_initSpec == null) _initSpec = AdsInitSpec.BuildDefault();
MaxSdk.SetVerboseLogging(_initSpec.isDebug);
InitService(); // 内部继承接口
} }
protected virtual void InitService() protected virtual void InitService()
@ -151,13 +138,6 @@ namespace Guru
set => Model.BuyNoAds = value; set => Model.BuyNoAds = value;
} }
private float GetRetryDelaySeconds(int retryCount)
{
// 最低 2^retryCount 秒
// 最高 2^6 = 64 秒
return (float)Math.Pow(2, Math.Min(MAX_ADS_RELOAD_INTERVAL, retryCount));
}
#region Lifecycele #region Lifecycele
public void OnAppPaused(bool paused) public void OnAppPaused(bool paused)
@ -437,7 +417,7 @@ namespace Guru
private string _iadsCategory = "main"; private string _iadsCategory = "main";
private int _interstitialRetryAttempt; private int _interstitialRetryAttempt;
protected float _iadsLoadStartTime; protected float _iadsLoadStartTime;
private Action _interCloseAction; private Action _interstitialDismissAction;
protected bool _isIadsLoading = false; protected bool _isIadsLoading = false;
public bool IsIadsLoading => _isIadsLoading; public bool IsIadsLoading => _isIadsLoading;
@ -493,7 +473,7 @@ namespace Guru
} }
_iadsCategory = category; _iadsCategory = category;
_interCloseAction = dismissAction; _interstitialDismissAction = dismissAction;
MaxSdk.ShowInterstitial(GetInterstitialID()); MaxSdk.ShowInterstitial(GetInterstitialID());
// RequestInterstitialAD(); // 直接加载下一个广告 // RequestInterstitialAD(); // 直接加载下一个广告
@ -520,8 +500,8 @@ namespace Guru
this.LogError( this.LogError(
$"OnInterstitialFailedEvent AdLoadFailureInfo:{errorInfo.AdLoadFailureInfo}, Message: {errorInfo.Message}"); $"OnInterstitialFailedEvent AdLoadFailureInfo:{errorInfo.AdLoadFailureInfo}, Message: {errorInfo.Message}");
_interstitialRetryAttempt++; _interstitialRetryAttempt++;
float retryDelay = GetRetryDelaySeconds(_interstitialRetryAttempt); double retryDelay = Math.Pow(2, Math.Min(3, _interstitialRetryAttempt));
DelayCall(retryDelay, RequestInterstitialAD); DelayCall((float)retryDelay, RequestInterstitialAD);
// Analytics.ADIadsFailed(adUnitId, (int)errorInfo.Code, GetAdsLoadDuration(ref _iadsLoadStartTime), _iadsCategory); // Analytics.ADIadsFailed(adUnitId, (int)errorInfo.Code, GetAdsLoadDuration(ref _iadsLoadStartTime), _iadsCategory);
Analytics.ADIadsFailed(AdParams.Build(adUnitId, Analytics.ADIadsFailed(AdParams.Build(adUnitId,
duration: GetAdsLoadDuration(ref _iadsLoadStartTime), category: _iadsCategory, duration: GetAdsLoadDuration(ref _iadsLoadStartTime), category: _iadsCategory,
@ -560,8 +540,7 @@ namespace Guru
protected virtual void OnInterstitialDismissedEvent(string adUnitId, MaxSdkBase.AdInfo adInfo) protected virtual void OnInterstitialDismissedEvent(string adUnitId, MaxSdkBase.AdInfo adInfo)
{ {
// Interstitial ad is hidden. Pre-load the next ad // Interstitial ad is hidden. Pre-load the next ad
_interCloseAction?.Invoke(); _interstitialDismissAction?.Invoke();
OnInterstitialClosed?.Invoke();
// Analytics.ADIadsClose(adUnitId, _iadsCategory); // Analytics.ADIadsClose(adUnitId, _iadsCategory);
Analytics.ADIadsClose(AdParams.Build(adUnitId, category: _iadsCategory)); Analytics.ADIadsClose(AdParams.Build(adUnitId, category: _iadsCategory));
//延时加载下一个广告 //延时加载下一个广告
@ -575,9 +554,9 @@ namespace Guru
private string _rewardCategory = "main"; private string _rewardCategory = "main";
private int _rewardRetryAttempt; private int _rewardRetryAttempt;
protected float _radsLoadStartTime; protected float _radsLoadStartTime;
private Action _rvRewardAction; private Action _rewardAction;
private Action<string> _rvFailAction; private Action<string> _failAction;
private Action _rvDismissAction; private Action _dismissAction;
protected bool _isRadsLoading = false; protected bool _isRadsLoading = false;
public bool IsRadsLoading => _isRadsLoading; public bool IsRadsLoading => _isRadsLoading;
@ -641,9 +620,9 @@ namespace Guru
} }
_rewardCategory = category; _rewardCategory = category;
_rvRewardAction = rewardAction; _rewardAction = rewardAction;
_rvFailAction = failAction; _failAction = failAction;
_rvDismissAction = dismissAction; _dismissAction = dismissAction;
MaxSdk.ShowRewardedAd(GetRewardedID()); MaxSdk.ShowRewardedAd(GetRewardedID());
// RequestRewardedAD(); // RequestRewardedAD();
@ -678,8 +657,8 @@ namespace Guru
errorCode: (int)errorInfo.Code, errorCode: (int)errorInfo.Code,
waterfallName: errorInfo?.WaterfallInfo?.Name ?? "")); waterfallName: errorInfo?.WaterfallInfo?.Name ?? ""));
_rewardRetryAttempt++; _rewardRetryAttempt++;
float retryDelay = GetRetryDelaySeconds(_rewardRetryAttempt); double retryDelay = Math.Pow(2, Math.Min(3, _rewardRetryAttempt));
DelayCall(retryDelay, RequestRewardedAD); DelayCall((float)retryDelay, RequestRewardedAD);
OnRewardFailed?.Invoke(); OnRewardFailed?.Invoke();
} }
@ -695,7 +674,7 @@ namespace Guru
duration: GetAdsLoadDuration(ref _radsLoadStartTime), category: _rewardCategory, duration: GetAdsLoadDuration(ref _radsLoadStartTime), category: _rewardCategory,
errorCode: (int)errorInfo.Code, errorCode: (int)errorInfo.Code,
waterfallName: errorInfo?.WaterfallInfo?.Name ?? "")); waterfallName: errorInfo?.WaterfallInfo?.Name ?? ""));
_rvFailAction?.Invoke("OnRewardedAdFailedToDisplayEvent"); _failAction?.Invoke("OnRewardedAdFailedToDisplayEvent");
DelayCall(2.0f, RequestRewardedAD); DelayCall(2.0f, RequestRewardedAD);
OnRewardFailed?.Invoke(); OnRewardFailed?.Invoke();
@ -718,17 +697,11 @@ namespace Guru
protected virtual void OnRewardedAdDismissedEvent(string adUnitId, MaxSdkBase.AdInfo adInfo) protected virtual void OnRewardedAdDismissedEvent(string adUnitId, MaxSdkBase.AdInfo adInfo)
{ {
this.Log("OnRewardedAdDismissedEvent"); this.Log("OnRewardedAdDismissedEvent");
_rvDismissAction?.Invoke();
OnRewardClosed?.Invoke();
// Analytics.ADRadsClose(adUnitId, _rewardCategory); // Analytics.ADRadsClose(adUnitId, _rewardCategory);
Analytics.ADRadsClose(AdParams.Build(adUnitId, category: _rewardCategory)); Analytics.ADRadsClose(AdParams.Build(adUnitId, category: _rewardCategory));
_dismissAction?.Invoke();
//延时加载下一个广告 //延时加载下一个广告
DelayCall(2.0f, RequestRewardedAD); DelayCall(2.0f, RequestRewardedAD);
} }
protected virtual void OnRewardedAdReceivedRewardEvent(string adUnitId, MaxSdk.Reward reward, protected virtual void OnRewardedAdReceivedRewardEvent(string adUnitId, MaxSdk.Reward reward,
@ -738,7 +711,7 @@ namespace Guru
// Analytics.ADRadsRewarded(adUnitId, _rewardCategory); // Analytics.ADRadsRewarded(adUnitId, _rewardCategory);
Analytics.ADRadsRewarded(AdParams.Build(adUnitId, category: _rewardCategory)); Analytics.ADRadsRewarded(AdParams.Build(adUnitId, category: _rewardCategory));
// Rewarded ad was displayed and user should receive the reward // Rewarded ad was displayed and user should receive the reward
_rvRewardAction?.Invoke(); _rewardAction?.Invoke();
} }
#endregion #endregion

View File

@ -32,11 +32,10 @@ namespace Guru
/// <summary> /// <summary>
/// MAX 渠道初始化, 启动服务 /// MAX 渠道初始化, 启动服务
/// </summary> /// </summary>
public void Initialize(bool isDebug = false) public void Initialize()
{ {
MaxSdk.SetSdkKey(GuruSettings.Instance.ADSetting.SDK_KEY); MaxSdk.SetSdkKey(GuruSettings.Instance.ADSetting.SDK_KEY);
MaxSdk.SetUserId(IPMConfig.IPM_UID); // 上报用户ID MaxSdk.SetUserId(IPMConfig.IPM_UID); // 上报用户ID
MaxSdk.SetVerboseLogging(isDebug); // 设置调试数据
MaxSdk.InitializeSdk(); MaxSdk.InitializeSdk();
} }

View File

@ -8,7 +8,7 @@ namespace Guru
{ {
// Action<string> OnRequestOver { get; set; } // Action<string> OnRequestOver { get; set; }
void Initialize(bool isDebug = false); void Initialize();
string Name { get;} string Name { get;}

View File

@ -29,7 +29,7 @@ namespace Guru
{ {
return null; return null;
} }
UnityEngine.Debug.Log($"{TAG} --- Send Adjust Event: {eventName}({tokenID})");
return new AdjustEvent(tokenID); return new AdjustEvent(tokenID);
} }

View File

@ -224,17 +224,17 @@ namespace Guru
/// </summary> /// </summary>
/// <param name="status"></param> /// <param name="status"></param>
/// <param name="channel"></param> /// <param name="channel"></param>
/// <param name="scene"></param> /// <param name="others"></param>
public static void AttResult(string status, string type = "custom", string scene = "") public static void AttResult(string status, string type = "custom", string others = "")
{ {
SetAttProperty(status); SetAttProperty(status);
Debug.Log($"{TAG} AttResult: {status} type:{type} others:{scene}"); Debug.Log($"{TAG} AttResult: {status} type:{type} others:{others}");
var dict = new Dictionary<string, dynamic>() var dict = new Dictionary<string, dynamic>()
{ {
{ ParameterItemCategory, status }, { ParameterItemCategory, status },
{ "type", type } { "type", type }
}; };
if(!string.IsNullOrEmpty(scene)) dict[ParameterItemName] = scene; if(!string.IsNullOrEmpty(others)) dict[ParameterItemName] = others;
LogEvent(EventATTResult, dict); LogEvent(EventATTResult, dict);
} }

View File

@ -6,6 +6,7 @@ namespace Guru
fail, fail,
timeout, timeout,
exit, exit,
replay
} }
//打点常量定义 //打点常量定义
@ -15,7 +16,7 @@ namespace Guru
// 美元符号 // 美元符号
public static readonly string USD = "USD"; public static readonly string USD = "USD";
// 广告平台 // 广告平台
public static readonly string AdMAX = "MAX"; public static readonly string AdMAX = "MAX";
//IAP打点事件 //IAP打点事件
public static readonly string EventIAPFirst = "first_iap"; public static readonly string EventIAPFirst = "first_iap";

View File

@ -33,41 +33,52 @@ namespace Guru
/// <summary> /// <summary>
/// 初始化Guru自打点系统 (请优先于 Firebase 初始化调用) /// 初始化Guru自打点系统 (请优先于 Firebase 初始化调用)
/// </summary> /// </summary>
public static void InstallGuruAnalytics(bool isDebug = false, bool enableErrorLog = false) public static void InstallGuruAnalytics(bool isDebug = false, bool enableErrorLog = false, string firebaseId = "")
{
InitGuruAnalyticService("", null, false, isDebug, enableErrorLog, firebaseId); // Android 初始化
}
public static void InitGuruAnalyticService(string baseUrl, string[] uploadIpAddress, bool isEnable = true, bool isDebug = false,
bool enableErrorLog = false, string firebaseId = "")
{ {
if (_hasInited) return; if (_hasInited) return;
try Debug.Log($"{TAG} --- InstallGuruAnalytics baseUrl: {baseUrl} enableErrorLog: {enableErrorLog} firebaseId:{firebaseId}");
{
try
{
#if UNITY_EDITOR #if UNITY_EDITOR
IsDebug = true; IsDebug = true;
#else #else
IsDebug = isDebug; IsDebug = isDebug;
#endif #endif
string appId = IPMConfig.IPM_X_APP_ID; string appId = IPMConfig.IPM_X_APP_ID;
string deviceInfo = new DeviceInfoData().ToString(); string deviceInfo = new DeviceInfoData().ToString();
GuruAnalytics.Init(appId, deviceInfo, IsDebug, enableErrorLog); // 初始化(带Header)
_hasGotFirebaseId = false;
_hasGotAdId = false;
_hasGotAdjustId = false;
_hasGotDeviceId = false;
_hasGotUid = false;
_lastReportRateDate = DateTime.Now;
_reportSuccessInterval = 120; // 2分钟上报一次
UpdateAllValues();
_hasInited = true; _hasGotFirebaseId = false;
} _hasGotAdId = false;
catch (Exception ex) _hasGotAdjustId = false;
{ _hasGotDeviceId = false;
LogCrashlytics(ex); _hasGotUid = false;
} _lastReportRateDate = DateTime.Now;
_reportSuccessInterval = 120; // 2分钟上报一次
if(!string.IsNullOrEmpty(firebaseId)) GuruAnalytics.SetFirebaseId(firebaseId);
GuruAnalytics.InitWithConfig(appId, deviceInfo, baseUrl, uploadIpAddress, isEnable, enableErrorLog); // Android 初始化
UpdateAllValues();
_hasInited = true;
}
catch (Exception ex)
{
LogCrashlytics(ex);
}
} }
#region 各ID上报信息
#region 各ID上报信息
/// <summary> /// <summary>
/// 设置用户ID /// 设置用户ID
@ -80,7 +91,7 @@ namespace Guru
{ {
Debug.Log($"---[ANA] UID: {IPMConfig.IPM_UID}"); Debug.Log($"---[ANA] UID: {IPMConfig.IPM_UID}");
GuruAnalytics.SetUid(IPMConfig.IPM_UID); GuruAnalytics.SetUid(IPMConfig.IPM_UID);
FirebaseAnalytics.SetUserProperty(PropertyUserID, IPMConfig.IPM_UID); FirebaseSetUserProperty(PropertyUserID, IPMConfig.IPM_UID);
_hasGotUid = true; _hasGotUid = true;
} }
@ -96,7 +107,7 @@ namespace Guru
if (!string.IsNullOrEmpty(IPMConfig.IPM_DEVICE_ID)) if (!string.IsNullOrEmpty(IPMConfig.IPM_DEVICE_ID))
{ {
GuruAnalytics.SetDeviceId(IPMConfig.IPM_DEVICE_ID); GuruAnalytics.SetDeviceId(IPMConfig.IPM_DEVICE_ID);
FirebaseAnalytics.SetUserProperty(PropertyDeviceID, IPMConfig.IPM_DEVICE_ID); FirebaseSetUserProperty(PropertyDeviceID, IPMConfig.IPM_DEVICE_ID);
_hasGotDeviceId = true; _hasGotDeviceId = true;
} }
} }
@ -185,6 +196,8 @@ namespace Guru
/// </summary> /// </summary>
private static void FetchFirebaseId() private static void FetchFirebaseId()
{ {
if (!IsFirebaseReady) return;
FirebaseAnalytics.GetAnalyticsInstanceIdAsync() FirebaseAnalytics.GetAnalyticsInstanceIdAsync()
.ContinueWithOnMainThread(task => .ContinueWithOnMainThread(task =>
{ {
@ -250,6 +263,8 @@ namespace Guru
/// </summary> /// </summary>
private static void UpdateAllValues() private static void UpdateAllValues()
{ {
Debug.Log($"{TAG} --- UpdateAllValues");
SetUid(); SetUid();
SetDeviceId(); SetDeviceId();
SetAdjustId(); SetAdjustId();
@ -297,9 +312,16 @@ namespace Guru
GuruAnalytics.SetUserProperty(key, value); GuruAnalytics.SetUserProperty(key, value);
UpdateAllValues(); // 同步所有的ID UpdateAllValues(); // 同步所有的ID
} }
catch (Exception e) catch (Exception ex)
{ {
Crashlytics.LogException(e); if (IsFirebaseReady)
{
Crashlytics.LogException(ex);
}
else
{
Debug.LogException(ex);
}
} }
} }
@ -309,17 +331,24 @@ namespace Guru
/// </summary> /// </summary>
/// <param name="key"></param> /// <param name="key"></param>
/// <param name="data"></param> /// <param name="data"></param>
private static void CustomLogEvent(string key, Dictionary<string, dynamic> data = null, int priority = -1) private static void CustomLogEvent(string key, Dictionary<string, dynamic> data = null)
{ {
try try
{ {
if (data == null) data = new Dictionary<string, dynamic>(); if (data == null) data = new Dictionary<string, dynamic>();
GuruAnalytics.LogEvent(key, data, priority); GuruAnalytics.LogEvent(key, data);
UpdateAllValues(); // 同步所有的ID UpdateAllValues(); // 同步所有的ID
} }
catch (Exception e) catch (Exception e)
{ {
Crashlytics.LogException(e); if (IsFirebaseReady)
{
Crashlytics.LogException(e);
}
else
{
Debug.LogWarning(e);
}
} }
} }
@ -343,7 +372,14 @@ namespace Guru
} }
catch (Exception e) catch (Exception e)
{ {
Crashlytics.LogException(e); if (IsFirebaseReady)
{
Crashlytics.LogException(e);
}
else
{
Debug.LogWarning(e);
}
} }
} }

View File

@ -295,11 +295,9 @@ namespace Guru
/// <param name="orderId"></param> /// <param name="orderId"></param>
/// <param name="orderType"></param> /// <param name="orderType"></param>
/// <param name="timestamp"></param> /// <param name="timestamp"></param>
/// <param name="isTest"></param> public static void Tch001IAPRev(double value, string productId, string orderId, string orderType, string timestamp)
public static void Tch001IAPRev(double value, string productId, string orderId, string orderType, string timestamp, bool isTest = false)
{ {
string sandbox = isTest ? "true" : "false"; TchRevEvent(EventTchAdRev001Impression, IAPPlatform, value, orderType, productId, orderId, timestamp);
TchRevEvent(EventTchAdRev001Impression, IAPPlatform, value, orderType, productId, orderId, timestamp, sandbox);
} }
/// <summary> /// <summary>
@ -311,13 +309,11 @@ namespace Guru
/// <param name="orderId"></param> /// <param name="orderId"></param>
/// <param name="orderType"></param> /// <param name="orderType"></param>
/// <param name="timestamp"></param> /// <param name="timestamp"></param>
/// <param name="isTest"></param>
// public static void Tch02IAPRev(double value, string productId, string orderId, string orderType, string timestamp) // public static void Tch02IAPRev(double value, string productId, string orderId, string orderType, string timestamp)
public static void Tch02IAPRev(double value, string productId, string orderId, string orderType, string timestamp, bool isTest = false) public static void Tch02IAPRev(double value, string productId, string orderId, string orderType, string timestamp)
{ {
if (!EnableTch02Event) return; if (!EnableTch02Event) return;
string sandbox = isTest ? "true" : "false"; TchRevEvent(EventTchAdRev02Impression, IAPPlatform, value, orderType, productId, orderId, timestamp);
TchRevEvent(EventTchAdRev02Impression, IAPPlatform, value, orderType, productId, orderId, timestamp, sandbox);
} }
/// <summary> /// <summary>
@ -366,9 +362,8 @@ namespace Guru
/// <param name="productId"></param> /// <param name="productId"></param>
/// <param name="orderId"></param> /// <param name="orderId"></param>
/// <param name="timestamp"></param> /// <param name="timestamp"></param>
/// <param name="sandbox"></param>
private static void TchRevEvent(string evtName, string platform, double value, private static void TchRevEvent(string evtName, string platform, double value,
string orderType = "", string productId = "", string orderId = "", string timestamp = "", string sandbox = "") string orderType = "", string productId = "", string orderId = "", string timestamp = "")
{ {
var data = new Dictionary<string, dynamic>() var data = new Dictionary<string, dynamic>()
{ {
@ -378,13 +373,10 @@ namespace Guru
}; };
//--------- Extra data for IAP receipt --------------- //--------- Extra data for IAP receipt ---------------
if(!string.IsNullOrEmpty(orderType)) data["order_type"] = orderType; if(!string.IsNullOrEmpty(orderType)) data["order_type"] = orderType;
if(!string.IsNullOrEmpty(productId)) data["product_id"] = productId; if(!string.IsNullOrEmpty(productId)) data["product_id"] = productId;
if(!string.IsNullOrEmpty(orderId)) data["order_id"] = orderId; if(!string.IsNullOrEmpty(orderId)) data["order_id"] = orderId;
if(!string.IsNullOrEmpty(timestamp)) data["trans_ts"] = timestamp; if(!string.IsNullOrEmpty(timestamp)) data["trans_ts"] = timestamp;
if(!string.IsNullOrEmpty(sandbox)) data["sandbox"] = sandbox;
//--------- Extra data for IAP receipt --------------- //--------- Extra data for IAP receipt ---------------
LogEvent(evtName, data); LogEvent(evtName, data);
@ -611,19 +603,15 @@ namespace Guru
/// </summary> /// </summary>
/// <param name="productId"></param> /// <param name="productId"></param>
/// <param name="usdPrice"></param> /// <param name="usdPrice"></param>
/// <param name="orderData"></param> /// <param name="userCurrency"></param>
/// <param name="isTest"></param> /// <param name="payPrice"></param>
public static void ReportIAPSuccessEvent(BaseOrderData orderData, double usdPrice, bool isTest = false) /// <param name="orderId"></param>
/// <param name="orderType"></param>
/// <param name="orderDate"></param>
/// <param name="scene"></param>
/// <param name="isFree"></param>
public static void ReportIAPSuccessEvent(double usdPrice, string productId, BaseOrderData orderData)
{ {
if (orderData == null) return;
if (!isTest && usdPrice == 0)
{
Debug.Log($"[SDK] --- Pruchase value is 0, skip report orders");
return;
}
string productId = orderData.productId;
string userCurrency = orderData.userCurrency; string userCurrency = orderData.userCurrency;
double payPrice = orderData.payPrice; double payPrice = orderData.payPrice;
string orderType = orderData.OrderType(); string orderType = orderData.OrderType();
@ -633,7 +621,7 @@ namespace Guru
string scene = orderData.scene; string scene = orderData.scene;
bool isFree = orderData.isFree; bool isFree = orderData.isFree;
string offerId = orderData.offerId; string offerId = orderData.offerId;
string transactionId = ""; string transactionId = "";
string productToken = ""; string productToken = "";
string receipt = ""; string receipt = "";
@ -648,23 +636,21 @@ namespace Guru
} }
// TCH 001 // TCH 001
Tch001IAPRev(usdPrice, productId, orderId, orderType, orderDate, isTest); Tch001IAPRev(usdPrice, productId, orderId, orderType, orderDate);
// TCH 020 // TCH 020
Tch02IAPRev(usdPrice, productId, orderId, orderType, orderDate, isTest); Tch02IAPRev(usdPrice, productId, orderId, orderType, orderDate);
// Facebook Track IAP Purchase // Facebook Track IAP Purchase
FBPurchase(usdPrice, USD, "iap", IAPPlatform); FBPurchase(usdPrice, USD, "iap", IAPPlatform);
if (orderData.orderType == 1) if (orderData.orderType == 1)
{ {
// sub_pruchase : Firebase + Guru + Adjust // sub_pruchase : Firebase + Guru + Adjust
SubPurchase(usdPrice, productId, orderId, orderDate, productToken, receipt, isTest); SubPurchase(usdPrice, productId, orderId, orderDate, productToken, receipt);
} }
else else
{ {
// iap_purchase : Firebase + Guru + Adjust // iap_purchase : Firebase + Guru + Adjust
IAPPurchase(usdPrice, productId, orderId, orderDate, productToken, receipt, isTest); IAPPurchase(usdPrice, productId, orderId, orderDate, productToken, receipt);
} }
// IAP Ret true : Firebase + Guru + Adjust // IAP Ret true : Firebase + Guru + Adjust
@ -683,33 +669,21 @@ namespace Guru
/// <param name="productId"></param> /// <param name="productId"></param>
/// <param name="orderId"></param> /// <param name="orderId"></param>
/// <param name="orderDate"></param> /// <param name="orderDate"></param>
/// <param name="purchaseToken"></param> public static void IAPPurchase(double value, string productId, string orderId, string orderDate,
/// <param name="receipt"></param> string purchaseToken = "", string receipt = "")
/// <param name="isSandbox"></param>
public static void IAPPurchase(double value, string productId, string orderId, string orderDate,
string purchaseToken = "", string receipt = "", bool isSandbox = false)
{ {
IAPPurchaseReport(EventIAPPurchase, value, productId, orderId, "IAP", orderDate, purchaseToken, receipt, isSandbox); IAPPurchaseReport(EventIAPPurchase, value, productId, orderId, "IAP", orderDate, purchaseToken, receipt);
}
/// <summary>
/// SUB 订阅上报
/// </summary>
/// <param name="value"></param>
/// <param name="productId"></param>
/// <param name="orderId"></param>
/// <param name="orderDate"></param>
/// <param name="purchaseToken"></param>
/// <param name="receipt"></param>
/// <param name="isSandbox"></param>
public static void SubPurchase(double value, string productId, string orderId, string orderDate,
string purchaseToken = "", string receipt = "", bool isSandbox = false)
{
IAPPurchaseReport(EventSubPurchase, value, productId, orderId, "SUB", orderDate, purchaseToken, receipt, isSandbox);
} }
private static void IAPPurchaseReport(string eventName, double value, string productId, public static void SubPurchase(double value, string productId, string orderId, string orderDate,
string orderId, string orderType, string orderDate, string purchaseToken = "", string receipt = "", bool isSandbox = false) string purchaseToken = "", string receipt = "")
{
IAPPurchaseReport(EventSubPurchase, value, productId, orderId, "SUB", orderDate, purchaseToken, receipt);
}
private static void IAPPurchaseReport(string eventName, double value, string productId, string orderId, string orderType, string orderDate,
string purchaseToken = "", string receipt = "")
{ {
var dict = new Dictionary<string, dynamic>() var dict = new Dictionary<string, dynamic>()
@ -720,17 +694,20 @@ namespace Guru
[ParameterProductId] = productId, [ParameterProductId] = productId,
["order_id"] = orderId, ["order_id"] = orderId,
["order_type"] = orderType, ["order_type"] = orderType,
["trans_ts"] = orderDate, ["trans_ts"] = orderDate
["sandbox"] = isSandbox? "true": "false"
}; };
// 上报Firebase + 自打点 // 上报Firebase + 自打点
LogEvent(eventName, dict, new EventSetting() { EnableFirebaseAnalytics = true }); LogEvent(eventName, dict, new EventSetting() { EnableFirebaseAnalytics = true, });
// 上报 Adjust 支付事件 // 上报 Adjust 支付事件
// if (value > 0)
// {
// 根据事件名称来获取对应的事件Tokeniap_purchase/sub_purchase
LogAdjustRevenueEvent(eventName, value, productId, orderId, purchaseToken, receipt, dict); LogAdjustRevenueEvent(eventName, value, productId, orderId, purchaseToken, receipt, dict);
// }
} }
#endregion #endregion

View File

@ -1,13 +1,14 @@
using System.Collections;
using UnityEngine;
namespace Guru namespace Guru
{ {
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using com.adjust.sdk; using com.adjust.sdk;
using Facebook.Unity; using Facebook.Unity;
using UnityEngine;
using Firebase.Analytics; using Firebase.Analytics;
using Firebase.Crashlytics; using Firebase.Crashlytics;
@ -28,12 +29,14 @@ namespace Guru
public static bool IsDebugMode => PlatformUtil.IsDebug(); public static bool IsDebugMode => PlatformUtil.IsDebug();
public static bool IsFirebaseReady => FirebaseUtil.IsFirebaseInitialized;
private static bool IsEnable private static bool IsEnable
{ {
get get
{ {
//Firebase服务没有初始化完成不上报打点 //Firebase服务没有初始化完成不上报打点
if (!FirebaseUtil.IsFirebaseInitialized) if (!IsFirebaseReady)
return false; return false;
//Analytics没有初始化不上报打点 //Analytics没有初始化不上报打点
@ -50,7 +53,7 @@ namespace Guru
#region 初始化 #region 初始化
public static void InitAnalytics() public static void InitAnalytics(string baseUrl = "", string[] uploadIpAddress = null, bool isEnable = false, bool enableErrorLog = false, string firebaseId = "")
{ {
if (_isInited) return; if (_isInited) return;
_isInited = true; _isInited = true;
@ -59,7 +62,7 @@ namespace Guru
CrashlyticsAgent.Install(); CrashlyticsAgent.Install();
// ------- 初始化自打点 ---------- // ------- 初始化自打点 ----------
InstallGuruAnalytics(IsDebug); InitGuruAnalyticService(baseUrl, uploadIpAddress, isEnable, IsDebug, enableErrorLog, firebaseId);
if (_defaultEventSetting == null) if (_defaultEventSetting == null)
{ {
@ -121,8 +124,7 @@ namespace Guru
{ {
Log.I(TAG,$"SetUserIDProperty -> userID:{userID}"); Log.I(TAG,$"SetUserIDProperty -> userID:{userID}");
if (!IsEnable) return; if (!IsEnable) return;
if (IsFirebaseReady) FirebaseAnalytics.SetUserId(userID);
FirebaseAnalytics.SetUserId(userID);
} }
/// <summary> /// <summary>
@ -132,13 +134,30 @@ namespace Guru
{ {
Log.I(TAG,$"SetUserProperty -> propertyName:{propertyName}, propertyValue:{propertyValue}"); Log.I(TAG,$"SetUserProperty -> propertyName:{propertyName}, propertyValue:{propertyValue}");
if (!IsEnable) if (!IsEnable) return;
return; FirebaseSetUserProperty(propertyName, propertyValue);
FirebaseAnalytics.SetUserProperty(propertyName, propertyValue);
CustomSetUserProperty(propertyName, propertyValue); CustomSetUserProperty(propertyName, propertyValue);
} }
/// <summary>
/// Firebase 上报用户属性
/// </summary>
/// <param name="propertyName"></param>
/// <param name="propertyValue"></param>
private static void FirebaseSetUserProperty(string propertyName, string propertyValue)
{
if (IsFirebaseReady)
{
FirebaseAnalytics.SetUserProperty(propertyName, propertyValue);
}
else
{
Debug.Log($"{TAG} --- Firebase not ready, call Firebase Init first!");
}
}
#endregion #endregion
#region 打点上报 #region 打点上报
@ -148,10 +167,10 @@ namespace Guru
/// </summary> /// </summary>
/// <param name="eventName"></param> /// <param name="eventName"></param>
/// <param name="eventSetting"></param> /// <param name="eventSetting"></param>
internal static void LogEvent(string eventName, EventSetting eventSetting = null, int priority = -1) internal static void LogEvent(string eventName, EventSetting eventSetting = null)
{ {
Log.I(TAG, $"eventName:{eventName}"); Log.I(TAG, $"eventName:{eventName}");
CustomLogEvent(eventName, null, priority); // 自定义打点上报 CustomLogEvent(eventName); // 自定义打点上报
CheckLogCache(eventName, null, eventSetting); // log缓存和消费 CheckLogCache(eventName, null, eventSetting); // log缓存和消费
if (!IsEnable) return; if (!IsEnable) return;
@ -180,24 +199,22 @@ namespace Guru
/// <param name="eventName"></param> /// <param name="eventName"></param>
/// <param name="extras"></param> /// <param name="extras"></param>
/// <param name="eventSetting"></param> /// <param name="eventSetting"></param>
/// <param name="priority"></param> internal static void LogEvent(string eventName, Dictionary<string, dynamic> extras, EventSetting eventSetting = null)
internal static void LogEvent(string eventName, Dictionary<string, dynamic> extras, EventSetting eventSetting = null, int priority = -1)
{ {
CustomLogEvent(eventName, extras, priority); // 自定义打点上报 Log.I(TAG, $"eventName:{eventName}, params:{string.Join(",", extras)}");
CustomLogEvent(eventName, extras); // 自定义打点上报
CheckLogCache(eventName, extras, eventSetting); // log缓存和消费 CheckLogCache(eventName, extras, eventSetting); // log缓存和消费
if (!IsEnable) return; if (!IsEnable) return;
if (extras == null) if (extras == null)
{ {
LogEvent(eventName, eventSetting, priority); // 防空判定 LogEvent(eventName, eventSetting); // 防空判定
return; return;
} }
string paramStr = string.Join(",", extras);
Log.I(TAG, $"eventName:{eventName}, params:{paramStr}");
if (eventSetting == null) eventSetting = _defaultEventSetting; eventSetting ??= _defaultEventSetting;
if (eventSetting.EnableFirebaseAnalytics) if (eventSetting.EnableFirebaseAnalytics)
{ {
List<Parameter> parameters = new List<Parameter>(); List<Parameter> parameters = new List<Parameter>();
@ -219,6 +236,7 @@ namespace Guru
parameters.Add(new Parameter(kv.Key, decimal.ToDouble(decimalValue))); parameters.Add(new Parameter(kv.Key, decimal.ToDouble(decimalValue)));
else else
parameters.Add(new Parameter(kv.Key, kv.Value.ToString())); parameters.Add(new Parameter(kv.Key, kv.Value.ToString()));
} }
FirebaseAnalytics.LogEvent(eventName, parameters.ToArray()); FirebaseAnalytics.LogEvent(eventName, parameters.ToArray());
@ -297,16 +315,15 @@ namespace Guru
/// <param name="key"></param> /// <param name="key"></param>
/// <param name="data"></param> /// <param name="data"></param>
/// <param name="setting"></param> /// <param name="setting"></param>
/// <param name="priority"></param> public static void Track(string key, Dictionary<string, dynamic> data = null, EventSetting setting = null)
public static void Track(string key, Dictionary<string, dynamic> data = null, EventSetting setting = null, int priority = -1)
{ {
if (null != data) if (null != data)
{ {
LogEvent(key, data, setting, priority); LogEvent(key, data, setting);
} }
else else
{ {
LogEvent(key, setting, priority); LogEvent(key, setting);
} }
} }
@ -352,7 +369,7 @@ namespace Guru
} }
private static void CheckLogCache(string key, Dictionary<string, dynamic> data = null, EventSetting setting = null, int priority = -1) private static void CheckLogCache(string key, Dictionary<string, dynamic> data = null, EventSetting setting = null)
{ {
try try
{ {
@ -360,7 +377,7 @@ namespace Guru
{ {
if (data == null) data = new Dictionary<string, dynamic>(); if (data == null) data = new Dictionary<string, dynamic>();
data["log_stamp"] = TimeUtil.GetCurrentTimeStamp().ToString(); data["log_stamp"] = TimeUtil.GetCurrentTimeStamp().ToString();
SavedLogs.Enqueue(new SavedLog(key, data, setting, priority)); SavedLogs.Enqueue(new SavedLog(key, data, setting));
} }
else else
{ {
@ -370,14 +387,14 @@ namespace Guru
while (SavedLogs.Count > 0) while (SavedLogs.Count > 0)
{ {
var log = SavedLogs.Dequeue(); var log = SavedLogs.Dequeue();
LogEvent(log.key, log.data, log.setting, log.priority); LogEvent(log.key, log.data, log.setting);
} }
} }
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Crashlytics.LogException(ex); if(IsFirebaseReady) Crashlytics.LogException(ex);
} }
} }
@ -389,27 +406,18 @@ namespace Guru
internal class SavedLog internal class SavedLog
{ {
public string key; public string key;
public int priority;
public Dictionary<string, dynamic> data; public Dictionary<string, dynamic> data;
public Analytics.EventSetting setting; public Analytics.EventSetting setting;
public SavedLog() public SavedLog()
{ {
} }
/// <summary> public SavedLog(string _key, Dictionary<string, dynamic> _data = null, Analytics.EventSetting _setting = null)
/// 保存打点信息
/// </summary>
/// <param name="_key"></param>
/// <param name="_data"></param>
/// <param name="_setting"></param>
/// <param name="_priority"></param>
public SavedLog(string _key, Dictionary<string, dynamic> _data = null, Analytics.EventSetting _setting = null, int _priority = -1)
{ {
key = _key; key = _key;
data = _data; data = _data;
setting = _setting; setting = _setting;
priority = _priority;
} }
} }
} }

View File

@ -52,10 +52,7 @@ namespace Guru
try try
{ {
// return JsonMapper.ToObject<T>(jsonStr); // return JsonMapper.ToObject<T>(jsonStr);
return JsonConvert.DeserializeObject<T>(jsonStr, new JsonSerializerSettings() return JsonConvert.DeserializeObject<T>(jsonStr);
{
ObjectCreationHandling = ObjectCreationHandling.Replace,
});
} }
catch (Exception e) catch (Exception e)
{ {

View File

@ -5,6 +5,8 @@ namespace Guru
{ {
public static partial class FirebaseUtil public static partial class FirebaseUtil
{ {
public static void InitCrashlytics() public static void InitCrashlytics()
{ {
if(!string.IsNullOrEmpty(IPMConfig.IPM_UID)) if(!string.IsNullOrEmpty(IPMConfig.IPM_UID))
@ -17,6 +19,7 @@ namespace Guru
/// </summary> /// </summary>
public static void SetUserID(string userID) public static void SetUserID(string userID)
{ {
if (!IsFirebaseInitialized) return;
Crashlytics.SetUserId(userID); Crashlytics.SetUserId(userID);
} }
@ -26,6 +29,7 @@ namespace Guru
/// </summary> /// </summary>
public static void SetCustomData(string key, string value) public static void SetCustomData(string key, string value)
{ {
if (!IsFirebaseInitialized) return;
Crashlytics.SetCustomKey(key, value); Crashlytics.SetCustomKey(key, value);
} }
@ -34,6 +38,7 @@ namespace Guru
/// </summary> /// </summary>
public static void LogMessage(string message) public static void LogMessage(string message)
{ {
if (!IsFirebaseInitialized) return;
Crashlytics.Log(message); Crashlytics.Log(message);
} }
@ -42,6 +47,7 @@ namespace Guru
/// </summary> /// </summary>
public static void LogException(Exception exception) public static void LogException(Exception exception)
{ {
if (!IsFirebaseInitialized) return;
Crashlytics.LogException(exception); Crashlytics.LogException(exception);
} }
} }

View File

@ -1,17 +1,22 @@
using System; using System.Text.RegularExpressions;
using Firebase;
using Firebase.Analytics;
using Firebase.Extensions;
using UnityEngine;
namespace Guru namespace Guru
{ {
using System;
using System.Collections.Generic;
using Firebase;
using Firebase.Analytics;
using Firebase.Extensions;
using Firebase.RemoteConfig;
using Random = UnityEngine.Random;
using UnityEngine;
public static partial class FirebaseUtil public static partial class FirebaseUtil
{ {
private static readonly string LOG_TAG = "Firebase"; private static readonly string LOG_TAG = "Firebase";
private static bool _isDebug = false; private static bool _isDebug = false;
private static bool _isReady = false; private static bool _isReady = false;
public static bool IsReady => _isReady; public static bool IsReady => _isReady && IsFirebaseInitialized;
public static DependencyStatus DependencyStatus = DependencyStatus.UnavailableOther; public static DependencyStatus DependencyStatus = DependencyStatus.UnavailableOther;
public static bool IsFirebaseInitialized => DependencyStatus == DependencyStatus.Available; public static bool IsFirebaseInitialized => DependencyStatus == DependencyStatus.Available;
@ -21,14 +26,15 @@ namespace Guru
public static Action<bool> OnUserAuthResult; public static Action<bool> OnUserAuthResult;
/// <summary>
/// 初始化 Firebase 组件
/// </summary>
/// <param name="callback"></param>
/// <param name="isDebug"></param>
public static void InitFirebase(Action callback, bool isDebug = false) public static void InitFirebase(Action callback, bool isDebug = false)
{ {
_isReady = false; _isReady = false;
_isDebug = isDebug; _isDebug = isDebug;
Analytics.InitAnalytics(); // 打点提前初始化
// Loom.StartUp(); // 确保主线程开启
// 初始化 Fireabse 依赖 // 初始化 Fireabse 依赖
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWithOnMainThread(task => { FirebaseApp.CheckAndFixDependenciesAsync().ContinueWithOnMainThread(task => {
@ -46,11 +52,16 @@ namespace Guru
onInitComplete?.Invoke(_isReady); onInitComplete?.Invoke(_isReady);
}); });
} }
/// <summary>
/// 初始化 Firebase 组件
/// </summary>
private static void InitializeFirebaseComp() private static void InitializeFirebaseComp()
{ {
InitCrashlytics(); // 老项目沿用此逻辑 InitCrashlytics(); // 老项目沿用此逻辑
InitRemoteConfig(); // 老项目沿用此逻辑 InitRemoteConfig(); // 老项目沿用此逻辑
InitAdjustService(); // 初始化 Firebase 服务 InitAssetByFirebaseIdAsync(); // 获取到 FirebaseID 后异步执行逻辑
if (IPMConfig.IPM_UID.IsNullOrEmpty()) if (IPMConfig.IPM_UID.IsNullOrEmpty())
{ {
@ -93,6 +104,36 @@ namespace Guru
} }
} }
private static void InitAssetByFirebaseIdAsync()
{
Debug.Log($"[SDK] --- InitAssetByFirebaseIdAsync");
// 异步获取 FirebaseID
FirebaseAnalytics.GetAnalyticsInstanceIdAsync()
.ContinueWithOnMainThread(task =>
{
string fid = task.Result;
if (task.IsCompleted && !string.IsNullOrEmpty(fid))
{
// 保存本地ID备份
IPMConfig.FIREBASE_ID = fid; // 保存FirebaseID
Debug.Log($"[SDK] --- Get FirebaseID: {fid}");
GuruAnalytics.SetFirebaseId(fid);
}
else
{
Debug.LogError($"[SDK] --- Fetch FirebaseID failed on start!");
}
//--- 结束后启动相关的服务 ---
InitAdjustService(); // 启动 AdjustService
InitAnalytics(); // 初始化打点逻辑和实现
});
}
#region 启动 Adjust 服务 #region 启动 Adjust 服务
/// <summary> /// <summary>
@ -100,57 +141,40 @@ namespace Guru
/// </summary> /// </summary>
private static void InitAdjustService() private static void InitAdjustService()
{ {
FirebaseAnalytics.GetAnalyticsInstanceIdAsync() // 启动 AdjustService
.ContinueWithOnMainThread(task => string appToken = GuruSettings.Instance.AdjustSetting?.GetAppToken() ?? "";
{ string fbAppId = GuruSettings.Instance.IPMSetting.FacebookAppId;
if (task.IsCompleted && !string.IsNullOrEmpty(task.Result)) AdjustService.StartService(appToken, fbAppId);
{
// 保存本地ID备份
string fid = task.Result;
IPMConfig.FIREBASE_ID = fid; // 保存FirebaseID
}
else
{
UnityEngine.Debug.LogError($"Fetch FirebaseID failed on start!");
}
// 启动 AdjustService
string appToken = GuruSettings.Instance.AdjustSetting?.GetAppToken() ?? "";
string fbAppId = GuruSettings.Instance.IPMSetting.FacebookAppId;
if (!string.IsNullOrEmpty(IPMConfig.ADJUST_ID))
{
ReportAdjustId(IPMConfig.ADJUST_ID); // 二次启动后,若有值则立即上报属性
}
AdjustService.StartService(appToken, fbAppId, adjustId =>
{
// 获取 ADID
if (string.IsNullOrEmpty(adjustId))
{
adjustId = "not_set";
}
else
{
IPMConfig.ADJUST_ID = adjustId;
}
ReportAdjustId(adjustId);
});
});
} }
private static void ReportAdjustId(string adjustId)
{
FirebaseAnalytics.SetUserProperty("adjust_id", adjustId); // 仅上报 Firebase 用户属性
Debug.Log($"[SDK] --- Firebase + Adjust ID: {adjustId}");
}
#endregion #endregion
#region Android 自打点特殊启动方式
// Firebase 初始化才能调用此方法
private static void InitAnalytics()
{
#if UNITY_ANDROID
// Android 平台开始
var fid = IPMConfig.FIREBASE_ID;// 获取 FirebaseID
var enableErrorLog = true; // 开启日志上报
// 随机抽取本地分组获取数据
GuruAnalyticsExp.GetGuruAnalyticsExpParams(out string groupId, out string baseUrl, out string[] uploadIpAddress, out bool isEnable);
// 初始化打点和自打点
Analytics.InitAnalytics(baseUrl, uploadIpAddress, isEnable, enableErrorLog, fid); // 打点提前初始化
Analytics.SetUserProperty(GuruAnalyticsExp.KEY_GURU_ANALYTICS_EXP, groupId); // 上报分组数据
#else
// iOS 不参与分组
Analytics.InitAnalytics();
Analytics.SetUserProperty(GuruAnalyticsExp.KEY_GURU_ANALYTICS_EXP, "none"); // 上报分组数据
#endif
}
#endregion
} }
} }

View File

@ -50,7 +50,6 @@ namespace Guru
{ {
GenDeviceId(); GenDeviceId();
} }
return SavedDeviceId; // 优先使用缓存的 DeviceID return SavedDeviceId; // 优先使用缓存的 DeviceID
} }
} }

View File

@ -72,7 +72,7 @@ namespace Guru
$"{nameof(language)}: {language}, {nameof(locale)}: {locale}, {nameof(deviceToken)}: {deviceToken}, {nameof(deviceType)}: {deviceType}, " + $"{nameof(language)}: {language}, {nameof(locale)}: {locale}, {nameof(deviceToken)}: {deviceToken}, {nameof(deviceType)}: {deviceType}, " +
$"{nameof(pushType)}: {pushType}, {nameof(appIdentifier)}: {appIdentifier}, {nameof(appVersion)}: {appVersion}, {nameof(brand)}: {brand}, " + $"{nameof(pushType)}: {pushType}, {nameof(appIdentifier)}: {appIdentifier}, {nameof(appVersion)}: {appVersion}, {nameof(brand)}: {brand}, " +
$"{nameof(model)}: {model}, {nameof(timezone)}: {timezone}, {nameof(pushNotificationEnable)}: {pushNotificationEnable}, " + $"{nameof(model)}: {model}, {nameof(timezone)}: {timezone}, {nameof(pushNotificationEnable)}: {pushNotificationEnable}, " +
$"{nameof(firebaseAppInstanceId)}: {firebaseAppInstanceId}, {nameof(idfa)}: {idfa}, {nameof(adid)}: {adid}, {nameof(gpsAdid)}: {gpsAdid}, {nameof(userUuid)}: {userUuid}"; $"{nameof(firebaseAppInstanceId)}: {firebaseAppInstanceId}, {nameof(idfa)}: {idfa}, {nameof(adid)}: {adid}, {nameof(gpsAdid)}: {gpsAdid}";
} }
} }

View File

@ -34,14 +34,19 @@ namespace Guru
{ {
try try
{ {
Debug.Log($"[IAP] --- Apple Order Response: {response}");
ResponseData<OrderResponse> responseData = JsonUtility.FromJson<ResponseData<OrderResponse>>(response); ResponseData<OrderResponse> responseData = JsonUtility.FromJson<ResponseData<OrderResponse>>(response);
if (responseData != null && responseData.data != null) if (responseData != null && responseData.data != null)
{ {
double usdPrice = responseData.data.usdPrice; double usdPrice = responseData.data.usdPrice;
bool isTest = responseData.data.test; string productId = orderData.productId;
Analytics.ReportIAPSuccessEvent(orderData, usdPrice, isTest); // Analytics.Tch001IAPRev(usdPrice, productId, orderData.orderId, orderData.OrderType(), orderData.payedDate);
// Analytics.Tch02IAPRev(usdPrice);
//
// AdjustService.TrackSubPurchase(usdPrice, productId);
// Analytics.SubPurchase(usdPrice, productId);
Analytics.ReportIAPSuccessEvent(usdPrice, productId, orderData);
} }
} }
catch (Exception ex) catch (Exception ex)

View File

@ -19,7 +19,7 @@ namespace Guru
{ {
DeviceData deviceData = new DeviceData(); DeviceData deviceData = new DeviceData();
deviceData.pushNotificationEnable = _isPushEnabled; deviceData.pushNotificationEnable = _isPushEnabled;
UnityEngine.Debug.Log($"[SDK] --- Send DeviceData:{deviceData}"); this.Log($"send deviceData:{deviceData}");
var request = new UnityWebRequest(RequestURL, "POST"); var request = new UnityWebRequest(RequestURL, "POST");
request.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(JsonUtility.ToJson(deviceData))); request.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(JsonUtility.ToJson(deviceData)));
request.downloadHandler = new DownloadHandlerBuffer(); request.downloadHandler = new DownloadHandlerBuffer();
@ -31,10 +31,10 @@ namespace Guru
protected override void RequestSuccessCallBack(string response) protected override void RequestSuccessCallBack(string response)
{ {
UnityEngine.Debug.Log("[SDK] --- Send DeviceData Success"); this.Log("@@@ Send OK!");
IPMConfig.IS_UPLOAD_DEVICE_SUCCESS = true; IPMConfig.IS_UPLOAD_DEVICE_SUCCESS = true;
} }
/// <summary> /// <summary>
/// 设置是否打开推送 /// 设置是否打开推送
/// </summary> /// </summary>

View File

@ -31,14 +31,21 @@ namespace Guru
{ {
try try
{ {
Debug.Log($"[IAP] --- Google Order Response: {response}");
ResponseData<OrderResponse> responseData = JsonUtility.FromJson<ResponseData<OrderResponse>>(response); ResponseData<OrderResponse> responseData = JsonUtility.FromJson<ResponseData<OrderResponse>>(response);
if (responseData != null && responseData.data != null) if (responseData != null && responseData.data != null)
{ {
double usdPrice = responseData.data.usdPrice; double usdPrice = responseData.data.usdPrice;
bool isTest = responseData.data.test; string productId = orderData.RealProductId;
Analytics.ReportIAPSuccessEvent(orderData, usdPrice, isTest); // Analytics.Tch001IAPRev(usdPrice, productId, orderId, orderType, orderDate); // TCH 001
// // Analytics.Tch02IAPRev(usdPrice, productId, orderId, orderTypeString, timestamp);
// Analytics.Tch02IAPRev(usdPrice); // TCH 020
//
// // Adjust Track IAP Purchase
// AdjustService.TrackIAPPurchase(usdPrice, productId); // 上报 IAP 支付事件
// Analytics.IAPPurchase(usdPrice, productId);
Analytics.ReportIAPSuccessEvent(usdPrice, productId, orderData);
} }
} }
catch (Exception ex) catch (Exception ex)

View File

@ -6,12 +6,10 @@ namespace Guru
public class OrderResponse public class OrderResponse
{ {
public double usdPrice; public double usdPrice;
public bool test;
public string state;
public override string ToString() public override string ToString()
{ {
return $"{nameof(usdPrice)}: {usdPrice} {nameof(test)}: {test} {nameof(state)}: {state}"; return $"{nameof(usdPrice)}: {usdPrice}";
} }
} }
} }

View File

@ -101,7 +101,7 @@ namespace Guru
public static void LogException(Exception ex) public static void LogException(Exception ex)
{ {
if (!_isReady) if (!_isReady || !FirebaseUtil.IsReady)
{ {
Install(); Install();
_expCache.Enqueue(ex); _expCache.Enqueue(ex);
@ -119,7 +119,7 @@ namespace Guru
public static void Log(string msg) public static void Log(string msg)
{ {
if (!_isReady) return; if (!_isReady) return;
Crashlytics.Log(msg); if(FirebaseUtil.IsReady) Crashlytics.Log(msg);
// CheckSetUser(); // CheckSetUser();
} }
@ -127,7 +127,7 @@ namespace Guru
public static void SetCustomKey(string key, string value) public static void SetCustomKey(string key, string value)
{ {
if (!_isReady) return; if (!_isReady) return;
Crashlytics.SetCustomKey(key, value); if(FirebaseUtil.IsReady) Crashlytics.SetCustomKey(key, value);
} }

View File

@ -99,22 +99,22 @@ namespace Guru
UpdateView(); // 刷新视图 UpdateView(); // 刷新视图
} }
// 字段缓冲
private StringBuilder _infoBuff;
private string CreateMonitorInfo() private string CreateMonitorInfo()
{ {
string msg = ""; string msg = "";
bool loaded = false; bool loaded = false;
StringBuilder sb = new StringBuilder();
if (!ADService.Instance.IsInitialized) if (!ADService.Instance.IsInitialized)
{ {
msg = ColoredText("AdService not initialized...", Consts.ColorRed); msg = ColoredText("AdService not initialized...", Consts.ColorRed);
return msg; return msg;
} }
if (_infoBuff == null) _infoBuff = new StringBuilder();
_infoBuff.Clear();
if (_curBadsInfo == null) if (_curBadsInfo == null)
{ {
msg = $"BADS: {ColoredText("not ready", Consts.ColorRed)}\n"; msg = $"BADS: {ColoredText("not ready", Consts.ColorRed)}\n";
@ -137,7 +137,7 @@ namespace Guru
msg = $"BADS: {ColoredText("loading...", Consts.ColorYellow)}\n\tformat: {_curBadsInfo.format}\n"; msg = $"BADS: {ColoredText("loading...", Consts.ColorYellow)}\n\tformat: {_curBadsInfo.format}\n";
break; break;
case AdStatusType.Paid: case AdStatusType.Paid:
msg = $"BADS: {ColoredText("display", Consts.ColorGreen)}\n\tnetwork: {_curBadsInfo.network}\n\trevenue: {_curBadsInfo.revenue}\n"; msg = $"BADS: {ColoredText("display", Consts.ColorGreen)}\n\tnetwork: {_curIadsInfo.network}\n\trevenue: {_curBadsInfo.revenue}\n";
break; break;
case AdStatusType.NotReady: case AdStatusType.NotReady:
msg = $"BADS: {ColoredText("not ready", Consts.ColorGray)}\n\t{ColoredText("---", Consts.ColorGray)}\n"; msg = $"BADS: {ColoredText("not ready", Consts.ColorGray)}\n\t{ColoredText("---", Consts.ColorGray)}\n";
@ -147,7 +147,7 @@ namespace Guru
break; break;
} }
} }
_infoBuff.Append(msg); sb.Append(msg);
if (_curIadsInfo == null) if (_curIadsInfo == null)
@ -159,7 +159,7 @@ namespace Guru
switch (_curIadsInfo.status) switch (_curIadsInfo.status)
{ {
case AdStatusType.Loaded: case AdStatusType.Loaded:
msg = $"IADS: {ColoredText("loaded", Consts.ColorGreen)}\n\tnetwork: {_curIadsInfo.network}\n\twaterfall: {_curIadsInfo.waterfall}\n"; msg = $"IADS: {ColoredText("loaded", Consts.ColorGreen)}\n\tnetwork: {_curIadsInfo.network}\n\twaterfall: {_curBadsInfo.waterfall}\n";
break; break;
case AdStatusType.LoadFailed: case AdStatusType.LoadFailed:
msg = $"IADS: {ColoredText("loading failed", Consts.ColorRed)}\n\tmessage: {_curIadsInfo.info}\n"; msg = $"IADS: {ColoredText("loading failed", Consts.ColorRed)}\n\tmessage: {_curIadsInfo.info}\n";
@ -181,7 +181,7 @@ namespace Guru
break; break;
} }
} }
_infoBuff.Append(msg); sb.Append(msg);
if (_curRadsInfo == null) if (_curRadsInfo == null)
@ -215,11 +215,10 @@ namespace Guru
break; break;
} }
} }
_infoBuff.Append(msg); sb.Append(msg);
return _infoBuff.ToString(); return sb.ToString();
} }

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 1fef034d48ac449fb99531b40139954e
timeCreated: 1718844748

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 8c0dd2d4b63445c2828e05c10274d672
timeCreated: 1718845536

View File

@ -1,178 +0,0 @@
#if UNITY_ANDROID
namespace Guru.Notification
{
using System;
using System.IO;
using UnityEditor.Android;
using System.Xml;
public class PostCustomActivity: IPostGenerateGradleAndroidProject
{
private const int POST_ORDER = 10;
private const string K_PREMISSION_POST_NOTIFICATIONS = "android.permission.POST_NOTIFICATIONS";
private const string K_CUSTOM_NOTIFICATION_ACTIVITY = "custom_notification_android_activity";
private const string V_DEFAULT_GURU_ACTIVITY = "com.google.firebase.messaging.MessageForwardingService";
const string K_ANDROID_NAMESPACE_URI = "http://schemas.android.com/apk/res/android";
public int callbackOrder => POST_ORDER;
public void OnPostGenerateGradleAndroidProject(string path)
{
SetupAndroidManifest(path);
}
/// <summary>
/// 设置 Android Manifest
/// </summary>
/// <param name="projectPath"></param>
/// <exception cref="FileNotFoundException"></exception>
private void SetupAndroidManifest(string projectPath)
{
var manifestPath = $"{projectPath}/src/main/AndroidManifest.xml";
if (!File.Exists(manifestPath))
throw new FileNotFoundException($"'{manifestPath}' doesn't exist.");
XmlDocument manifestDoc = new XmlDocument();
manifestDoc.Load(manifestPath);
InjectAndroidManifest(manifestPath, manifestDoc);
manifestDoc.Save(manifestPath);
}
internal static void InjectAndroidManifest(string manifestPath, XmlDocument manifestDoc)
{
string mainActivity = GetLauncherActivity(manifestDoc);
AppendAndroidMetadataField(manifestPath, manifestDoc, K_CUSTOM_NOTIFICATION_ACTIVITY, mainActivity);
AppendAndroidPermissionField(manifestPath, manifestDoc, K_PREMISSION_POST_NOTIFICATIONS);
UnityEngine.Debug.Log($"<color=#88ff00>Add custom notification activity: {mainActivity} success!!</color>");
}
internal static void AppendAndroidPermissionField(string manifestPath, XmlDocument xmlDoc, string name, string maxSdk = null)
{
AppendAndroidPermissionField(manifestPath, xmlDoc, "uses-permission", name, maxSdk);
}
internal static void AppendAndroidPermissionField(string manifestPath, XmlDocument xmlDoc, string tagName, string name, string maxSdk)
{
var manifestNode = xmlDoc.SelectSingleNode("manifest");
if (manifestNode == null)
throw new ArgumentException(string.Format("Missing 'manifest' node in '{0}'.", manifestPath));
XmlElement metaDataNode = null;
foreach (XmlNode node in manifestNode.ChildNodes)
{
if (!(node is XmlElement) || node.Name != tagName)
continue;
var element = (XmlElement)node;
var elementName = element.GetAttribute("name", K_ANDROID_NAMESPACE_URI);
if (elementName == name)
{
if (maxSdk == null)
return;
var maxSdkAttr = element.GetAttribute("maxSdkVersion", K_ANDROID_NAMESPACE_URI);
if (!string.IsNullOrEmpty(maxSdkAttr))
return;
metaDataNode = element;
}
}
if (metaDataNode == null)
{
metaDataNode = xmlDoc.CreateElement(tagName);
metaDataNode.SetAttribute("name", K_ANDROID_NAMESPACE_URI, name);
}
if (maxSdk != null)
metaDataNode.SetAttribute("maxSdkVersion", K_ANDROID_NAMESPACE_URI, maxSdk);
manifestNode.AppendChild(metaDataNode);
}
internal static void AppendAndroidMetadataField(string manifestPath, XmlDocument xmlDoc, string name, string value)
{
var applicationNode = xmlDoc.SelectSingleNode("manifest/application");
if (applicationNode == null)
throw new ArgumentException(string.Format("Missing 'application' node in '{0}'.", manifestPath));
var nodes = xmlDoc.SelectNodes("manifest/application/meta-data");
if (nodes != null)
{
// Check if there is a 'meta-data' with the same name.
foreach (XmlNode node in nodes)
{
var element = node as XmlElement;
if (element == null)
continue;
var elementName = element.GetAttribute("name", K_ANDROID_NAMESPACE_URI);
if (elementName == name)
{
element.SetAttribute("value", K_ANDROID_NAMESPACE_URI, value);
return;
}
}
}
XmlElement metaDataNode = xmlDoc.CreateElement("meta-data");
metaDataNode.SetAttribute("name", K_ANDROID_NAMESPACE_URI, name);
metaDataNode.SetAttribute("value", K_ANDROID_NAMESPACE_URI, value);
applicationNode.AppendChild(metaDataNode);
}
internal static string GetLauncherActivity(XmlDocument xmlDoc)
{
var applicationNode = xmlDoc.SelectSingleNode("manifest/application");
if (applicationNode == null)
throw new ArgumentException($"Missing 'application' node in doc.");
var nodes = xmlDoc.SelectNodes("manifest/application/activity");
if (nodes != null)
{
foreach (XmlNode node in nodes)
{
var activityNode = node as XmlElement;
if (activityNode == null)
continue;
if (activityNode.HasChildNodes)
{
var intentFilterNode = activityNode.SelectSingleNode("intent-filter");
if(intentFilterNode == null || !intentFilterNode.HasChildNodes)
continue;
foreach (XmlElement childNode in intentFilterNode)
{
if(childNode == null) continue;
// 判断 action/category 二者取其一
if (childNode.Name == "action" && childNode.InnerXml.Contains("android.intent.action.MAIN"))
{
var activityName = activityNode.GetAttribute("name", K_ANDROID_NAMESPACE_URI);
return activityName;
}
if (childNode.Name == "category" && childNode.InnerXml.Contains("android.intent.category.LAUNCHER"))
{
var activityName = activityNode.GetAttribute("name", K_ANDROID_NAMESPACE_URI);
return activityName;
}
}
}
}
}
return V_DEFAULT_GURU_ACTIVITY;
}
}
}
#endif

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 2e6de94961cc47abbabbc7e872085a98
timeCreated: 1718934648

View File

@ -1,6 +0,0 @@
{
"name": "GuruNotification.Editor",
"includePlatforms": [
"Editor"
]
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: e75025463d0c436482bf4c3dab674315
timeCreated: 1718845553

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: d6dd827f370441718ca2e49f3f603e4e
timeCreated: 1718845525

View File

@ -1,15 +0,0 @@
namespace Guru.Notification
{
using System;
public interface INotificationAgent
{
void Init();
string GetStatus();
bool IsAllowed();
void RequestPermission(Action<string> callback = null);
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 43e051a65e9b469b8b9e8a9f6b4be944
timeCreated: 1718844775

View File

@ -1,252 +0,0 @@
namespace Guru.Notification
{
using System;
using UnityEngine;
#if UNITY_ANDROID
using UnityEngine.Android;
using Unity.Notifications.Android;
#endif
public class NotificationAgentAndroid : INotificationAgent
{
public const string FCM_DEFAULT_CHANNEL_ID = "fcm_default_channel";
private const string STATUS_GRANTED = "granted";
private const string STATUS_DENIDED = "denied";
// private const string STATUS_NOT_DETERMINED = "not_determined";
private const int REQUEST_PERMISSION_SDK_VERSION = 33;
private const string PERMISSION_POST_NOTIFICATION = "android.permission.POST_NOTIFICATIONS";
private bool _initOnce = false;
private string _notiStatus;
private string SavedNotiPermStatus
{
get => PlayerPrefs.GetString(nameof(SavedNotiPermStatus), "");
set => PlayerPrefs.SetString(nameof(SavedNotiPermStatus), value);
}
/// <summary>
/// 初始化
/// </summary>
public void Init()
{
if (!_initOnce) return;
_initOnce = true;
_notiStatus = STATUS_DENIDED;
if (!string.IsNullOrEmpty(SavedNotiPermStatus))
{
_notiStatus = SavedNotiPermStatus;
}
#if UNITY_ANDROID
InitPlugins();
#endif
}
/// <summary>
/// 获取状态
/// </summary>
/// <returns></returns>
public string GetStatus()
{
if (!_initOnce) Init();
#if UNITY_ANDROID
UpdateNotiStatus();
#endif
return _notiStatus;
}
/// <summary>
/// 设置授权状态
/// </summary>
/// <param name="status">授权状态</param>
private void SetGrantStatus(string status)
{
_notiStatus = status;
SavedNotiPermStatus = status;
}
public bool IsAllowed()
{
return _notiStatus == STATUS_GRANTED;
}
public void RequestPermission(Action<string> callback = null)
{
#if UNITY_ANDROID
RequestAndroidPermission(callback);
#endif
}
// -------------------- Android 获取状态逻辑 --------------------
#if UNITY_ANDROID
private PermissionStatus _permissionStatus;
private void TryExecute(Action handler)
{
try
{
handler?.Invoke();
}
catch (Exception ex)
{
Debug.LogError(ex);
}
}
/// <summary>
/// 初始化插件
/// </summary>
private void InitPlugins()
{
AndroidNotificationCenter.Initialize();
Debug.Log($"[Noti][AND] --- Notification Service InitPlugins");
UpdateNotiStatus();
}
/// <summary>
/// 更新 Notification 状态码
/// </summary>
private void UpdateNotiStatus()
{
TryExecute(() =>
{
_permissionStatus = AndroidNotificationCenter.UserPermissionToPost;
var status = "";
switch (_permissionStatus)
{
// case PermissionStatus.NotRequested:
// _notiStatus = STATUS_NOT_DETERMINED;
// break;
case PermissionStatus.Allowed:
status = STATUS_GRANTED;;
break;
default:
status = STATUS_DENIDED;
break;
}
SetGrantStatus(status);
Debug.LogWarning($"[SDK][AND] --- UpdateNotiStatus:{_notiStatus} | UserPermissionToPost:{_permissionStatus}");
});
}
private Action<string> _onPermissionCallback;
private PermissionCallbacks _permissionCallbacks;
private void RequestAndroidPermission(Action<string> callback = null)
{
UpdateNotiStatus();
if (_notiStatus == STATUS_GRANTED)
{
callback?.Invoke(_notiStatus);
return;
}
_onPermissionCallback = callback;
TryExecute(() =>
{
var sdkInt = GetAndroidSDKVersion();
if (sdkInt < REQUEST_PERMISSION_SDK_VERSION)
{
// 低版本处理方式
Debug.Log($"[SDK][Noti] --- #2 SDK {sdkInt} not requested -> open channel");
AndroidNotificationCenter.RegisterNotificationChannel(new AndroidNotificationChannel(FCM_DEFAULT_CHANNEL_ID,
FCM_DEFAULT_CHANNEL_ID, "", Importance.Default)); // 打开ChannelID
SetGrantStatus(STATUS_GRANTED);
}
else
{
// SDK 33 以上,请求弹窗
bool hasPermission = Permission.HasUserAuthorizedPermission(PERMISSION_POST_NOTIFICATION);
if (hasPermission)
{
SetGrantStatus(STATUS_GRANTED);
callback?.Invoke(STATUS_GRANTED);
return;
}
Debug.Log($"[SDK][Noti] --- #3 SDK {sdkInt} :: Ask Post Permission");
Permission.RequestUserPermission(PERMISSION_POST_NOTIFICATION, SetupPermissionCallbacks());
}
});
}
private PermissionCallbacks SetupPermissionCallbacks()
{
if(_permissionCallbacks != null) DisposePermissionCallbacks();
_permissionCallbacks = new PermissionCallbacks();
_permissionCallbacks.PermissionGranted += OnPermissionGranted;
_permissionCallbacks.PermissionDenied += OnPermissionDenied;
_permissionCallbacks.PermissionDeniedAndDontAskAgain += OnPermissionDenied;
return _permissionCallbacks;
}
private void DisposePermissionCallbacks()
{
if (_permissionCallbacks != null)
{
_permissionCallbacks.PermissionGranted -= OnPermissionGranted;
_permissionCallbacks.PermissionDenied -= OnPermissionDenied;
_permissionCallbacks.PermissionDeniedAndDontAskAgain -= OnPermissionDenied;
_permissionCallbacks = null;
}
}
/// <summary>
/// 请求通过
/// </summary>
/// <param name="permissionName"></param>
private void OnPermissionGranted(string permissionName)
{
if (permissionName == PERMISSION_POST_NOTIFICATION)
{
_notiStatus = STATUS_GRANTED;
_onPermissionCallback?.Invoke(_notiStatus);
}
SetGrantStatus(STATUS_GRANTED);
DisposePermissionCallbacks();
}
/// <summary>
/// 请求拒绝
/// </summary>
/// <param name="permissionName"></param>
private void OnPermissionDenied(string permissionName)
{
if (permissionName == PERMISSION_POST_NOTIFICATION)
{
_notiStatus = STATUS_DENIDED;
_onPermissionCallback?.Invoke(_notiStatus);
}
SetGrantStatus(STATUS_DENIDED);
DisposePermissionCallbacks();
}
private int GetAndroidSDKVersion()
{
int sdkInt = 999;
TryExecute(() =>
{
using (AndroidJavaClass jc = new AndroidJavaClass("android.os.Build$VERSION"))
{
sdkInt = jc.GetStatic<int>("SDK_INT");
Debug.LogWarning($"[SDK] --- Android SDK Version:{sdkInt}");
}
});
return sdkInt;
}
#endif
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 554fcea56ce74a80a2424e5c037ce6c0
timeCreated: 1718844764

View File

@ -1,151 +0,0 @@
namespace Guru.Notification
{
using System;
using UnityEngine;
#if UNITY_IOS
using System.Threading.Tasks;
using Unity.Notifications.iOS;
#endif
public class NotificationAgentIOS : INotificationAgent
{
private const string STATUS_GRANTED = "granted";
private const string STATUS_DENIDED = "denied";
private const string STATUS_PROVISIONAL = "provisional";
private const string STATUS_NOT_DETERMINED = "not_determined";
private static bool _initOnce;
private static int _waitSeconds = 30;
private string SavedNotiPermStatus
{
get => PlayerPrefs.GetString(nameof(SavedNotiPermStatus), "");
set => PlayerPrefs.SetString(nameof(SavedNotiPermStatus), value);
}
private string _notiStatus;
public void Init()
{
if (_initOnce) return;
_initOnce = true;
_notiStatus = SavedNotiPermStatus;
if (string.IsNullOrEmpty(_notiStatus))
_notiStatus = STATUS_NOT_DETERMINED;
#if UNITY_IOS
InitPlugins();
#endif
}
public string GetStatus()
{
if (!_initOnce) Init();
#if UNITY_IOS
UpdateStatus();
#endif
return _notiStatus;
}
public bool IsAllowed()
{
return _notiStatus == STATUS_GRANTED;
}
public void RequestPermission(Action<string> callback = null)
{
if (!_initOnce) Init();
if (_notiStatus == STATUS_GRANTED || _notiStatus == STATUS_DENIDED)
{
Debug.Log($"[SDK][Noti][iOS] --- Already has Status: {_notiStatus}");
callback?.Invoke(_notiStatus); // 已获得授权, 直接返回结果
return;
}
#if UNITY_IOS
RequestIOSPermission(callback);
#endif
}
#if UNITY_IOS
private void InitPlugins()
{
UpdateStatus();
}
/// <summary>
/// 更新状态
/// </summary>
private void UpdateStatus()
{
string status = STATUS_NOT_DETERMINED;
var authorizationStatus = iOSNotificationCenter.GetNotificationSettings().AuthorizationStatus;
switch (authorizationStatus)
{
case AuthorizationStatus.Authorized:
status = STATUS_GRANTED;
break;
case AuthorizationStatus.Denied:
status = STATUS_DENIDED;
break;
case AuthorizationStatus.NotDetermined:
status = STATUS_NOT_DETERMINED;
break;
case AuthorizationStatus.Provisional:
status = STATUS_PROVISIONAL;
break;
default:
Debug.Log($"[SDK][Noti][iOS] --- Unmarked AuthorizationStatus: {status}");
break;
}
SetGrantStatus(status);
}
private void SetGrantStatus(string status)
{
_notiStatus = status;
SavedNotiPermStatus = status;
}
/// <summary>
/// 请求 IOS 的推送
/// </summary>
/// <param name="callback"></param>
private async void RequestIOSPermission(Action<string> callback = null)
{
Debug.Log($"[SDK][Noti][iOS] --- RequestIOSPermission start");
int timePassed = 0;
using (var req = new AuthorizationRequest(AuthorizationOption.Alert | AuthorizationOption.Badge, true))
{
while (!req.IsFinished && timePassed < _waitSeconds)
{
timePassed++;
await Task.Delay(1000);
};
if (timePassed >= _waitSeconds)
{
Debug.LogWarning($"[SDK][Noti][iOS] --- RequestIOSPermission timeout");
}
UpdateStatus();
callback?.Invoke(_notiStatus);
Debug.Log($"[SDK][Noti][iOS] --- User Selected: {_notiStatus}");
}
}
#endif
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 5068c6e238e74251955320761c96182b
timeCreated: 1718871877

View File

@ -1,58 +0,0 @@
namespace Guru.Notification
{
using System;
using System.Threading.Tasks;
using UnityEngine;
/// <summary>
/// For Editor to use Notifications
/// </summary>
public class NotificationAgentStub: INotificationAgent
{
private const string STATUS_GRANTED = "granted";
private const string STATUS_DENIDED = "denied";
private const string STATUS_NOT_DETERMINED = "not_determined";
private Action<string> _onPermissionCallback;
private float _delaySeconds = 1.0f;
private string EditorGrantedStatus
{
get => PlayerPrefs.GetString(nameof(EditorGrantedStatus), STATUS_NOT_DETERMINED);
set => PlayerPrefs.SetString(nameof(EditorGrantedStatus), value);
}
public void Init()
{
Debug.Log($"[SDK][Noti][EDT] --- NotificationAgentStub Init: {EditorGrantedStatus}");
}
public string GetStatus() => EditorGrantedStatus;
public bool IsAllowed()
{
return EditorGrantedStatus == STATUS_GRANTED;
}
public void RequestPermission(Action<string> callback = null)
{
Debug.Log($"[SDK][Noti][EDT] --- RequestPermission ---");
_onPermissionCallback = callback;
DelayCallPermissionHandle();
}
/// <summary>
/// 延迟模拟回调
/// </summary>
private async void DelayCallPermissionHandle()
{
await Task.Delay((int)(1000 * _delaySeconds));
EditorGrantedStatus = STATUS_GRANTED;
_onPermissionCallback?.Invoke(EditorGrantedStatus);
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 6550e85d25634c46b7a57c490dcea173
timeCreated: 1718850440

View File

@ -1,112 +0,0 @@
namespace Guru.Notification
{
using UnityEngine;
using System;
/// <summary>
/// 消息管理器
/// </summary>
public class NotificationService
{
// 服务版本号
public const string Version = "0.0.1";
// 初始化标志位
private static bool _initOnce;
private static string DefaultPermissionStatus
{
get
{
#if UNITY_IOS
return "not_determined";
#endif
return "denied";
}
}
#region 初始化
private static INotificationAgent _agent;
internal static INotificationAgent Agent
{
get
{
if (_agent == null)
{
_agent = GetAgent();
}
return _agent;
}
}
private static INotificationAgent GetAgent()
{
#if UNITY_EDITOR
return new NotificationAgentStub();
#elif UNITY_ANDROID
return new NotificationAgentAndroid();
#elif UNITY_IOS
return new NotificationAgentIOS();
#endif
return null;
}
/// <summary>
/// 初始化
/// </summary>
public static void Initialize()
{
if (_initOnce) return;
_initOnce = true;
Agent?.Init(); // 初始化代理
}
#endregion
#region 接口
/// <summary>
/// 拉起 Noti 请求
/// </summary>
/// <param name="callback"></param>
public static void RequestPermission(Action<string> callback = null)
{
if (Agent != null)
{
Agent.RequestPermission(callback);
return;
}
Debug.LogError($"[SDK][Noti] --- Agent is missing, return default status: {DefaultPermissionStatus}");
callback?.Invoke(DefaultPermissionStatus);
}
public static bool IsPermissionGranted()
{
return Agent?.IsAllowed() ?? false;
}
public static string GetStatus()
{
if(!_initOnce) Initialize();
if(Agent != null) return Agent.GetStatus();
Debug.LogError($"[SDK][Noti] --- Agent is missing, return default status: {DefaultPermissionStatus}");
return DefaultPermissionStatus;
}
#endregion
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: e0f31750e8bc48a6a5e4f56ee2d5e1cc
timeCreated: 1718846076

View File

@ -1,8 +1,6 @@
namespace Guru namespace Guru
{ {
using System; using System;
using UnityEngine;
using Newtonsoft.Json;
public abstract class RemoteConfigBase<T>: IRemoteConfig<T> where T : IRemoteConfig<T> public abstract class RemoteConfigBase<T>: IRemoteConfig<T> where T : IRemoteConfig<T>
{ {
@ -10,8 +8,6 @@ namespace Guru
/// 配置是否可用 /// 配置是否可用
/// </summary> /// </summary>
public bool enable { get; set; } = true; public bool enable { get; set; } = true;
[JsonIgnore]
public Action<T> OnValueChanged { get; set; } public Action<T> OnValueChanged { get; set; }
/// <summary> /// <summary>
/// 转为Json /// 转为Json

View File

@ -12,7 +12,6 @@
"dependencies": { "dependencies": {
"com.unity.ads.ios-support": "1.2.0", "com.unity.ads.ios-support": "1.2.0",
"com.unity.editorcoroutines": "1.0.0", "com.unity.editorcoroutines": "1.0.0",
"com.unity.mobile.notifications": "2.2.2",
"com.unity.mobile.android-logcat": "1.3.2", "com.unity.mobile.android-logcat": "1.3.2",
"com.unity.purchasing": "4.10.0" "com.unity.purchasing": "4.10.0"
} }