介绍
TopOn 是一站式广告聚合平台。
TopOn 提供多种平台 SDK 集成,支持 Android、iOS、Unity、Cocos2dx、CocosCreator。这里分析一下 Unity SDK 的实现原理,主要说明 Unity 与 Native 如何通信、框架文件结构。
环境
- TopOn SDK v5.8.13
- Unity 2019.4.32f1
下载
SDK
- Android v5.8.13 2022-03-11 TopOn SDK(编译后0.61MB)+全部广告平台SDK 39.82MB(编译后19.26MB)
- iOS v5.8.13 2022-03-11 TopOn SDK(编译后0.99MB)+全部广告平台SDK 603.27MB(编译后29.68MB)
下载方法
选择所有广告平台,然后点击 Integrate 后下载。
实现原理
TopOn 将广告平台的 SDK 封装打包成 unitypackage 文件,然后根据勾选将这些 unitypackage 打包成一个 zip 文件提供下载。
SDK Demo
克隆完仓库需要手动切换到 v5.8.13
分支
文档
通信
使用 RPC 进行 Unity 与 Android/iOS 之间的信息交互。
Unity to Android
原理
Instances of UnityEngine.AndroidJavaObject and UnityEngine.AndroidJavaClass have a one-to-one mapping to an instance of java.lang.Object and java.lang.Class (or their subclasses) on the Java side, respectively. They essentially provide 3 types of interaction with the Java side:
- Call a method
- Get the value of a field
- Set the value of a field
C# 调用 Java 代码使用的是 AndroidJavaObject 来调用 Java 方法。
C# Assets/AnyThinkAds/Platform/Android/ATSDKAPIClient.cs
1 2 3 4
|
AnyThinkAds.Android.ATSDKAPIClient.initSDK this.sdkInitHelper = new AndroidJavaObject("com.anythink.unitybridge.sdkinit.SDKInitHelper", this); this.sdkInitHelper.Call("initAppliction", appId, appKey);
|
Java Assets/AnyThinkAds/Plugins/Android/anythink_bridge.aar
1
|
com.anythink.unitybridge.sdkinit.SDKInitHelper.initAppliction(final String appid, String appkey)
|
Android to Unity
原理
Java 层使用构造方法注入 C# 的回调方法
Java Assets/AnyThinkAds/Plugins/Android/anythink_bridge.aar
1 2 3 4 5 6
|
public SDKInitHelper(SDKInitListener pSDKInitListener) public interface SDKInitListener { void initSDKSuccess(String str); void initSDKError(String str, String str2); }
|
C# Assets/AnyThinkAds/Platform/Android/ATSDKAPIClient.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
this.sdkInitHelper = new AndroidJavaObject("com.anythink.unitybridge.sdkinit.SDKInitHelper", this); public void initSDKSuccess(string appid) { Debug.Log("initSDKSuccess...unity3d."); if(sdkInitListener != null){ sdkInitListener.initSuccess(); } } public void initSDKError(string appid, string message) { Debug.Log("initSDKError..unity3d.."); if (sdkInitListener != null) { sdkInitListener.initFail(message); } }
|
Unity to iOS
原理
使用 C 接口调用 Native 代码
1 2 3
|
extern "C" { float FooPluginFunction(); }
|
C# Assets/AnyThinkAds/Platform/iOS/Internal/Script/ATUnityCBridge.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
ATUnityCBridge.SendMessageToC("ATUnityManager", "startSDKWithAppID:appKey:", new object[]{appID, appKey}); static public bool SendMessageToC(string className, string selector, object[] arguments, bool carryCallback) { Debug.Log("Unity: ATUnityCBridge::SendMessageToC()"); Dictionary<string, object> msgDict = new Dictionary<string, object>(); msgDict.Add("class", className); msgDict.Add("selector", selector); msgDict.Add("arguments", arguments); CCallBack callback = null; if (carryCallback) callback = MessageFromC; #if UNITY_IOS || UNITY_IPHONE return message_from_unity(JsonMapper.ToJson(msgDict), callback); #else return false; #endif } #if UNITY_IOS || UNITY_IPHONE [DllImport("__Internal")] extern static bool message_from_unity(string msg, Func<string, int> callback); #endif
|
Objective-C Assets/AnyThinkAds/Platform/iOS/Internal/C/ATUnityManager.m
这里通过反射获取要调用的类名,然后再将调用分发到这个类上进行处理。并且这里存储了后续需要使用的回调方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
/* *class: *selector: *arguments: */ bool message_from_unity(const char *msg, void(*callback)(const char*, const char *)) { NSString *msgStr = [NSString stringWithUTF8String:msg]; NSDictionary *msgDict = [NSJSONSerialization JSONObjectWithData:[msgStr dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:nil]; Class class = NSClassFromString(msgDict[@"class"]); bool ret = false; ret = [[[class sharedInstance] selWrapperClassWithDict:msgDict callback:callback != NULL ? callback : nil] boolValue]; return ret; }
|
iOS to Unity
原理
Calling C# back from native code
Unity iOS supports limited native-to-managed callback functionality. You can do this in one of two ways:
- Using
UnitySendMessage
- Via delegates
TopOn 这里实现得比较复杂,但是核心原理简单。通过将 C# 的回调方法注入到 Objective-C 中,并且将回调地址存储到 placementId 对应的 Value 字典中,后续回调时通过 placementId 查找到回调再调用。
Objective-C Assets/AnyThinkAds/Platform/iOS/Internal/C/ATBaseUnityWrapper.m
1 2 3 4 5 6 7 8 9 10 11 12
|
-(void) invokeCallback:(NSString*)callback placementID:(NSString*)placementID error:(NSError*)error extra:(NSDictionary*)extra { if ([self callbackForKey:placementID] != NULL) { if ([callback isKindOfClass:[NSString class]] && [callback length] > 0) { NSMutableDictionary *paraDict = [NSMutableDictionary dictionaryWithObject:callback forKey:@"callback"]; ... [self callbackForKey:placementID]([self scriptWrapperClass].UTF8String, paraDict.jsonString.UTF8String); } } }
|
C# Assets/AnyThinkAds/Platform/iOS/Internal/Script/ATUnityCBridge.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
[MonoPInvokeCallback(typeof(CCallBack))] static public void MessageFromC(string wrapperClass, string msg) { Debug.Log("Unity: ATUnityCBridge::MessageFromC(" + wrapperClass + "," + msg + ")"); JsonData jsonData = JsonMapper.ToObject(msg); if (wrapperClass.Equals("ATRewardedVideoWrapper")) { Debug.Log("Unity: ATUnityCBridge::MessageFromC(), hit rv"); ATRewardedVideoWrapper.InvokeCallback(jsonData); } else if (wrapperClass.Equals("ATNativeAdWrapper")) { ATNativeAdWrapper.InvokeCallback(jsonData); } else if (wrapperClass.Equals("ATInterstitialAdWrapper")) { ATInterstitialAdWrapper.InvokeCallback(jsonData); } else if (wrapperClass.Equals("ATBannerAdWrapper")) { ATBannerAdWrapper.InvokeCallback(jsonData); } else if (wrapperClass.Equals("ATNativeBannerAdWrapper")) { ATNativeBannerAdWrapper.InvokeCallback(jsonData); } }
|
比较
TopOn SDK 将 Unity 与 Android 通信实现得较为简单,将 Unity 与 iOS 通信实现得较为复杂。
Demo
Demo 都是以最简的代码实现了一个例子,没有任何多余功能。
Android
Android Demo 是一个 Gradle 工程,同时附带了已经编译好可运行的 apk,使用模拟器可以直接查看效果。
iOS
iOS Demo 是一个 Xcode 工程,使用 GitHub - CocoaPods/CocoaPods: The Cocoa Dependency Manager. 对库进行管理,所以仓库里并没有库文件。
总结
- 实现了一层薄薄的胶水层,提供 Unity C# 方便逻辑代码调用,同时定义回调方便发生事件时通知。
- 内部使用工厂模式创建对应平台对象,使用接口多态动态分发请求到不同平台的实现(Android、iOS、Unity 编辑器)。
- 逻辑代码全在 Native 层(Android、iOS),并且做了混淆与加密。
Android
- 所有的代码都按照功能分别编译到单独的 aar 文件中。
- 针对每一个广告 SDK 都单独编写了 Native 的桥接代码,如:
anythink_network_unity_baidu.aar
。
- 广告 SDK 原始库文件 aar 与桥接 aar 放在一起使用。
- aar 反编译可以看到所有的 Java 代码都已经进行了混淆。
SDK 核心代码:
1 2 3 4 5 6 7 8 9
|
anythink_bridge.aar anythink_banner.aar anythink_china_core.aar anythink_core.aar anythink_interstitial.aar anythink_native.aar anythink_rewardvideo.aar anythink_splash.aar tramini_sdk.aar
|
iOS
- 所有的代码都按照功能分别编译到单独的 Framework 文件中。
- 针对每一个 SDK 都单独编写了 Native 的桥接代码,如:
AnyThinkBaiduAdapter.framework
。
- 桥接代码与 SDK 代码直接静态编译到了一起。
SDK 核心代码:
1 2 3 4 5 6 7
|
AnyThinkBanner.framework AnyThinkInterstitial.framework AnyThinkNative.framework AnyThinkRewardedVideo.framework AnyThinkSDK.bundle AnyThinkSDK.framework AnyThinkSplash.framework
|
共有 0 条评论