运行于Android平台的原生App直接调用Android接口,可以享受近水楼台先得月的优势,而使用Unity开发的Android应用App则像是二等公民,使用Android原生功能特性就要麻烦得多,比如WiFi、蓝牙等,特别是一些高级功能特性,Unity中没有完全覆盖,直接在Unity中开发显得力不从心,而且,Unity为适应跨平台开发部署需求,其引擎架构设计要复杂灵活得多,基于Unity引擎开发的App应用运行于独立的VM(Virtual Machine,虚拟机)中(采用IL2CPP后端编译的应用,运行时仍然需要虚拟机支持),这给App应用与Android原生系统代码的交互带来了困难。在实际应用开发中,基于Unity的App应用与底层的Android平台之间经常有交互需求,本系列我们主要学习Unity引擎与Android平台的交互通信。
(一) Android与Unity通信原理
Unity引擎最大的优势和特点是一次制作、多端部署,极大的减轻了多平台游戏的开发和维护成本,而Unity引擎实现强大跨平台能力的基础是Mono / IL2CPP,Mono / IL2CPP是Unity引擎跨平台的核心和根本。
在2001年,电信标准组织ECMA制定了一个与特定语言无关的跨体系结构的运行环境CLI(Common Language Infrastructure,公共语言基础)标准规范,只要使用规范定义的高级语言进行开发、应用程序符合CLI规范即可以确保在不同的计算机体系结构上实现跨平台运行。在此基础上,微软公司根据标准实现了.NET Framework公共语言运行时(Common Language Runtime,CLR),因此CLR是CLI的一个实现,.NET Framework即是一个运行于CLR基础上的框架,它支持C#、VB.NET、C++、Python等语言,但由于.NET Framework与Windows的深厚渊源,.NET Framework本身并不能跨平台。
Mono则是Xamarin公司主导的另一个CLI实现,它通过内置C#语言编译器、CLR运行时和各类基础类库,可以使应用程序运行在Windows、Linux、FreeBSD、Android、iOS等各种平台上,因此,通过Mono能使用C#语言编写Android或iOS应用程序。在CLI规范中,高级语言并不是直接被编译成机器字节码,而是编译成中间语言(Intermediate Language,IL),这是一种介于高级言与机器字节码之间的与特定底层硬件无关的语言,在真正需要执行的时候,IL会被加载到Mono VM中[ Unity支持C#、Unity Script、Boo三种脚本开发语言,所有高级语言都会被编译成IL中间语言。],由VM动态的编译成机器码再执行(Just In Time,JIT编译),其执行过程如图1(a)图所示。
虽然IL2CPP最后是采用AOT方式直接编译成各平台原生机器码,但其继承了IL中间语言的机制,对上层语言几乎没有影响(因为C++是静态语言,采用AOT方式编译,JIT动态语言的一些特性将不再可用),但它也需要借助VM进行内存管理等工作,如图1(b)所示。Android原生应用采用Java语言编写,Java语言也是先编译为Bytecode IL中间语言,然后依赖Android上的dalvik / art虚拟机解释执行。所以Unity引擎与Android原生代码交互通信实际是两个VM之间的通信,如图2所示。虽然Unity代码与Android原生代码在不同的VM中执行,但它们都处于同一个进程中,因此可以共享数据。
在执行层面,UnityEngine封装了AndroidJavaObject、AndroidJavaClass、AndroidJavaProxy类,通过这几个类就可以获取Android端静态类或者动态对象,从而可以执行其相应方法;Android则是通过Unity应用的mainActivity与C#代码通信[ 本节讲述的Unity与Android的交互通信实质上是指C#代码与Java代码、JAR包、AAR包、SO包的相互调用,但遵循习惯描述为Unity引擎与Android操作系统软件之间的通信。]。
Activity是Android应用中最基本和最重要的组件之一,其作用类似于web中的page、winform中的form,因此,只有活跃的Activity才能响应输入,所以,Android代码首先要通过com.unity3d.player.UnityPlayer包下的currentActivity获取到活跃Activity,利用其与Unity代码通信。
(二) Unity直接调用Java代码
Unity2018之后的版本统一使用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所示。
公共方法 | 调用对象/属性类型 | 泛型方法 | 描述 |
---|---|---|---|
Call | 非静态 | Call | 调用对象非静态方法 |
CallStatic | 静态 | CallStatic | 调用类静态方法 |
Get | 非静态 | Get | 获取对象非静态属性 |
GetStatic | 静态 | GetStatic | 获取类静态属性 |
Set | 非静态 | Set | 设置对象的非静态属性 |
SetStatic | 静态 | SetStatic | 设置类静态属性 |
GetRawClass | - | GetRawClass | 获取原生Java类型的指针,用于JNI |
GetRawObject | - | GetRawObject | 获取原生Java对象的指针,用于JNI |
Dispose | - | - | IDisposable接口回调 |
AndroidJavaObject是java.lang.Object类在Unity中的表达,而java.lang.Object类是Java中所有类的基类,因此其能接受所有的Java对象,其公共方法如表2所示。
公共方法 | 调用对象/属性类型 | 泛型方法 | 描述 |
---|---|---|---|
Call | 非静态 | Call | 调用对象非静态方法 |
CallStatic | 静态 | CallStatic | 调用类静态方法 |
Get 非静态 | Get | 获取对象非静态属性 | |
GetStatic | 静态 | GetStatic | 获取类静态属性 |
Set | 非静态 | Set | 设置对象的非静态属性 |
SetStatic | 静态 | SetStatic | 设置类静态属性 |
GetRawClass | - | GetRawClass | 获取原生Java类型的指针,用于JNI |
GetRawObject | - | GetRawObject | 获取原生Java对象的指针,用于JNI |
Dispose | - | - | IDisposable接口回调 |