介绍

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. 对库进行管理,所以仓库里并没有库文件。

总结

  1. 实现了一层薄薄的胶水层,提供 Unity C# 方便逻辑代码调用,同时定义回调方便发生事件时通知。
  2. 内部使用工厂模式创建对应平台对象,使用接口多态动态分发请求到不同平台的实现(Android、iOS、Unity 编辑器)。
  3. 逻辑代码全在 Native 层(Android、iOS),并且做了混淆与加密。

Android

  1. 所有的代码都按照功能分别编译到单独的 aar 文件中。
  2. 针对每一个广告 SDK 都单独编写了 Native 的桥接代码,如:anythink_network_unity_baidu.aar
  3. 广告 SDK 原始库文件 aar 与桥接 aar 放在一起使用。
  4. 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

  1. 所有的代码都按照功能分别编译到单独的 Framework 文件中。
  2. 针对每一个 SDK 都单独编写了 Native 的桥接代码,如:AnyThinkBaiduAdapter.framework
  3. 桥接代码与 SDK 代码直接静态编译到了一起。

SDK 核心代码:

1
2
3
4
5
6
7
AnyThinkBanner.framework
AnyThinkInterstitial.framework
AnyThinkNative.framework
AnyThinkRewardedVideo.framework
AnyThinkSDK.bundle
AnyThinkSDK.framework
AnyThinkSplash.framework