介绍
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
|