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",
"rootNamespace": "",
"references": [
"Guru.Runtime",
"Guru.Notification"
"Guru.Runtime"
],
"includePlatforms": [
"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)
{
if (target != BuildTarget.iOS) return;

View File

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

View File

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

View File

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

View File

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

View File

@ -50,7 +50,7 @@ namespace Guru
* 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.
*/
public void Initialize(bool isDebug = false)
public void Initialize()
{
#if UNITY_EDITOR
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.appcompat:appcompat:1.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-ktx:2.7.1" />
<androidPackage spec="androidx.work:work-rxjava2:2.7.1" />
<androidPackage spec="androidx.work:work-runtime:2.8.1" />
<androidPackage spec="androidx.work:work-runtime-ktx:2.8.1" />
<androidPackage spec="androidx.work:work-rxjava2:2.8.1" />
<androidPackage spec="androidx.lifecycle:lifecycle-process:2.4.0" />
<androidPackage spec="com.jakewharton.timber:timber:4.7.1" />
<androidPackage spec="com.google.code.gson:gson:2.8.5" />
<androidPackage spec="androidx.room:room-runtime:2.4.3" />
<androidPackage spec="androidx.room:room-rxjava2:2.4.3" />
<androidPackage spec="androidx.room:room-runtime:2.5.1" />
<androidPackage spec="androidx.room:room-rxjava2:2.5.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: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.squareup.retrofit2:retrofit:2.7.1" />-->
</androidPackages>

View File

@ -7,29 +7,23 @@ GuruAnalyticsLib 的 Unity 插件库
- 插件库内的 .aar 通过 [guru_analytics](https://github.com/castbox/guru_analytics) 项目直接构建 ( 命令 `gradle publishToMavenLocal` )
- 构建后请改名为 `guru-analytics-{version}.aar`
- 请将 .aar 文件放置于 `./Runtime/GuruAnalytics/Plugins/Android` 目录下
- **iOS**
- **iOS**
- 插件库内的文件 通过 [GuruAnalytics_iOS](https://github.com/castbox/GuruAnalytics_iOS) 项目
- (1) 请将 repo 内的两个文件夹 `Assets``Classses` 拷贝至 `./Runtime/GuruAnalytics/Plugins/iOS/GuruAnalytics` 目录下:
- (2) 请将部署到 Unity 内所有的 `.swift` 文件的 meta 属性内, 取消 iOS 文件属性. (因为打包时会按照 POD 导入)
- 注意及时更新 `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
### 1.12.0
### 1.11.1
- Android 端对齐 `1.1.1`
> Hash: bdb41ae118dcf438e8efe4f27d0ec856bc3147b0
> Hash: 8791b946c441a081b80248aadbf65b515a8d2a10
- iOS 端对齐 `0.3.6`
> Hash: 0cd5ce7aa64e12caa7413c938a3164687b973843
- Pod 库改为 本地文件引用 (配合外部发行项目)
### 1.11.0
- Android 端对齐 `1.0.3`
> 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
{
// Plugin Version
public const string Version = "1.10.5";
public const string Version = "1.11.1";
public static readonly string Tag = "[ANU]";
private static readonly string ActionName = "logger_error";
@ -91,9 +91,54 @@ namespace Guru
_autoSyncProperties = syncProperties;
_enableErrorLog = enableErrorLog;
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(); // 激活错误日志回调
}
/// <summary>
/// 设置视图名称
/// </summary>
@ -558,11 +603,13 @@ namespace Guru
case AnalyticsCode.ERROR_ZIP:
case AnalyticsCode.ERROR_DNS_CACHE:
case AnalyticsCode.CRONET_INTERCEPTOR:
case AnalyticsCode.EVENT_LOOKUP:
case AnalyticsCode.EVENT_SESSION_ACTIVE:
canCatch = true;
break;
}
if (code > 100 && code <= 200)
if (!canCatch && code is > 100 and <= 200)
{
// 100 < code <= 200
canCatch = true;
@ -608,21 +655,24 @@ namespace Guru
{
Unknown = -1,
DELETE_EXPIRED = 12,
UPLOAD_FAIL = 14,
Network_Lost = 22,
CRONET_INIT_FAIL = 26,
CRONET_INIT_EXCEPTION = 27,
DELETE_EXPIRED = 12, // 删除过期事件
UPLOAD_FAIL = 14, // 上报事件失败
Network_Lost = 22, // 网络状态不可用
CRONET_INIT_FAIL = 26, // 开启Cronet失败
CRONET_INIT_EXCEPTION = 27, // 开启Cronet报错
ERROR_API = 101,
ERROR_RESPONSE = 102,
ERROR_CACHE_CONTROL = 103,
ERROR_DELETE_EXPIRED = 104,
ERROR_LOAD_MARK = 105,
ERROR_DNS = 106,
ERROR_ZIP = 107,
ERROR_DNS_CACHE = 108,
CRONET_INTERCEPTOR = 109,
ERROR_API = 101, // 调用api出错
ERROR_RESPONSE = 102, // api返回结果错误
ERROR_CACHE_CONTROL = 103, // 设置cacheControl出错
ERROR_DELETE_EXPIRED = 104, // 删除过期事件出错
ERROR_LOAD_MARK = 105, // 从数据库取事件以及更改事件状态为正在上报出错
ERROR_DNS = 106, // dns 错误
ERROR_ZIP = 107, // zip 错误
ERROR_DNS_CACHE = 108, // zip 错误
CRONET_INTERCEPTOR = 109, // cronet拦截器
EVENT_LOOKUP = 1003,
EVENT_SESSION_ACTIVE = 1004,
}
}

View File

@ -15,7 +15,7 @@ namespace Guru
void SetUid(string uid);
bool IsDebug { get; }
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 SetTch02Value(double value); // 设置太极02数值
void InitCallback(string objName, string method); // 设置回调对象参数

View File

@ -4,6 +4,7 @@ namespace Guru
{
using System;
using UnityEngine;
using System.Collections.Generic;
public class AnalyticsAgentAndroid: IAnalyticsAgent
{
@ -16,8 +17,6 @@ namespace Guru
#endif
private static bool _isDebug = false;
public static bool UseWorker = true;
public static bool UseCronet = false;
public static string BaseUrl = "";
#region 工具方法
@ -76,14 +75,65 @@ namespace Guru
#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)
{
_isDebug = isDebug;
string bundleId = Application.identifier;
// public static void init(String appId, String deviceInfo, String bundleId, boolean isDebug, boolean useWorker, boolean useCronet, String baseUrl)
CallStatic("init", appId, deviceInfo, bundleId, isDebug, UseWorker, UseCronet, BaseUrl); // 调用接口
bool useWorker = true;
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)
{
if (string.IsNullOrEmpty(screenName)) return;
@ -125,10 +175,7 @@ namespace Guru
}
public bool IsDebug => CallStatic<bool>("isDebug");
public void LogEvent(string eventName, string parameters, int priority = -1)
{
CallStatic("logEvent", eventName, parameters, priority);
}
public void LogEvent(string eventName, string parameters, int priority = 0) => CallStatic("logEvent", eventName, parameters, priority);
public void ReportEventSuccessRate() => CallStatic("reportEventRate");
public void SetTch02Value(double value) => CallStatic("setTch02Value", value);
public void InitCallback(string objName, string method) => CallStatic("initCallback", objName, method);

View File

@ -122,7 +122,7 @@ namespace Guru
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
unityLogEvent(eventName, data);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -224,17 +224,17 @@ namespace Guru
/// </summary>
/// <param name="status"></param>
/// <param name="channel"></param>
/// <param name="scene"></param>
public static void AttResult(string status, string type = "custom", string scene = "")
/// <param name="others"></param>
public static void AttResult(string status, string type = "custom", string others = "")
{
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>()
{
{ ParameterItemCategory, status },
{ "type", type }
};
if(!string.IsNullOrEmpty(scene)) dict[ParameterItemName] = scene;
if(!string.IsNullOrEmpty(others)) dict[ParameterItemName] = others;
LogEvent(EventATTResult, dict);
}

View File

@ -6,6 +6,7 @@ namespace Guru
fail,
timeout,
exit,
replay
}
//打点常量定义

View File

@ -33,11 +33,19 @@ namespace Guru
/// <summary>
/// 初始化Guru自打点系统 (请优先于 Firebase 初始化调用)
/// </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;
Debug.Log($"{TAG} --- InstallGuruAnalytics baseUrl: {baseUrl} enableErrorLog: {enableErrorLog} firebaseId:{firebaseId}");
try
{
#if UNITY_EDITOR
@ -47,7 +55,6 @@ namespace Guru
#endif
string appId = IPMConfig.IPM_X_APP_ID;
string deviceInfo = new DeviceInfoData().ToString();
GuruAnalytics.Init(appId, deviceInfo, IsDebug, enableErrorLog); // 初始化(带Header)
_hasGotFirebaseId = false;
_hasGotAdId = false;
@ -57,6 +64,9 @@ namespace Guru
_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;
@ -67,6 +77,7 @@ namespace Guru
}
}
#region 各ID上报信息
/// <summary>
@ -80,7 +91,7 @@ namespace Guru
{
Debug.Log($"---[ANA] UID: {IPMConfig.IPM_UID}");
GuruAnalytics.SetUid(IPMConfig.IPM_UID);
FirebaseAnalytics.SetUserProperty(PropertyUserID, IPMConfig.IPM_UID);
FirebaseSetUserProperty(PropertyUserID, IPMConfig.IPM_UID);
_hasGotUid = true;
}
@ -96,7 +107,7 @@ namespace Guru
if (!string.IsNullOrEmpty(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;
}
}
@ -185,6 +196,8 @@ namespace Guru
/// </summary>
private static void FetchFirebaseId()
{
if (!IsFirebaseReady) return;
FirebaseAnalytics.GetAnalyticsInstanceIdAsync()
.ContinueWithOnMainThread(task =>
{
@ -250,6 +263,8 @@ namespace Guru
/// </summary>
private static void UpdateAllValues()
{
Debug.Log($"{TAG} --- UpdateAllValues");
SetUid();
SetDeviceId();
SetAdjustId();
@ -297,9 +312,16 @@ namespace Guru
GuruAnalytics.SetUserProperty(key, value);
UpdateAllValues(); // 同步所有的ID
}
catch (Exception e)
catch (Exception ex)
{
Crashlytics.LogException(e);
if (IsFirebaseReady)
{
Crashlytics.LogException(ex);
}
else
{
Debug.LogException(ex);
}
}
}
@ -309,18 +331,25 @@ namespace Guru
/// </summary>
/// <param name="key"></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
{
if (data == null) data = new Dictionary<string, dynamic>();
GuruAnalytics.LogEvent(key, data, priority);
GuruAnalytics.LogEvent(key, data);
UpdateAllValues(); // 同步所有的ID
}
catch (Exception e)
{
if (IsFirebaseReady)
{
Crashlytics.LogException(e);
}
else
{
Debug.LogWarning(e);
}
}
}
/// <summary>
@ -342,9 +371,16 @@ namespace Guru
}
}
catch (Exception e)
{
if (IsFirebaseReady)
{
Crashlytics.LogException(e);
}
else
{
Debug.LogWarning(e);
}
}
}
#endregion

View File

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

View File

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

View File

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

View File

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

View File

@ -1,17 +1,22 @@
using System;
using Firebase;
using Firebase.Analytics;
using Firebase.Extensions;
using UnityEngine;
using System.Text.RegularExpressions;
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
{
private static readonly string LOG_TAG = "Firebase";
private static bool _isDebug = 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 bool IsFirebaseInitialized => DependencyStatus == DependencyStatus.Available;
@ -21,14 +26,15 @@ namespace Guru
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)
{
_isReady = false;
_isDebug = isDebug;
Analytics.InitAnalytics(); // 打点提前初始化
// Loom.StartUp(); // 确保主线程开启
// 初始化 Fireabse 依赖
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWithOnMainThread(task => {
@ -46,11 +52,16 @@ namespace Guru
onInitComplete?.Invoke(_isReady);
});
}
/// <summary>
/// 初始化 Firebase 组件
/// </summary>
private static void InitializeFirebaseComp()
{
InitCrashlytics(); // 老项目沿用此逻辑
InitRemoteConfig(); // 老项目沿用此逻辑
InitAdjustService(); // 初始化 Firebase 服务
InitAssetByFirebaseIdAsync(); // 获取到 FirebaseID 后异步执行逻辑
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 服务
/// <summary>
@ -100,57 +141,40 @@ namespace Guru
/// </summary>
private static void InitAdjustService()
{
FirebaseAnalytics.GetAnalyticsInstanceIdAsync()
.ContinueWithOnMainThread(task =>
{
if (task.IsCompleted && !string.IsNullOrEmpty(task.Result))
{
// 保存本地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);
}
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
#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();
}
return SavedDeviceId; // 优先使用缓存的 DeviceID
}
}

View File

@ -72,7 +72,7 @@ namespace Guru
$"{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(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
{
Debug.Log($"[IAP] --- Apple Order Response: {response}");
ResponseData<OrderResponse> responseData = JsonUtility.FromJson<ResponseData<OrderResponse>>(response);
if (responseData != null && responseData.data != null)
{
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)

View File

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

View File

@ -31,14 +31,21 @@ namespace Guru
{
try
{
Debug.Log($"[IAP] --- Google Order Response: {response}");
ResponseData<OrderResponse> responseData = JsonUtility.FromJson<ResponseData<OrderResponse>>(response);
if (responseData != null && responseData.data != null)
{
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)

View File

@ -6,12 +6,10 @@ namespace Guru
public class OrderResponse
{
public double usdPrice;
public bool test;
public string state;
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)
{
if (!_isReady)
if (!_isReady || !FirebaseUtil.IsReady)
{
Install();
_expCache.Enqueue(ex);
@ -119,7 +119,7 @@ namespace Guru
public static void Log(string msg)
{
if (!_isReady) return;
Crashlytics.Log(msg);
if(FirebaseUtil.IsReady) Crashlytics.Log(msg);
// CheckSetUser();
}
@ -127,7 +127,7 @@ namespace Guru
public static void SetCustomKey(string key, string value)
{
if (!_isReady) return;
Crashlytics.SetCustomKey(key, value);
if(FirebaseUtil.IsReady) Crashlytics.SetCustomKey(key, value);
}

View File

@ -99,22 +99,22 @@ namespace Guru
UpdateView(); // 刷新视图
}
// 字段缓冲
private StringBuilder _infoBuff;
private string CreateMonitorInfo()
{
string msg = "";
bool loaded = false;
StringBuilder sb = new StringBuilder();
if (!ADService.Instance.IsInitialized)
{
msg = ColoredText("AdService not initialized...", Consts.ColorRed);
return msg;
}
if (_infoBuff == null) _infoBuff = new StringBuilder();
_infoBuff.Clear();
if (_curBadsInfo == null)
{
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";
break;
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;
case AdStatusType.NotReady:
msg = $"BADS: {ColoredText("not ready", Consts.ColorGray)}\n\t{ColoredText("---", Consts.ColorGray)}\n";
@ -147,7 +147,7 @@ namespace Guru
break;
}
}
_infoBuff.Append(msg);
sb.Append(msg);
if (_curIadsInfo == null)
@ -159,7 +159,7 @@ namespace Guru
switch (_curIadsInfo.status)
{
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;
case AdStatusType.LoadFailed:
msg = $"IADS: {ColoredText("loading failed", Consts.ColorRed)}\n\tmessage: {_curIadsInfo.info}\n";
@ -181,7 +181,7 @@ namespace Guru
break;
}
}
_infoBuff.Append(msg);
sb.Append(msg);
if (_curRadsInfo == null)
@ -215,11 +215,10 @@ namespace Guru
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
{
using System;
using UnityEngine;
using Newtonsoft.Json;
public abstract class RemoteConfigBase<T>: IRemoteConfig<T> where T : IRemoteConfig<T>
{
@ -10,8 +8,6 @@ namespace Guru
/// 配置是否可用
/// </summary>
public bool enable { get; set; } = true;
[JsonIgnore]
public Action<T> OnValueChanged { get; set; }
/// <summary>
/// 转为Json

View File

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