H5、推送SDK

This commit is contained in:
玉峰 2025-09-01 14:57:00 +08:00
parent c25bcd63e9
commit a71b171e16
19 changed files with 1489 additions and 0 deletions

View File

@ -0,0 +1,33 @@
using UnityEngine;
namespace EFSDK
{
public static class AutoSetEFSdk
{
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
public static void SetupCommunicationObject()
{
try
{
GameObject communicationObject = new GameObject("EFSdkAndroid");
if (communicationObject != null)
{
EFSdkAndroid communicationComponent = communicationObject.AddComponent<EFSdkAndroid>();
if (communicationComponent == null)
{
Debug.LogError("Failed to add EFSdkAndroid component to the GameObject.");
}
Object.DontDestroyOnLoad(communicationObject);
}
else
{
Debug.LogError("Failed to create the EFSdkAndroid GameObject.");
}
}
catch (System.Exception e)
{
Debug.LogError($"An error occurred while setting up the communication object: {e.Message}");
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9937d38cb2014e1e94087f6216eacd62
timeCreated: 1742202739

433
Assets/EFSDK/EFSdk.cs Normal file
View File

@ -0,0 +1,433 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace EFSDK
{
/// <summary>
/// TKG Native SDK Android platform interface call
/// </summary>
public class EFSdk
{
private static EFSdk _mEfSdk;
private static string mappingInfo = @"{""items"":[{""key"":""_sdk_float_balloon.png"",""value"":""aoa38ay.png""}]}";
public static EFSdk get()
{
if (_mEfSdk == null)
{
_mEfSdk = new EFSdk();
}
return _mEfSdk;
}
private AndroidJavaObject jo;
public EFSdk()
{
// java interface class
using (AndroidJavaClass jc = new AndroidJavaClass("com.earn.push._SDK"))
{
jo = jc.GetStatic<AndroidJavaObject>("INSTANCE");
}
}
private T SDKCall<T>(string _method, params object[] _param)
{
try
{
return jo.Call<T>(_method, _param);
}
catch (Exception e)
{
Debug.LogError(e);
}
return default(T);
}
private void SDKCall(string _method, params object[] _param)
{
try
{
jo.Call(_method, _param);
}
catch (Exception e)
{
Debug.LogError(e);
}
}
public enum ActionType
{
COIN_CLICK, //点击金币
BALLOON_CLICK, //点击气球
COIN_SHOW, //金币展示出来了
BOX_SHOW, //气球/宝箱展示出来了
GAM_LOAD_SUCC, //GAM页面加载成功
ON_RESUME, //游戏可见时回调,
// CAN_GOBACK, //游戏可见时回调,
}
public Action<ActionType, string> ActionCallback;
public Action<string, Dictionary<string, string>> ActionSDKEventCallback;
public Action<string> HdH5ImpressionAction;
public Action<bool> mCanGobackAction;
public Action<bool> mReqNotifyPermissionAction;
/// <summary>
/// 在Init方法之后调用这个方法,设置SDK上报事件回调, 将SDK传过来的事件上报到Firebase,数数等
/// </summary>
/// <param name="actionCallbvack">
/// 事件ID,事件属性
/// </param>
public void SetSDKEventCallback(Action<string, Dictionary<string, string>> eventKeyDict)
{
ActionSDKEventCallback = eventKeyDict;
}
/// <summary>
/// 互动广告展示回调,此时可以计算上报互动广告展示次数和收益
/// </summary>
/// <param name="callback">string 是互动广告的url</param>
public void SetHdH5ImpressionCallback(Action<string> callback)
{
HdH5ImpressionAction = callback;
}
/// <summary>
/// 初始化
/// </summary>
/// <param name="actionCallbvack">ActionType 回调类型 ; string msg </param>
public void Init(Action<ActionType, string> actionCallbvack)
{
ActionCallback = actionCallbvack;
SDKInit();
}
private void SDKInit()
{
// SDKCall("init");
ActionCallback?.Invoke(ActionType.GAM_LOAD_SUCC, string.Empty);
}
/// <summary>
/// 展示WebView
/// </summary>
/// <param name="id">标签id</param>
/// <param name="url">网址</param>
/// <param name="pRect"></param>
/// <param name="pCam"></param>
public void ShowWebView(int id, string url, RectTransform pRect, Camera pCam = null)
{
Vector3[] tWorldCorners = new Vector3[4];
pRect.GetWorldCorners(tWorldCorners);
Vector2 tTopLeft = RectTransformUtility.WorldToScreenPoint(pCam, tWorldCorners[1]);
Vector2 tBottomRight = RectTransformUtility.WorldToScreenPoint(pCam, tWorldCorners[3]);
int tWidth = (int)Mathf.Abs(tBottomRight.x - tTopLeft.x);
int tHeight = (int)Mathf.Abs(tBottomRight.y - tTopLeft.y);
SDKCall("showWebViewToActivity", id, url, (int)tTopLeft.x, (int)(Screen.height - tTopLeft.y), tWidth,
tHeight);
}
/// <summary>
/// 移除所有原生View, 回到游戏时调用
/// </summary>
/// <returns></returns>
public void RemoveAll()
{
SDKCall("removeAll");
}
/// <summary>
/// 刷新当前页面
/// </summary>
/// <returns></returns>
public void Refresh()
{
SDKCall("refresh");
}
/// <summary>
/// 回上一页
/// </summary>
/// <returns></returns>
public void GoBack()
{
SDKCall("goBack");
}
/// <summary>
/// 回首页
/// </summary>
/// <returns></returns>
public void GoHome()
{
SDKCall("goHome");
}
/// <summary>
/// 是否手动控制漂浮道具显示/隐藏
/// SDK内默认当H5页面加载完成后自动显示漂浮道具
/// </summary>
/// <param name="autoShow">true: 自动显示/隐藏道具 false: 游戏主动控制道具显示/隐藏</param>
/// <returns></returns>
public void AutoShowFloat(bool autoShow)
{
SDKCall("autoShowFloat", autoShow);
}
/// <summary>
/// 飘金币
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public void ShowFloatCoin(int id)
{
SDKCall("showFloatCoin", id);
}
/// <summary>
/// 飘金币
/// </summary>
/// <param name="id"></param>
/// <param name="res">悬浮金币按钮的图片资源,传字符串 0 或 1 0:金币图 1:红点宝箱图 </param>
/// <returns></returns>
public void ShowFloatCoin(int id, String res)
{
SDKCall("showFloatCoin", id, res);
}
/// <summary>
/// 设置悬浮金币按钮的图片资源
/// </summary>
/// <param name="res">传字符串 0 或 1 0:金币图 1:红点宝箱图</param>
public void SetFloatCoinRes(String res)
{
SDKCall("setFloatCoinRes", res);
}
/// <summary>
/// 隐藏金币
/// </summary>
/// <returns></returns>
public void HideFloatCoin()
{
SDKCall("hideFloatCoin");
}
/// <summary>
/// 飘气球
/// </summary>
/// <param name="startId"></param>
/// <param name="endId"></param>
/// <param name="fly_first_time"></param>
/// <param name="fly_gap_time"></param>
/// <returns></returns>
public void ShowBalloon(int startId, int endId, int fly_first_time, int fly_gap_time)
{
SDKCall("showBalloon", startId, endId, fly_first_time, fly_gap_time);
}
/// <summary>
/// 隐藏气球
/// </summary>
/// <returns></returns>
public void HideBalloon()
{
SDKCall("hideBalloon");
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public void ShowToast(string message)
{
SDKCall("showToast", message);
}
/// <summary>
/// 判断当前网页是否还能返回上一页, true:可以返回,此时页面不在首页 false: 不能返回了,当前页面就在首页
/// </summary>
public void CanGoback(Action<bool> canGobackAction)
{
mCanGobackAction = canGobackAction;
SDKCall("canGoback");
}
#region
/// <summary>
/// 满足条件:未领取 R0.1 的 买量用户, 调用这个方法
/// </summary>
public void SubscribeUnclaimed01()
{
SDKCall("subscribeUnclaimed01");
}
/// <summary>
/// 不满足条件:未领取 R0.1 的 买量用户, 调用这个方法
/// </summary>
public void UnSubscribeUnclaimed01()
{
SDKCall("unSubscribeUnclaimed01");
}
/// <summary>
// 满足条件: 在排队中 且 当日R1 未领取 的买量用户, 调用这个方法
/// </summary>
public void SubscribePending1()
{
SDKCall("subscribePending1");
}
/// <summary>
/// 不满足条件: 在排队中 且 当日R1 未领取 的买量用户, 调用这个方法
/// </summary>
public void UnSubscribePending1()
{
SDKCall("unSubscribePending1");
}
/// <summary>
/// 订阅Firebase推送主题
/// </summary>
/// <param name="topic">主题名称</param>
public void SubscribeToTopic(string topic)
{
SDKCall("subscribeToTopic", topic);
}
/// <summary>
/// 取消订阅Firebase推送主题
/// </summary>
/// <param name="topic">主题名称</param>
public void UnSubscribeToTopic(string topic)
{
SDKCall("UnSubscribeToTopic", topic);
}
/// <summary>
/// 向SDK上报当前金币总数,每次金币变化都要调用一次
/// </summary>
/// <param name="totalGold"></param>
public void SendTotalGold2SDK(int totalGold)
{
SDKCall("setGoldNum", totalGold.ToString());
}
/// <summary>
/// 向SDK上报当前要提现的现金额,每次变化都要调用一次
/// </summary>
/// <param name="cashNum"></param>
public void SendCashNum2SDK(double cashNum)
{
SDKCall("setCashNum", cashNum.ToString("0.00"));
}
/// <summary>
/// 向SDK上报 游戏名字(当前语言的),每次语言变化都上报
/// </summary>
/// <param name="gameName"></param>
public void SetGameName(string gameName)
{
SDKCall("setGameName", gameName);
}
// /// <summary>
// /// 设置推送 消息通知 的文案
// /// </summary>
// /// <param name="message"></param>
// public void SetCommPushMessage(string message)
// {
// SDKCall("setCommPushMessage", message);
// }
/// <summary>
/// 设置当前游戏语言是否是 西班牙语
///
/// </summary>
/// <param name="isEs">西班牙语传 true, 其他的都传 false </param>
public void SetCurrLang(bool isEs)
{
SDKCall("setCurrLang", isEs);
}
/// <summary>
/// 获取当前是否有通知权限
/// </summary>
public bool HasNotifyPermission()
{
return SDKCall<bool>("hasNotifyPermission");
}
/// <summary>
/// 请求获取通知权限
/// </summary>
public void ReqNotifyPermission()
{
SDKCall("reqNotifyPermission");
}
/// <summary>
/// 请求获取通知权限
/// <param name="action">授权弹窗关闭回调 bool:表示用户是否允许了权限 true:有权限 false:无权限</param>
/// </summary>
public void ReqNotifyPermission(Action<bool> action)
{
mReqNotifyPermissionAction = action;
SDKCall("reqNotifyPermission");
}
/// <summary>
/// 设置推送开关, SDK默认关闭通知
/// </summary>
/// <param name="isOpen"></param>
public void SetPushSwitch(bool isOpen)
{
SDKCall("pushSwitch", isOpen);
}
/// <summary>
/// 消息类通知弹出间隔设置为60秒在线参数控制-Key: messagenotif Value:60
/// </summary>
/// <param name="timeSeconds"></param>
public void SetPushMessagenotif(int timeSeconds)
{
SDKCall("setPushMessagenotif", timeSeconds);
}
/// <summary>
/// 持续性通知在进入游戏时弹出的时间间隔设置为300秒在线参数控制 -Key:persistentnotif Value:300
/// </summary>
/// <param name="timeSeconds"></param>
public void SetPushPersistentnotif(int timeSeconds)
{
SDKCall("setPushPersistentnotif", timeSeconds);
}
/// <summary>
/// 每次回调游戏的onResume的时候都调用一次,获取游戏要跳转的页面
/// </summary>
/// <returns>
/// 0 不需要进行任何跳转
/// 1 进行游戏主页
/// 2 进入游戏的金币提现界面
/// 3 进入对应小游戏1界面
/// 4 进入对应小游戏2界面
/// </returns>
public int GetJumpPage()
{
return SDKCall<int>("getJumpPage");
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3cf9c70ade0a42e08c9ea06733912dd2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,109 @@
using System.Collections.Generic;
namespace EFSDK
{
using UnityEngine;
public class EFSdkAndroid : MonoBehaviour
{
private string COIN_CLICK = "coin_click";
private string BALLOON_CLICK = "balloon_click";
private string Coin_Show = "Coin_Show";
private string Box_Show = "Box_Show";
private string Gam_Load_Succ = "Gam_Load_Succ";
private string On_Resume = "onResume";
private string Can_Goback = "canGoback";
public void OnReceiverAnd(string message)
{
WLoom.QueueOnMainThread(_ =>
{
Debug.Log("Received message from Android: " + message);
if (message.Contains(On_Resume))
{
EFSdk.get().ActionCallback?.Invoke(EFSdk.ActionType.ON_RESUME, message);
}
if (message.Contains(Can_Goback))
{
EFSdk.get().mCanGobackAction?.Invoke(bool.Parse(message.Split('#')[1]));
}
if (BALLOON_CLICK.Equals(message))
{
//点击气球
EFSdk.get().ActionCallback?.Invoke(EFSdk.ActionType.BALLOON_CLICK, message);
}
if (Coin_Show.Equals(message))
{
//金币展示出来了
EFSdk.get().ActionCallback?.Invoke(EFSdk.ActionType.COIN_SHOW, message);
}
if (COIN_CLICK.Equals(message))
{
//金币点击
EFSdk.get().ActionCallback?.Invoke(EFSdk.ActionType.COIN_CLICK, message);
}
if (Box_Show.Equals(message))
{
//宝箱展示出来了
EFSdk.get().ActionCallback?.Invoke(EFSdk.ActionType.BOX_SHOW, message);
}
if (message.Contains(Gam_Load_Succ))
{
//GAM页面加载成功 Gam_Load_Succ@id
string[] parts = message.Split('@');
EFSdk.get().ActionCallback?.Invoke(EFSdk.ActionType.GAM_LOAD_SUCC, parts[1]);
}
if (message.StartsWith("reqNotifyPermission#"))
{
string[] flag = message.Split('#');
EFSdk.get().mReqNotifyPermissionAction?.Invoke(flag[1].Equals("1"));
}
if (message.StartsWith("Event#"))
{
string[] eventKeys = message.Split('#');
if (eventKeys.Length > 0)
{
if (message.Contains("hd_h5_impression"))
{
//互动广告展示
string url = eventKeys[2];
EFSdk.get().HdH5ImpressionAction?.Invoke(url);
}
else if (eventKeys.Length == 2)
{
// 只有一个事件key
string eventKey = eventKeys[1];
EFSdk.get().ActionSDKEventCallback?.Invoke(eventKey, null);
}
else if (eventKeys.Length == 3)
{
// key-value事件
string eventKey = eventKeys[1];
string value = eventKeys[2];
}
else
{
//多属性事件
string eventKey = eventKeys[1];
Dictionary<string, string> attrs = new Dictionary<string, string>();
for (int i = 2; i < eventKeys.Length - 1; i += 2)
{
string key = eventKeys[i];
string value = eventKeys[i + 1];
attrs[key] = value;
}
EFSdk.get().ActionSDKEventCallback?.Invoke(eventKey, attrs);
}
}
}
}, "");
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 40a70c7c94e6494d947902b87c809b30
timeCreated: 1742202547

3
Assets/EFSDK/Editor.meta Normal file
View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e4d0b212325940748840950d8c400f9f
timeCreated: 1756628120

View File

@ -0,0 +1,162 @@
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Collections.Generic;
using IOCompression = System.IO.Compression;
namespace EFSDK
{
public class AndroidResAarBuilder
{
private static readonly string ResDir = "Assets/StreamingAssets/Android/res";
private static readonly string OutputDir = "Assets/Plugins/Android";
private static readonly string TempDir = "Temp/AndroidResAar";
private static readonly string EFSdk_FILE = "Assets/EFSDK/EFSdk.cs";
[MenuItem("EFSDK/Build Android Res AAR")]
public static void BuildAAR()
{
if (!Directory.Exists(ResDir))
{
Debug.LogError($"Res folder not found: {ResDir}");
return;
}
// 清理临时目录
if (Directory.Exists(TempDir)) Directory.Delete(TempDir, true);
Directory.CreateDirectory(TempDir);
// 复制资源并重命名
CopyAndRenameFiles(ResDir, TempDir, out Dictionary<string, string> mapping);
string manifestPath = Path.Combine(TempDir, "AndroidManifest.xml");
File.WriteAllText(manifestPath,
@"<manifest xmlns:android=""http://schemas.android.com/apk/res/android""
package=""com.unity.reswrapper"">
<application/>
</manifest>");
// 打包 AAR
string aarPath = Path.Combine(OutputDir, "efsdk_res.aar");
if (!Directory.Exists(OutputDir)) Directory.CreateDirectory(OutputDir);
if (File.Exists(aarPath)) File.Delete(aarPath);
IOCompression.ZipFile.CreateFromDirectory(TempDir, aarPath, IOCompression.CompressionLevel.Optimal, false);
Debug.Log($"✅ AAR built: {aarPath}");
// 生成压缩 JSON (key 只保留文件名)
Dictionary<string, string> simpleMapping = new Dictionary<string, string>();
foreach (var kv in mapping)
{
string fileName = Path.GetFileName(kv.Key);
simpleMapping[fileName] = kv.Value;
}
string mappingJson = GenerateMappingJson(mapping);
// 更新 mappingInfo
UpdateMappingInEFSdk_LineByLine(mappingJson);
// 映射文件
string mappingPath = Path.Combine(TempDir, "res_mapping.json");
File.WriteAllText(mappingPath, mappingJson);
// 清理临时目录
Directory.Delete(TempDir, true);
AssetDatabase.Refresh();
}
private static void CopyAndRenameFiles(string srcDir, string dstDir, out Dictionary<string, string> mapping)
{
mapping = new Dictionary<string, string>();
foreach (var filePath in Directory.GetFiles(srcDir, "*", SearchOption.AllDirectories))
{
if (filePath.EndsWith(".meta")) continue;
string relativePath = filePath.Substring(srcDir.Length + 1).Replace("\\", "/");
string newName = GenerateRandomAndroidName(Path.GetExtension(filePath));
mapping[Path.GetFileName(filePath)] = newName;
string dstPath = Path.Combine(dstDir, newName);
string dstFolder = Path.GetDirectoryName(dstPath);
if (!Directory.Exists(dstFolder)) Directory.CreateDirectory(dstFolder);
File.Copy(filePath, dstPath);
}
foreach (var dir in Directory.GetDirectories(srcDir, "*", SearchOption.AllDirectories))
{
string relativeDir = dir.Substring(srcDir.Length + 1);
string dstSubDir = Path.Combine(dstDir, relativeDir);
if (!Directory.Exists(dstSubDir)) Directory.CreateDirectory(dstSubDir);
}
Debug.Log("✅ Files copied and renamed");
}
private static string GenerateMappingJson(Dictionary<string, string> mapping)
{
var items = new List<MappingItem>();
foreach (var kv in mapping)
{
items.Add(new MappingItem { key = kv.Key, value = kv.Value });
}
MappingListWrapper wrapper = new MappingListWrapper { items = items };
return JsonUtility.ToJson(wrapper, false);
}
[System.Serializable]
private class MappingItem { public string key; public string value; }
[System.Serializable]
private class MappingListWrapper { public List<MappingItem> items; }
private static string GenerateRandomAndroidName(string ext)
{
int len = UnityEngine.Random.Range(6, 12);
string chars = "abcdefghijklmnopqrstuvwxyz0123456789";
string name = "";
for (int i = 0; i < len; i++)
{
name += chars[UnityEngine.Random.Range(0, chars.Length)];
}
if (!char.IsLetter(name[0])) name = "a" + name.Substring(1);
return name + ext;
}
private static void UpdateMappingInEFSdk_LineByLine(string mappingJson)
{
if (!File.Exists(EFSdk_FILE))
{
Debug.LogError($"EFSdk.cs not found: {EFSdk_FILE}");
return;
}
string[] lines = File.ReadAllLines(EFSdk_FILE);
bool updated = false;
for (int i = 0; i < lines.Length; i++)
{
if (lines[i].Contains("mappingInfo"))
{
lines[i] = $" private static string mappingInfo = @\"{mappingJson.Replace("\"", "\"\"")}\";";
updated = true;
break; // 找到第一行就替换,防止重复
}
}
if (!updated)
{
// 如果没有找到 mappingInfo 行,则在 _mEfSdk 后插入
for (int i = 0; i < lines.Length; i++)
{
if (lines[i].Contains("private static EFSdk _mEfSdk"))
{
lines[i] += $"\n private static string mappingInfo = @\"{mappingJson.Replace("\"", "\"\"")}\";";
updated = true;
break;
}
}
}
File.WriteAllLines(EFSdk_FILE, lines);
Debug.Log("✅ mappingInfo updated in EFSdk.cs (line-by-line)");
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ff87df6ca3d9445e98929bb62fb10a15
timeCreated: 1756694090

View File

@ -0,0 +1,13 @@
<dependencies>
<androidPackages>
<androidPackage spec="com.earn.money:sdk:+">
<repositories>
<repository>https://repo.dgtverse.cn/repository/maven-public/</repository>
<repository>https://android-sdk.is.com/</repository>
<repository>https://dl-maven-android.mintegral.com/repository/mbridge_android_sdk_oversea</repository>
<repository>https://artifact.bytedance.com/repository/pangle/</repository>
</repositories>
</androidPackage>
</androidPackages>
</dependencies>

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 61484455ad49448f839552619a42625a
timeCreated: 1756628147

View File

@ -0,0 +1,181 @@
using System.Collections.Generic;
using System.IO;
using Unity.Plastic.Newtonsoft.Json.Linq;
using UnityEditor.Android;
using UnityEngine;
public class DynamicApplicationClass : IPostGenerateGradleAndroidProject
{
public int callbackOrder
{
get { return int.MaxValue; }
}
private string GetRootPath(string path)
{
return Path.Combine(path, "..", "");
}
public void OnPostGenerateGradleAndroidProject(string path)
{
var androidManifest = new SDKTool.AndroidManifest(SDKTool.GetManifestPath(path));
androidManifest.SetStartingActivityAttribute("hardwareAccelerated", "true");
androidManifest.Save();
SetGradleConstraints(path);
FixedAddressValueTypeAttribute(path);
ParseConfigFile(path);
}
private static void SetGradleConstraints(string path)
{
string gradlePath = Path.Combine(path, "../unityLibrary", "build.gradle");
if (!File.Exists(gradlePath))
{
Debug.LogError("未找到unityLibrary模块的build.gradle文件: " + gradlePath);
}
// var content = " implementation \"androidx.datastore:datastore:1.0.0\"\n constraints {\n implementation(\"androidx.datastore:datastore\") {\n version {\n strictly \"1.0.0\"\n }\n because \"1.0.0版本,避免高版本兼容性问题\"\n }\n }";
var buildGradleOutLines = new List<string>();
foreach (var line in File.ReadLines(gradlePath))
{
if (line.Trim().Contains("com.earn.money:sdk"))
{
Debug.Log("找到com.earn.money:sdk");
buildGradleOutLines.Add($" implementation ('com.earn.money:sdk:{SDKTool.GetSDKVersion()}')");
}
else
{
buildGradleOutLines.Add(line);
}
}
File.WriteAllText(gradlePath, string.Join("\n", buildGradleOutLines.ToArray()) + "\n");
}
private static void FixedAddressValueTypeAttribute(string path)
{
string launcherBuildGradlePath = Path.Combine(path, "../launcher", "build.gradle");
string content =
" configurations.all {\n resolutionStrategy {\n force \"androidx.appcompat:appcompat:1.6.1\"\n force \"androidx.core:core:1.12.0\"\n }\n }";
var launcherBuildGradleOutLines = new List<string>();
foreach (var line in File.ReadLines(launcherBuildGradlePath))
{
launcherBuildGradleOutLines.Add(line);
if (line.Trim().StartsWith("defaultConfig"))
{
launcherBuildGradleOutLines.Add(content);
}
}
File.WriteAllText(launcherBuildGradlePath, string.Join("\n", launcherBuildGradleOutLines.ToArray()) + "\n");
}
private void ParseConfigFile(string path)
{
Dictionary<string, string> jsonDict = ParseToSimpleDictionary();
if (jsonDict == null || jsonDict.Count == 0)
{
throw new System.Exception("配置文件中未解析到google-services.json");
}
// 获取launcher模块的build.gradle路径
string gradlePath = Path.Combine(path, "../launcher", "build.gradle");
if (!File.Exists(gradlePath))
{
Debug.LogError("未找到launcher模块的build.gradle文件: " + gradlePath);
}
// 读取文件内容
string newResValue = $"\n resValue \"string\", \"game_services_project_id\", \"{jsonDict["project_id"]}\"" +
$"\n resValue \"string\", \"project_id\", \"{jsonDict["project_id"]}\"" +
$"\n resValue \"string\", \"google_project_id\", \"{jsonDict["project_id"]}\"" +
$"\n resValue \"string\", \"google_package_name\", \"{jsonDict["package_name"]}\"" +
$"\n resValue \"string\", \"gcm_defaultSenderId\", \"{jsonDict["project_number"]}\"" +
$"\n resValue \"string\", \"google_api_key\", \"{jsonDict["current_key"]}\"" +
$"\n resValue \"string\", \"google_app_id\", \"{jsonDict["mobilesdk_app_id"]}\"" +
$"\n resValue \"string\", \"google_crash_reporting_api_key\", \"{jsonDict["current_key"]}\"" +
$"\n resValue \"string\", \"google_storage_bucket\", \"{jsonDict["storage_bucket"]}\"";
Debug.Log($"DSSdk newResValue: {newResValue}");
string launcherBuildGradlePath = Path.Combine(path, "../launcher", "build.gradle");
var launcherBuildGradleOutLines = new List<string>();
foreach (var line in File.ReadLines(launcherBuildGradlePath))
{
launcherBuildGradleOutLines.Add(line);
if (line.Trim().StartsWith("defaultConfig"))
{
launcherBuildGradleOutLines.Add(newResValue);
}
}
File.WriteAllText(launcherBuildGradlePath, string.Join("\n", launcherBuildGradleOutLines.ToArray()) + "\n");
}
public Dictionary<string, string> ParseToSimpleDictionary()
{
Dictionary<string, string> simpleDict = new Dictionary<string, string>();
try
{
string filePath = Path.Combine(Application.streamingAssetsPath, "google-services.json");
if (!File.Exists(filePath))
{
Debug.LogError("文件不存在: " + filePath);
return simpleDict;
}
string jsonContent = File.ReadAllText(filePath);
JObject jsonObject = JObject.Parse(jsonContent);
// 递归解析所有字段
ParseJToken(jsonObject, simpleDict);
return simpleDict;
}
catch (System.Exception e)
{
Debug.LogError("解析错误: " + e.Message);
return simpleDict;
}
}
/// <summary>
/// 递归解析JToken并提取字段名和值
/// </summary>
private void ParseJToken(JToken token, Dictionary<string, string> dict)
{
if (token is JObject jObject)
{
foreach (var property in jObject.Properties())
{
// 如果是对象类型,继续递归解析
if (property.Value is JObject || property.Value is JArray)
{
ParseJToken(property.Value, dict);
}
else
{
// 基本类型直接添加相同key会覆盖
if (dict.ContainsKey(property.Name))
{
Debug.LogWarning($"字段名重复,已覆盖: {property.Name}");
}
dict[property.Name] = property.Value.ToString();
}
}
}
else if (token is JArray jArray)
{
// 解析数组中的每个元素
foreach (var item in jArray)
{
ParseJToken(item, dict);
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e118bf217da84e56846255d208995b6c
timeCreated: 1756628731

View File

@ -0,0 +1,45 @@
using System;
using UnityEngine;
using UnityEngine.Networking;
public class SDKEditorNetworkTool
{
public static string GetText(string url)
{
try
{
using (UnityWebRequest www = UnityWebRequest.Get(url))
{
www.timeout = 10; // 设置超时时间为10秒
www.SendWebRequest(); // 同步发送请求
while (www.isDone == false)
{
}
if (www.result != UnityWebRequest.Result.Success)
{
Debug.LogError($"Request failed: {www.error}");
return "";
}
if (www.responseCode != 200)
{
Debug.LogWarning($"Unexpected status code: {www.responseCode}");
return "";
}
return www.downloadHandler.text;
}
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
return "";
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 69514595cf674f94bd3e0b15beb30d73
timeCreated: 1756628731

View File

@ -0,0 +1,300 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using Microsoft.CSharp.RuntimeBinder;
using UnityEditor;
using UnityEditor.Build;
using UnityEngine;
public class SDKTool
{
public static string GetPackageName()
{
return PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.Android);
}
public static string MavenStr = @"
maven {
url 'https://repo.dgtverse.cn/repository/maven-public/'
name 'maven_repo.dgtverse.cn'
}
";
public static Hashtable LoadProperties(string file)
{
var ht = new Hashtable(16);
string content = null;
try
{
content = file;
}
catch (Exception e)
{
return null;
}
var rows = content.Split('\n');
string[] kv = null;
foreach (var c in rows)
{
if (c.Trim().Length == 0)
continue;
kv = c.Split('=');
ht[kv[0].Trim()] = kv.Length switch
{
1 => "",
2 => kv[1].Trim(),
_ => ht[kv[0].Trim()]
};
}
return ht;
}
public static string GetSDKVersion()
{
var xmlText =
SDKEditorNetworkTool.GetText("https://repo.dgtverse.cn/repository/tk_my/com/earn/money/sdk/maven-metadata.xml");
if (string.IsNullOrEmpty(xmlText))
{
throw new RuntimeBinderException(
"获取版本号失败 , 接口请求返回为空,或请求不到. https://repo.dgtverse.cn/repository/tk_my/com/earn/money/sdk/maven-metadata.xml");
}
try
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xmlText);
XmlNodeList versions = xmlDoc.SelectNodes("//versioning/latest");
if (versions == null)
{
throw new RuntimeBinderException("获取版本号失败");
}
if (versions.Count > 0)
{
string latestVersion = versions[0].InnerText;
Debug.Log($"Latest version: {latestVersion}");
return latestVersion;
}
throw new RuntimeBinderException("解析xml失败");
}
catch (Exception e)
{
throw new RuntimeBinderException($"获取版本号失败 : XML parsing error: {e.Message}");
}
}
private void CopyGoogleServices(string path)
{
// var gpPath = Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + "SDKConfig" +
// Path.DirectorySeparatorChar + SDKTool.GetPackageName() + Path.DirectorySeparatorChar +
// "google-services.json";
string gpPath = Path.Combine(Application.streamingAssetsPath, "google-services.json");
if (!File.Exists(gpPath))
{
throw new BuildFailedException("Can't find google-services.json");
}
var targetPath = path + Path.DirectorySeparatorChar + ".." + Path.DirectorySeparatorChar + "launcher" +
Path.DirectorySeparatorChar + "google-services.json";
Debug.Log("gpPath : " + gpPath + "\t targetPath : " + targetPath);
if (File.Exists(targetPath))
{
File.Delete(targetPath);
}
File.Copy(gpPath, targetPath);
}
private void AddFirebasePluginToGradle(string path)
{
var filePath =
$"{path}{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}launcher{Path.DirectorySeparatorChar}build.gradle";
if (File.ReadAllText(filePath).Contains("com.google.gms.google-services"))
{
return;
}
var newLine = new List<string>
{
"apply plugin: 'com.google.gms.google-services'\n",
"apply plugin: 'com.google.firebase.crashlytics'\n"
};
newLine.AddRange(File.ReadLines(filePath));
File.WriteAllLines(filePath, newLine);
}
private void AddFirebasePlugin(string rootPath)
{
_AddFirebasePlugin($"{rootPath}{Path.DirectorySeparatorChar}build.gradle");
}
private void _AddFirebasePlugin(string filePath)
{
if (File.ReadAllText(filePath).Contains("com.google.gms:google-services"))
{
return;
}
var newLine = new List<string>();
foreach (var line in File.ReadLines(filePath))
{
var trim = line.Trim();
if (trim.Contains("com.google.gms.google-services") && trim.Contains("4.4.2"))
{
continue;
}
if (trim.Contains("com.google.firebase.crashlytics") && trim.Contains("2.9.4"))
{
continue;
}
if (trim.Contains("com.google.gms:google-services") && trim.Contains("4.3.10"))
{
continue;
}
if (trim.Contains("com.google.firebase:firebase-crashlytics-gradle") && trim.Contains("2.9.4"))
{
continue;
}
newLine.Add(line);
if (trim.StartsWith("google()"))
{
newLine.Add(MavenStr);
}
if (trim.StartsWith("classpath"))
{
newLine.Add("classpath 'com.google.gms:google-services:4.3.10'\n");
newLine.Add("classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.4'\n");
}
if (trim.StartsWith("id 'com.android.application'"))
{
newLine.Add("id 'com.google.gms.google-services' version '4.4.2' apply false\n");
newLine.Add("id 'com.google.firebase.crashlytics' version '2.9.4' apply false\n");
}
}
File.WriteAllLines(filePath, newLine);
}
private static string _manifestFilePath;
public static string GetManifestPath(string basePath)
{
if (string.IsNullOrEmpty(_manifestFilePath))
{
StringBuilder pathBuilder = new StringBuilder(basePath);
pathBuilder.Append(Path.DirectorySeparatorChar).Append("src");
pathBuilder.Append(Path.DirectorySeparatorChar).Append("main");
pathBuilder.Append(Path.DirectorySeparatorChar).Append("AndroidManifest.xml");
_manifestFilePath = pathBuilder.ToString();
Debug.Log($"_manifestFilePath = {_manifestFilePath}");
}
return _manifestFilePath;
}
internal class AndroidXmlDocument : XmlDocument
{
private string m_Path;
protected XmlNamespaceManager nsMgr;
public const string AndroidXmlNamespace = "http://schemas.android.com/apk/res/android";
public AndroidXmlDocument(string path)
{
m_Path = path;
using (var reader = new XmlTextReader(m_Path))
{
reader.Read();
Load(reader);
}
nsMgr = new XmlNamespaceManager(NameTable);
nsMgr.AddNamespace("android", AndroidXmlNamespace);
}
public string Save()
{
return SaveAs(m_Path);
}
public string SaveAs(string path)
{
using (var writer = new XmlTextWriter(path, new UTF8Encoding(false)))
{
writer.Formatting = Formatting.Indented;
Save(writer);
}
return path;
}
}
internal class AndroidManifest : AndroidXmlDocument
{
private readonly XmlElement ApplicationElement;
public AndroidManifest(string path) : base(path)
{
ApplicationElement = SelectSingleNode("/manifest/application") as XmlElement;
}
private XmlAttribute CreateAndroidAttribute(string key, string value, string prefix = "android", string namespaceURI = AndroidXmlNamespace)
{
XmlAttribute attr = CreateAttribute(prefix, key, namespaceURI);
attr.Value = value;
return attr;
}
internal XmlNode GetActivityWithLaunchIntent()
{
return SelectSingleNode(
"/manifest/application/activity[intent-filter/action/@android:name='android.intent.action.MAIN' and " +
"intent-filter/category/@android:name='android.intent.category.LAUNCHER']", nsMgr);
}
internal void SetApplicationTheme(string appTheme)
{
ApplicationElement.Attributes.Append(CreateAndroidAttribute("theme", appTheme));
}
internal void SetStartingActivityName(string activityName)
{
GetActivityWithLaunchIntent().Attributes.Append(CreateAndroidAttribute("name", activityName));
}
internal void SetApplicationAttribute(string key, string value, string prefix = "android", string namespaceURI = AndroidXmlNamespace)
{
ApplicationElement.Attributes.Append(CreateAndroidAttribute(key, value, prefix, namespaceURI));
}
internal void RemoveApplicationAttribute(string key)
{
// ApplicationElement.Attributes.Remove(CreateAndroidAttribute(key, key));
var removeNamedItem = ApplicationElement.Attributes.RemoveNamedItem(key);
Debug.Log($"删除节点 key = {key} value = {removeNamedItem}");
}
internal void SetStartingActivityAttribute(string key, string value)
{
XmlNode node = GetActivityWithLaunchIntent();
Debug.Log($"Main节点 node = {node} key = {key} value = {value}");
if (node != null)
{
XmlAttributeCollection attributes = node.Attributes;
attributes.Append(CreateAndroidAttribute(key, value));
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 01a2130a27404acabf67d5122908d668
timeCreated: 1756628731

175
Assets/EFSDK/WLoom.cs Normal file
View File

@ -0,0 +1,175 @@
using UnityEngine;
using System.Collections.Generic;
using System;
using System.Threading;
using System.Linq;
namespace EFSDK
{
public class WLoom : MonoBehaviour
{
public static int maxThreads = 8;
static int numThreads;
private static WLoom _current;
//private int _count;
public static WLoom Current
{
get
{
Initialize();
return _current;
}
}
void Awake()
{
_current = this;
initialized = true;
}
static bool initialized;
public static void Initialize()
{
if (!initialized)
{
if (!Application.isPlaying)
return;
initialized = true;
var g = new GameObject("Loom");
_current = g.AddComponent<WLoom>();
DontDestroyOnLoad(g);
}
}
public struct NoDelayedQueueItem
{
public Action<object> action;
public object param;
}
private List<NoDelayedQueueItem> _actions = new List<NoDelayedQueueItem>();
public struct DelayedQueueItem
{
public float time;
public Action<object> action;
public object param;
}
private List<DelayedQueueItem> _delayed = new List<DelayedQueueItem>();
List<DelayedQueueItem> _currentDelayed = new List<DelayedQueueItem>();
public static void QueueOnMainThread(Action<object> taction, object tparam)
{
QueueOnMainThread(taction, tparam, 0f);
}
public static void QueueOnMainThread(Action<object> taction, object tparam, float time)
{
if (time != 0)
{
lock (Current._delayed)
{
Current._delayed.Add(new DelayedQueueItem { time = Time.time + time, action = taction, param = tparam });
}
}
else
{
lock (Current._actions)
{
Current._actions.Add(new NoDelayedQueueItem { action = taction, param = tparam });
}
}
}
public static Thread RunAsync(Action a)
{
Initialize();
while (numThreads >= maxThreads)
{
Thread.Sleep(100);
}
Interlocked.Increment(ref numThreads);
ThreadPool.QueueUserWorkItem(RunAction, a);
return null;
}
private static void RunAction(object action)
{
try
{
((Action)action)();
}
catch
{
}
finally
{
Interlocked.Decrement(ref numThreads);
}
}
void OnDisable()
{
if (_current == this)
{
_current = null;
}
}
public void ToukaGamesInit()
{
}
// Use this for initialization
void Start()
{
}
List<NoDelayedQueueItem> _currentActions = new List<NoDelayedQueueItem>();
// Update is called once per frame
void Update()
{
if (_actions.Count > 0)
{
lock (_actions)
{
_currentActions.Clear();
_currentActions.AddRange(_actions);
_actions.Clear();
}
for (int i = 0; i < _currentActions.Count; i++)
{
_currentActions[i].action(_currentActions[i].param);
}
}
if (_delayed.Count > 0)
{
lock (_delayed)
{
_currentDelayed.Clear();
_currentDelayed.AddRange(_delayed.Where(d => d.time <= Time.time));
for (int i = 0; i < _currentDelayed.Count; i++)
{
_delayed.Remove(_currentDelayed[i]);
}
}
for (int i = 0; i < _currentDelayed.Count; i++)
{
_currentDelayed[i].action(_currentDelayed[i].param);
}
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3aeb3cde9e8947319795ca9d78758647
timeCreated: 1745562486