在实际应用开发中,基于 Unity 的 App 应用与底层的 Android 平台之间经常有交互需求,本系列我们主要学习 Unity 引擎与 Android 平台的交互通信。
图 2 Unity 与 Android 交互通信示意图
在图 2 中,Unity 引擎通过 UnityEngine 提供的 API 调用 Android 方法,Android 则借助于 com.unity3d.player 包提供的 API 调用 Unity 方法。通过这种方式,Unity 引擎可以直接调用 Android 类及对象的方法,而 Android 则只能调用 Unity 中指定 GameObject 所挂载的脚本的方法,或者通过动态代理的方式调用 Unity 引擎中的方法。
在执行层面,UnityEngine 封装了 AndroidJavaObject、AndroidJavaClass、AndroidJavaProxy 类,通过这几个类就可以获取 Android 端静态类或者动态对象,从而可以执行其相应方法;Android 则是通过 Unity 应用的 mainActivity 与 C# 代码通信 [ 本节讲述的 Unity 与 Android 的交互通信实质上是指 C# 代码与 Java 代码、JAR 包、AAR 包、SO 包的相互调用,但遵循习惯描述为 Unity 引擎与 Android 操作系统软件之间的通信。]。
Unity 2018 之后的版本统一使用 Gradle 进行 Android 端的编译、构建和打包,而 Android Studio 也使用 Gradle 进行编译、构建和打包,即它们都使用同一种编译构建工具,也即是 Java 代码与 C# 代码都可以在 Unity 中被正确的编译到 Android 端,这就为在 Unity 中直接使用 Java 与 C# 语言打下了基础。
而且为方便映射 Java 数据结构,在 UnityEngine 类中还内置了若干封装好的类,其中最重要的类有:AndroidJavaClass、AndroidJavaObject、AndroidJavaProxy,这些类是进行 Java 端与 C# 端相互调用的基础。AndroidJavaClass 是 java.lang.Class 类在 Unity 中的表达,主要用于类结构反射、获取类静态属性或者调用类静态方法,其公共方法如表 1 所示。
通过表 1 和表 2 可以看到,这两个类公共方法完全一样,这就为开发者使用这两个类提供了完全一致的使用外观。在使用中,通过 AndroidJavaClass 类的 forName() 方法、.class 属性生成相应类的对象,由于 Class 类方法常用于反射,所以一般用于调用对应类的静态属性或者方法;AndroidJavaObject 表示对象,通过其 getClass() 方法可以获取该对象的类型,所以一般用于调用对象的实例方法或者属性。例如有一个类 com.davidwang.util,其有一个静态方法 StaticMethod(),一个实例方法 InstanceMethod(),则 new AndroidJavaClass("com.davidwang.util").callstatic("StaticMethod") 等同于调用 util.StaticMethod(); 而 new AndroidJavaObject("com.davidwang.util").call("InstanceMethod") 等同于 new Util().InstanceMethod()。
//代码片断1
package com.example.davidwang;
public class Unity2Java {
public static void StaticPrint(String str){
System.out.println(str);
}
public static int StaticAdd(int a,int b)
{
return a+b;
}
public void DynamicPrint(String str){
System.out.println(str);
}
public int DynamicAdd(int a,int b)
{
return a+b;
}
}
找到该类所在的 .java 文件,并将该文件复制到 Unity 工程 Assets/Plugins/Android 目录或其子目录下,然后在 Unity 工程窗口(Project 窗口)中选中该 java 文件,在属性窗口(Inspector 窗口)中查看其导入设置,确保 Android 平台被选择,如图 3 所示。
//代码片断2
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Android2Unity : MonoBehaviour
{
void Start()
{
using (AndroidJavaClass Unity2JavaClass = new AndroidJavaClass("com.example.davidwang.Unity2Java"))
{
Unity2JavaClass.CallStatic("StaticPrint", "Hello World from Android static method ");
int result1 = Unity2JavaClass.CallStatic<int>("StaticAdd", 1, 1);
Debug.Log("结果1:" + result1);
Unity2JavaClass.Call("DynamicPrint", "Hello World from Android dynamic method ");
int result2 = Unity2JavaClass.Call<int>("DynamicAdd", 1, 2);
Debug.Log("结果2:" + result2);
}
using (AndroidJavaObject Unity2JavaObject = new AndroidJavaObject("com.example.davidwang.Unity2Java"))
{
Unity2JavaObject.CallStatic("StaticPrint", "Hello World from Android static method ");
int result3 = Unity2JavaObject.CallStatic<int>("StaticAdd", 1, 3);
Debug.Log("结果3:" + result3);
Unity2JavaObject.Call("DynamicPrint", "Hello World from Android dynamic method ");
int result4 = Unity2JavaObject.Call<int>("DynamicAdd", 1, 4);
Debug.Log("结果4:" + result4);
}
}
}
在 Unity 中,将 Android2Unity 脚本挂载到场景中的任意对象上,连接手机,打包运行[ Java 代码编译后运行于 dalvik / art 虚拟机,其控制台输出不能输出到 Unity Debug 窗口,所以只能通过真机运行,Logcat 查看输出。],输出结果如下:
行号 类型 输出信息
1 System.out Hello World from Android static method
2 Unity 结果1:2
3 Unity
4 Unity 结果2:0
5 System.out Hello World from Android static method
6 Unity 结果3:4
7 System.out Hello World from Android dynamic method
8 Unity 结果4:5
上述代码首先演示了 Java 端无返回值、有返回值方法的调用,通过泛型方法定义返回值类型获取 Java 端方法执行结果,由于数据类型的不同,Java 端与 C# 端交互支持的类型有 string、int、float、bool、AndroidJavaObject 共 5 类(也可以返回这些数据类型的数组);
其次演示了静态方法和实例方法的调用,类静态方法使用带 static 后辍的方法访问,而对象实例方法则使用不带 Static 后辍的方法调用;再次演示了 AndroidJavaClass 和 AndroidJavaObject 类使用上的区别,通过输出结果,可以看到,AndroidJavaClass 调用对象实例方法既不报错,也不执行;AndroidJavaObject 类调用类静态方法可以正常执行 [ Java 语言支持实例对象调用类静态方法或者获取类静态属性,这与 C# 语言不同。],虽然我们使用时都使用了 new 关键字,但 AndroidJavaClass 类不会生成实例对象,而 AndroidJavaObject 类会实例化对象。
类与实例的属性获取/设置与上述方法使用基本一致,通过这种方式,就可以直接在 C# 代码中调用 Java 端的原生类,代码如下 [ 为了简化排版,后续 Java 代码与 C# 代码将放置于同一个代码片断中,并使用注释进行说明。]:
//代码片断3
//Java端代码
package com.example.davidwang;
import android.app.Activity;
import android.widget.Toast;
public class Unity2Java {
public boolean ShowToast(Activity activity, String str){
Toast.makeText(activity,str,Toast.LENGTH_SHORT).show();
return true;
}
}
//C#端代码
//获取设备UUID
private string GetAndroidID()
{
string androidID = "NONE";
using (AndroidJavaObject contentResolver = new AndroidJavaClass("com.unity3d.player.UnityPlayer").GetStatic<AndroidJavaObject>("currentActivity").Call<AndroidJavaObject>("getContentResolver"))
{
using (AndroidJavaClass secure = new AndroidJavaClass("android.provider.Settings$Secure"))
{
androidID = secure.CallStatic<string>("getString", contentResolver, "android_id");
}
}
return androidID;
}
//调用Andriod端Toast
private void ShowToast()
{
if (Application.platform == RuntimePlatform.Android)
using (AndroidJavaObject Unity2JavaObject = new AndroidJavaObject("com.example.davidwang.Unity2Java"))
{
using (AndroidJavaObject activity = new AndroidJavaClass("com.unity3d.player.UnityPlayer").GetStatic<AndroidJavaObject>("currentActivity"))
{
bool isSuccess = Unity2JavaObject.Call<bool>("ShowToast", activity, "From Unity");
Debug.Log("ShowToast Status :" + isSuccess);
}
}
}
在上述代码片断 3 中,我们演示了两种直接调用 Android 端原生类的方式,第一种方法是通过直接获取 Android 端的原生类,调用其静态方法;第二种是通过调用自定义的 Java 类间接调用 Android 原生类方法。同时,由于 C# 代码运行平台不确定,为确保代码兼容多平台,我们也使用了两种判断代码执行平台的方法,第一种使用预编译指令区分平台,另一种通过 Application 类直接判断当前运行平台,这也是在多平台开发中经常使用的技巧。
除此之外,代码还演示了获取当前活动 Activity 的方法,即通过 com.unity3d.player.UnityPlayer 类获取当前 Activity,Android 很多类都需要传递活动的 Activity 或者 Context 上下文对象,通过这种方式获取当前 Activity 是一种常用方法。
使用 AndroidJavaClass 和 AndroidJavaObject 类直接调用 Java 代码或 Android 原生类非常方便,但只能是单向由 C# 调用 Java 代码,Java 没办法反向调用 C# 代码,实现反向调用则必须使用 AndroidJavaProxy 类,正如其名,这是个代理类,负责在 Java 和 C# 代码之间桥接,后文我们还会详细介绍该类。
如前文所述,Java 端与 C# 端交互支持的类型有 string、int、float、bool、AndroidJavaObject 共 5 类,AndroidJavaObject 可以表示所有对象类型,因此,除 string、int、float、bool 4 种基本类型,其余对象都可由 AndroidJavaObject 表达。C# 端调用 Java 端方法千差万别,但 Java 端调用 C# 端就以上 5 类(包括数组则共 10 类),因此我们可以编写一个通用的框架,因为结构稍微有点复杂,涉及到 C# 端 AndroidCallbackManager.cs、AndoridCallbackInterface.cs 两个脚本文件,Java 端 CallUnityInterface.java、Java2Unity.java 两个代码文件。
//代码片断4
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AndroidCallbackManager : MonoBehaviour
{
void Start()
{
using (AndroidJavaObject activity = new AndroidJavaClass("com.unity3d.player.UnityPlayer").GetStatic<AndroidJavaObject>("currentActivity"))
{
using (AndroidJavaObject appController = new AndroidJavaObject("com.example.davidwang.Java2Unity"))
{
AndoridCallbackInterface callback = new AndoridCallbackInterface("com.example.davidwang.CallUnityInterface");
callback.stringCallBack = StringProcess;
callback.intCallBack = IntProcess;
callback.floatArrayCallBack = FloatArrayProcess;
appController.Call("Init", activity, callback);
Debug.Log("In Start");
}
}
}
public void StringProcess(string str)
{
Debug.Log("string callback :"+str);
}
public void IntProcess(int value)
{
Debug.Log("int callback :" + value);
}
public void FloatArrayProcess(float[] arr)
{
foreach (var value in arr)
Debug.Log("float in arr:" + value);
}
}
在代码片断 4 中,AndroidCallbackManager 类是 Unity 端的使用类,是调用入口,其首先获取到当前 Activity,实例化 Java 端的 Java2Unity 类,并且同时实例化了一个 C# 端的 AndoridCallbackInterface 类。然后设置 AndoridCallbackInterface 类的回调方法之后调用了 Java 端的初始化方法。
AndoridCallbackInterface.cs 文件代码如下:
//代码片断5
using System;
using UnityEngine;
public class AndoridCallbackInterface : AndroidJavaProxy
{
public Action<AndroidJavaObject> javaObjectCallBack;
public Action<bool> boolCallBack;
public Action<string> stringCallBack;
public Action<int> intCallBack;
public Action<float> floatCallBack;
public Action<float[]> floatArrayCallBack;
//构造方法
public AndoridCallbackInterface(string interfaceName) : base(interfaceName)
{
}
public void JavaObjectCallBack(AndroidJavaObject _data)
{
if (javaObjectCallBack != null)
javaObjectCallBack(_data);
}
public void BoolCallBack(bool _data)
{
if (boolCallBack != null)
boolCallBack(_data);
}
public void StringCallBack(string _data)
{
if (stringCallBack != null)
stringCallBack(_data);
}
public void IntCallBack(int _data)
{
if (intCallBack != null)
intCallBack(_data);
}
public void FloatCallBack(float _data)
{
if (floatCallBack != null)
floatCallBack(_data);
}
public void FloatArrayCallBack(float[] _data)
{
if (floatArrayCallBack != null)
floatArrayCallBack(_data);
}
}
//代码片断6
package com.example.davidwang;
public interface CallUnityInterface {
public void JavaObjectCallBack(Object _data);
public void BoolCallBack(boolean _data);
public void StringCallBack(String _data);
public void IntCallBack(int _data);
public void FloatCallBack(float _data);
public void FloatArrayCallBack(float[] _data);
}
//代码片断7
package com.example.davidwang;
import android.content.Context;
public class Java2Unity {
private Context context = null; //上下文对象
private CallUnityInterface callback = null; //缓存回调
public void Init(Context context , CallUnityInterface callback)
{
this.context = context;
this.callback = callback;
try {
java.lang.Thread.sleep(5000);
Run();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
private void Run()
{
int i = 100;
String str = "From Java";
float[] floatArr = {1.01f,1.02f,1.03f};
this.callback.IntCallBack(i);
this.callback.StringCallBack(str);
this.callback.FloatArrayCallBack(floatArr);
}
}
图 4 Java 端与 C# 端相互调用关系示意图
在 Unity 中,将 AndroidCallbackManager 脚本挂载到场景中的任意对象上,连接手机,打包运行 [ Java 代码编译后运行于 dalvik / art 虚拟机,其控制台输出不能输出到 Unity Debug 窗口,所以只能通过真机运行,Logcat 查看输出。],输出结果如下:
行号 类型 输出信息
1 Unity int callback :100
2 Unity string callback :From Java
3 Unity float in arr:1.01
4 Unity float in arr:1.02
5 Unity float in arr:1.03
6 Unity In Start