UVP 价值专家 | Unity与Android交互通信(中)

文摘   游戏   2024-08-16 20:02   上海  
这篇文章来自 Unity 社区资深开发者 DavidWang。汪老师是 CSDN 博客专家,专注图形学 / XR 开发,著有多本基于 Unity 进行 AR/MR 开发的专业技术书籍,申请发明专利十余项,软件著作权若干。
本文带来汪老师的系列博客《Unity 与 Android 交互通信》第 3-4 篇:模块化调用、UnityPlayerActivity 类的继承和使用点击阅读原文,可以访问汪老师的 CSDN 个人主页,阅读更多技术干货。

上篇文章中,我们已经能够通过直接使用 Java,或者通过 AndroidJavaClass、AndroidJavaObject 这两个类实现在 Unity 端和 Android 原生端的通信。这已经可以解决很多问题,但这种方式不够模块化,不够优雅。

在实际使用中,将 Android 端代码编译成 aar 包供 Unity 端调用更有利于代码管理、人员分工、降低开发工具之间的耦合,而且目前有大量第三方库是通过 aar 包的形式提供。本节我们主要演示通过 Android Studio 生成 aar 包及在 Unity 中调用 aar 包中功能的流程及方法。

在 Android Studio 中生成 aar 包
(1)创建新 Android Studio 工程。在 Android Studio 中,依次选择 File →New → New Project,如图 1 所示。

图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 按钮完成工程创建。

图 3 填写工程信息

工程创建完成后,因为我们希望以模块的形式导出 aar 包,所以首先创建应用模块。在新创建的工程左侧 Project 列表面板 app 目录上鼠标右键,在弹出的级联菜单中依次选择 New →Module,如图 4 所示。

图 4 新建 Module 模块

在打开的新建模块面板中,因为我们希望创建 Android 类库,在其左侧列表中选择 Android Library,然后在右侧面板中填写 Module name(模块名)与其它信息,如图 5 所示,最后点击 Finish 按钮完成 android2unity 模块的创建 [在本示例中,工程名与模块名重名,实际使用中建议根据模块功能命名并避免与工程重名]。

图 5 Module 模块信息填写面板

Android Studio 会自动生成模块的层级结构,依次选择 android2unity 模块下的 java →com.davidwang.android2unity 包名,并在其上鼠标右键打开级联菜单,如图 6 所示。

图 6 在模块下新建 Java 类

在弹出的级联菜单中依次选择 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 包

在 Unity 中调用 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 端的调用形式。

通过这种包/库的方式调用就舒服多了。
UnityPlayerActivity 类的继承和使用

在一些深度交互场合,比如 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 所示。


图 8 引入 classes.jar 包

由于我们只是引入其进行编译,保证编译正确进行,打包的时候并不需要 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 { @Override public void onCreate() { super.onCreate(); System.out.println("自定义Application"); }}
继承 UnityPlayerActivity 类的示例代码如下:
//代码片断2//Java端代码package com.davidwang.android2unity;import android.os.Bundle;
public class CustomActivity extends UnityPlayerActivity { @Override 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 过滤器。

因为我们是直接继承了 Unity 的主 Activity,所以不需要在 Unity 场景中做任何处理,打包到真机运行,可以看到正确的自定义内容输出。
提示

在导入该 aar 包后,Unity 打包时会引发配置错误,原因是 aar 包中 BuildConfig.class 类配置与 Unity 项目配置有冲突。将导入的 aar 包名后缀改为 rar,然后利用 rar 解压工具直接打开(不是解压出来),可以看到 classes.jar 包,鼠标双击在另一个解压界面中打开,然后点击项目包名依次展开,可以看到在工程/模块包名下有一个 BuildConfig.class 文件,鼠标右键在弹出的菜单中选择删除,关闭新打开的解压界面,回到原解压界面时会弹出提示,选择更新压缩文件,完成包内文件的删除,最后再将 rar 后辍改为 aar 即可。

如果是打包为 jar 文件,处理方法类似,但其包内没有 classes.jar 包,可以直接点击模块包名依次展开,删除 BuildConfig.class 文件,后续处理方法与前文一致。

我们将继续更新《Unity 与 Android 交互通信系列》下篇:AndroidJavaProxy 代理及广播订阅。点击阅读原文,可以访问 DavidWang 在 CSDN 的技术专栏,持续学习更多内容。
长按关注
Unity 官方开发者服务平台
第一时间了解 Unity 社区动向,学习开发技巧

 点击“阅读原文”,访问 DavidWang 的主页 



Unity官方开发者服务平台
Unity引擎官方开发者服务平台,分享技术干货、学习课程、产品信息、前沿案例、活动资讯、直播信息等内容。
 最新文章