在实际使用中,将 Android 端代码编译成 aar 包供 Unity 端调用更有利于代码管理、人员分工、降低开发工具之间的耦合,而且目前有大量第三方库是通过 aar 包的形式提供。本节我们主要演示通过 Android Studio 生成 aar 包及在 Unity 中调用 aar 包中功能的流程及方法。
图1 创建新工程
选择 New Project 之后会打开新工程创建面板,如图 2 所示,在面板左侧列表中选择 Phone and Tablet,然后在右侧图像列表中选择 No Activity、Basic Activity、Empty Activity 模板之一,本节选择 Empty Activity 模板。
图 2 新工程创建面板
选择好所使用的工程模板后,点击 Next 进入工程信息填写面板,如图 3 所示,其中 Package name 即为所使用的包名,需要认真填写;Minimum SDK 建议选择 Android 8.0 以上(ARCore 不支持 Android 7.0 以下版本);其余项可根据需要填写,点击 Finish 按钮完成工程创建。
工程创建完成后,因为我们希望以模块的形式导出 aar 包,所以首先创建应用模块。在新创建的工程左侧 Project 列表面板 app 目录上鼠标右键,在弹出的级联菜单中依次选择 New →Module,如图 4 所示。
图 4 新建 Module 模块
在打开的新建模块面板中,因为我们希望创建 Android 类库,在其左侧列表中选择 Android Library,然后在右侧面板中填写 Module name(模块名)与其它信息,如图 5 所示,最后点击 Finish 按钮完成 android2unity 模块的创建 [在本示例中,工程名与模块名重名,实际使用中建议根据模块功能命名并避免与工程重名]。
Android Studio 会自动生成模块的层级结构,依次选择 android2unity 模块下的 java →com.davidwang.android2unity 包名,并在其上鼠标右键打开级联菜单,如图 6 所示。
在弹出的级联菜单中依次选择 New →Java Class 创建新类,本节类名命名为 example,并编写代码如下:
//代码片段1
//Java端代码
package com.davidwang.android2unity;
import android.app.Activity;
import android.widget.Toast;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class example {
private Activity _unityActivity;
private String _unityObject;
private String _unityMethod;
//初始化,注册Unity对象与回调方法
public void Init(String unityObject,String unityMethod){
try {
Class<?> classtype = Class.forName("com.unity3d.player.UnityPlayer");
Activity activity = (Activity) classtype.getDeclaredField("currentActivity").get(classtype);
_unityActivity = activity;
}
catch (ClassNotFoundException e) { }
catch (IllegalAccessException e) { }
catch (NoSuchFieldException e) { }
_unityObject = unityObject;
_unityMethod = unityMethod;
}
//通过UnitySendMessage方法传播消息
private boolean callUnity(String args){
try {
Class<?> classtype = Class.forName("com.unity3d.player.UnityPlayer");
Method method =classtype.getMethod("UnitySendMessage", String.class,String.class,String.class);
method.invoke(classtype,_unityObject,_unityMethod,args);
return true;
}
catch (ClassNotFoundException e) {}
catch (NoSuchMethodException e) {}
catch (IllegalAccessException e) {}
catch (InvocationTargetException e) {}
return false;
}
//调用Android端本地方法
public boolean showToast(String content){
Toast.makeText(_unityActivity,content,Toast.LENGTH_SHORT).show();
callUnity("Call from android");
return true;
}
}
在上述代码中,我们采用 UnitySendMessage() 消息传播方式回调 Unity 端的方法,由于 UnitySendMessage() 方法需要明确的场景中游戏对象、游戏对象脚本中方法名作为参数,为方便 Unity 中的调用,这里通过 Init() 方法由 Unity 端注入这两个参数,从而不需要一一绑定游戏对象与脚本方法名。同时,为避免引入 Unity 引擎的 Classes.jar 包,这里采用了反射的方式获取当前 Activity。
至此,Android 端的模块已完成,在左侧 Project 列表中选中 android2unity 模块,然后在 Android Studio 菜单中依次选择 Build →Rebuild Project 开始构建模块 aar 包,如图 7 所示,构建完成后,可以在 android2unity 模块下的 build →outputs →aar 目录中找到生成的 aar 包。
图 7 构建模块 aar 包
将该 aar 包文件复制到 Unity 工程 Assets/Plugins/Android 目录或其子目录下,完成 aar 包的导入。为测试其功能,在 Unity 中新建 Android2Unity.cs 脚本文件,代码如下:
//代码片段2
//C#端代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Android2Unity : MonoBehaviour
{
void Start()
{
using(AndroidJavaObject jo = new AndroidJavaObject("com.davidwang.android2unity.example"))
{
jo.Call("Init",gameObject.name, "AndroidCallback");
bool success = jo.Call<bool>("showToast", "Content from unity");
if (true == success)
{
Debug.Log("Method executed");
}
}
}
//Android端回调的方法
public void AndroidCallback(string str)
{
Debug.Log(str);
}
}
该脚本逻辑比较简单,首先通过实例化 Android 端的类,并通过其 Init() 方法注入回调游戏对象名及脚本方法名。将该脚文件挂载到场景中的任意对象上,连接手机,打包运行,可以看到,Unity 端的方法参数可以正确传递到 Android 端,Android 端也可以正确回调 Unity 端的脚本方法。
由于 UnitySendMessage() 方法需要遍历场景中的游戏对象,效率并不很高,特别是在频繁调用或者场景比较复杂的情况下可能会导致性能问题,在实际开发中,建议通过 AndroidJavaProxy 类交互。通过构建模块 aar 包的形式,有利于代码管理,并且可以方便的整合第三方类库,提供一个整体的模块方案,简化 Unity 端的调用形式。
在一些深度交互场合,比如 Activity 切换、程序启动预处理等,这时可能会需要继承 Application 和 UnityPlayerActivity 类,下面我们演示该功能。
本示例需要引入 UnityPlayer 类,该类位于 Unity 提供的 Classes.jar 包中 [该包文件位置与 Unity 安装路径和版本有关,如笔者使用 Unity2020.3.15,Unity 安装在 C 盘默认目录,其路径为:C:\Program Files\ Unity\ Hub\ Editor \2020.3.15f1c1\ Editor\ Data\ PlaybackEngines\ AndroidPlayer\ Variations\ il2cpp\ Release\ Classes],所以我们需要先引入该 Jar 包,首先直接复制 Classes.jar 包,然后在 Android Studio 工程 app 目录(本示例使用模块,所以我们将其放置到android2unity 模块的 libs 目录下)的 libs 文件夹上鼠标右键,在弹出的菜单中选择 Paste 粘贴 Classes.jar 包。然后打开同级目录的 build.gradle 文件,在其 dependencies 配置节中引入该 Jar 包,如图 8 所示。
由于我们只是引入其进行编译,保证编译正确进行,打包的时候并不需要 classes.jar 包,所以使用 complieOnly 指令确保该 Jar 包只用于编译 [修改完 build.gradle 文件后不要忘记点击 Sync Now 按钮以使配置起作用]。
在 Unity2019 以后的版本中,UnityPlayerActivity 类不再位于 Classes.jar 包,而是以独立的 UnityPlayerActivity.java 文件存在 [该 java 文件位置与 Unity 安装路径和版本有关,如笔者使用 Unity2020.3.15,Unity 安装在 C 盘默认目录,其路径为:C:\Program Files\ Unity\ Hub\ Editor\ 2020.3.15f1c1\ Editor\ Data\ PlaybackEngines\ AndroidPlayer\ Source\ com \unity3d\ player],将该文件复制到模块代码文件夹下。
//代码片断1
//Java端代码
package com.davidwang.android2unity;
import android.app.Application;
public class CustomApplication extends Application {
public void onCreate() {
super.onCreate();
System.out.println("自定义Application");
}
}
//代码片断2
//Java端代码
package com.davidwang.android2unity;
import android.os.Bundle;
public class CustomActivity extends UnityPlayerActivity {
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
System.out.println("自定义Activity");
}
}
正常导出 aar 包,将该 aar 包文件复制到 Unity 工程 Assets/Plugins/Android 目录下。由于我们继承了 Application 和 UnityPlayerActivity 类,实质上是修改了应用程序的入口,所以需要在 Unity 工程 Assets/Plugins/Android 目录下新创建 AndroidManifest.xml 文件,文件内容如下:
//代码片断3
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.davidwang.android2unity" >
<application
android:name="com.davidwang.android2unity.CustomApplication"
android:label="@string/app_name">
<activity android:name="com.davidwang.android2unity.CustomActivity"
android:label="@string/app_name"
android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data android:name="android.app.lib_name" android:value="unity" />
<meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="false" />
</activity>
</application>
</manifest>
AndroidManifest.xml 文件中第一行定义了应用程序包名,所以 Unity 工程中的包名必须与该包名一致。然后将应用程序入口(application 节)设置为自定义的 CustomApplication;将启动 Activity 设置为自定义的 CustomActivity,并设置了相应的 intent-filter 过滤器。
在导入该 aar 包后,Unity 打包时会引发配置错误,原因是 aar 包中 BuildConfig.class 类配置与 Unity 项目配置有冲突。将导入的 aar 包名后缀改为 rar,然后利用 rar 解压工具直接打开(不是解压出来),可以看到 classes.jar 包,鼠标双击在另一个解压界面中打开,然后点击项目包名依次展开,可以看到在工程/模块包名下有一个 BuildConfig.class 文件,鼠标右键在弹出的菜单中选择删除,关闭新打开的解压界面,回到原解压界面时会弹出提示,选择更新压缩文件,完成包内文件的删除,最后再将 rar 后辍改为 aar 即可。
如果是打包为 jar 文件,处理方法类似,但其包内没有 classes.jar 包,可以直接点击模块包名依次展开,删除 BuildConfig.class 文件,后续处理方法与前文一致。