由于工作需要在接入第三方 SDK ,而一些第三方 SDK 没有提供 Unity 版本,就需要自己去包装一层需要调用的接口,之后就可以在 Unity 端统一接入,直接导出可用的对应平台的工程或程序包。
前几天开始封装 iOS 平台的 SDK,至此,已经接入过手机上主流的 Android 和 iOS 两个平台的SDK,这篇文章就是对之前开发插件的一个总结。
概述
Unity 端调用 Android 端的方法,可以使用 AndroidJavaClass, AndroidJavaObject 和 AndroidJavaProxy 这三个类,通过 JNI 对应了 Java 中的类,对象和接口。
Unity 端调用 iOS 端的方法,则相对容易一些,也就是 C# 和 Objective-C 两个语言相互交互,可以注意到这两个都是 C 系语言,所以实际上 Objective-C 写的本地插件会被编译成 DLL 来给 C# 调用,处理好原始类型和引用类型的相互转换就可以了。
而反过来,插件回调至 Unity 端则有两种方法:
Unity 在 Android 和 iOS 平台上都支持调用 UnitySendMessage 方法来向 Unity 传递信息,Unity 端根据传递来的信息执行对应的逻辑。不过使用这一方法会有一些限制,我们先看一下这个方法的签名:void UnitySendMessage("GameObjectName1", "MethodName1", "Message to send");,需要在 Unity 端有一个 GameObject 来接收信息,这个方法不能返回值,只能传递一个字符串,也就导致类型信息在这个过程中是丢失的。
下面详细介绍对应这两个平台的另一种回调方法:
Unity 与 Android
Android 端 有一个 bar 方法,接受一个 ICallback 的实例作为参数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package com.test.sdk;
public class Foo {
public void bar(ICallback callback) {
if(success) {
callback.onSuccess();
} else {
callback.onFailure("Something wrong");
}
}
}
public interface ICallback {
public void onSuccess();
public void onFailure(String message);
}
为了能够给 bar 方法传递一个 ICallback 实例,我们需要在 Unity 端 对应定义一个接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32public class ICallback : AndroidJavaProxy
{
//这里的 Action 只是为了方便传递匿名方法
Action onSuccessAction;
Action onFailureAction;
public ICallback(Action onSuccessAction,
Action onFailureAction) :
base("com.test.sdk.ICallback")
//此处传递了对应 Android 端接口的类路径
{
this.onSuccessAction = onSuccessAction;
this.onFailureAction = onFailureAction;
}
//这里的方法名要对应 Android 端接口的方法名
public void onSuccess()
{
if (onSuccess != null)
{
onSuccess.Invoke(UserInfo.FromJavaObject(userInfo));
}
}
public void onFailureAction(string message)
{
if (onFailureAction != null)
{
onFailureAction.Invoke(message);
}
}
}
接下来我们就可以在 Unity 端直接调用 bar 方法了:
1
2
3
4
5
6public void bar(ICallback callback) {
using (AndroidJavaObject fooObj = new AndroidJavaClass("com.test.sdk.Foo").CallStatic("getInstance");)
{
fooObj.Call("bar", callback);
}
}
可以看到以上场景中,我们使用 AndroidJavaProxy 对应实现了接口,使用 AndroidJavaClass 获取到 Java 类对象,通过 CallStatic 方法获取到用 AndroidJavaObject 对应表示的实例对象。
基本上这样就完成 Unity 和 Java 两端交互的功能了,Unity 端包装好之后,使用者是不需要接触到 AndroidJavaObject 这些类的。
进阶,传递引用类型
上面用到的 ICallback 接口的方法定义中,只有 String 这种类型的数据,对于字符串、数字、布尔类型,Unity 和 Java 之间是可以直接相互传递的。
那要传递其他类型怎么办呢?这里以 Java 中 ArrayList 类型为例,除了刚才提到的可以直接传递的类型,其它类型都必须指定为 AndroidJavaObject。
如需要在 Unity 中遍历,则需要这样做:
1
2
3
4
5AndroidJavaObject arrayObj = new AndroidJavaClass("com.test.sdk.FooBar").CallStatic("getArray");
for (int i = 0; i < arrayObj.Call("size"); i = i + 1)
{
Console.WriteLine("值: {0}", arrayObj.Call("get", i));
}
也就是必须使用 AndroidJavaObject 的方法将数据从其中取出来,不能直接使用 Java 端的字段和方法定义。
Unity 与 iOS
iOS端有一个 bar 方法,定义如下:
1
2
3typedef void (*BarHandler) (const char* message);
void bar(BarHandler barHandler);
对应的我们需要在 Unity 端这样定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//1. 声明 iOS 端的导出方法
[DllImport("__Internal")]
//IntPtr 即表示一个函数指针
static extern void bar(IntPtr barHandler);
//2. 声明回调方法的接口
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void BarHandler(string message);
//3. 实现回调方法
[MonoPInvokeCallback(typeof(BarHandler))]
public static void onBar(string message)
{
Debug.Log("[ReturnValue] " + message);
}
然后在 Unity 端这样调用:
1
2
3BarHandler handler = new BarHandler(onBar);
IntPtr functionPointer = Marshal.GetFunctionPointerForDelegate(handler);
bar (functionPointer);
拓展阅读DllImport(“__Internal”) iOS 平台上的插件是静态链接的,所以这里使用 [DllImport("__Internal")],其他平台的插件是动态链接的,使用 [DllImport("PluginName")]。
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] 用来标记方法为非托管 (Unmanaged) 方法,CallingConvention.Cdecl 是C 和 C++ 语言默认的调用约定。
MonoPInvokeCallback(typeof(BarHandler)) 在静态方法上使用,被 Mono 的预编译器用来将 native 回调方法转换为对应的托管方法,接收的参数为之前定义的非托管 delegate 的类型。