有时候,我们需要在 Unity 里调用一些 Android 的功能,这些功能在 Unity 中可能并没有提供接口,需要在 Android 平台上实现。此时,我们需要有一个方法来让 Android 代码和 Unity 代码互调用。这里记录一下操作方法,并提供一个工具来简化两个工程之间的集成流程。
示例工程 #
下面的记录中所使用的工程可以参考 UnityAndroidExample。其中,根目录是 Unity 工程,可以直接用 Unity 打开。根目录下的 AndroidSample 子目录是 Android 工程,可以用 Android Studio 打开。
Unity 工程运行后如下图左所示,只有一个文本和一个按钮,点击按钮就会触发 Unity 到 Anrdoid 的调用,在主界面上产生一个 toast,同时,触发一次从 Anrdoid 到 Unity 的调用,主界面上的文本变为「Hello From Android」:
具体操作方式 #
新建一个 Android 工程 #
这里随便用 Android Studio 建立空一个工程就行了。建立好工程后,参考官方文档在工程里添加一个自定义的模块:
- 菜单栏点击 「File」-「New」-「New Module…」
- 弹出窗口中左侧选「Android Library」
- 右侧填入相关信息后创建模块
假设创建的模块名为「mod」,那么就会在工程根目录下新增一个名为 mod 的目录。此时可以删除工程根目录中默认创建的 app 目录,并将工程根目录中 settings.gradle 文件里的 include ':app'
这一行删除。
添加 Unity jar 依赖 #
为了在 Android 中和 Unity 互交互,我们需要引入 Unity 提供的库,这个库以 jar 包的形式提供。以下目录中都有这个 classes.jar 文件,有 mono 和 il2cpp 版本,还区分 Release 和 Development:
- YOUR_EDITOR_PATH/Data/PlaybackEngines/AndroidPlayer/Variations/il2cpp/Release/Classes
- YOUR_EDITOR_PATH/Data/PlaybackEngines/AndroidPlayer/Variations/mono/Release/Classes
- YOUR_EDITOR_PATH/Data/PlaybackEngines/AndroidPlayer/Variations/il2cpp/Development/Classes
- YOUR_EDITOR_PATH/Data/PlaybackEngines/AndroidPlayer/Variations/mono/Development/Classes
这里的「YOUR_EDITOR_PATH」是 Unity editor 程序所在的路径,例如,如果在 Windows 上用 Unity Hub 安装了 2020.3.5fc1 的 Unity,那么这个路径就是 C:Program FilesUnityHubEditor2020.3.5f1c1Editor。
参考Android 官方文档添加依赖,将该 jar 文件复制到 Android 工程中的对应模块的 libs 目录中,具体是复制哪一个 jar 无关紧要,因为后面的流程中并不会实际加入这个 jar 包。在复制的之后可以修改一个名字,例如修改为 unity.jar。然后修改 gradle 构建文件,注意这里是修改模块目录下的 build.gradle 而非根目录下的。在 dependencies
中添加如下内容:
dependencies {
// ... 前面有一堆默认的
// 添加 unity 的 jar
// 注意这里必须是 compileOnly 以避免该 jar 包被打入 aar 包中,否则会在之后发生命名冲突
compileOnly files('./libs/unity.jar')
// 如果还有别的自定义的 jar 就用 implementation
implementation files('./libs/some_other_lib.jar')
}
修改完后,Android Studio 会提示是否要同步,点击「Sync Now」即可。
引入 UnityPlayerActivity
#
我们在实现自己的 Activity 时不能直接实现,而是需要继承 Unity 的 UnityPlayerActivity
,这个类型会按照一定的规则去调用 Unity 的回调函数,以确保程序的正确运行。从前这个类就在刚刚我们引入的 unity.jar 中,而在新版本的 Unity 中这个类却以单独文件的形式存在,需要自己拷贝一下,这个文件所在的路径为:YOUR_EDITOR_PATH/Data/PlaybackEngines/AndroidPlayer/Source/com/unity3d/player/UnityPlayerActivity.java。
我们直接将 YOUR_EDITOR_PATH/Data/PlaybackEngines/AndroidPlayer/Source/com 这个目录直接拷到工程里的 mod/src/main/java 目录下,这样一来,我们的代码就可以看到这个 Activity 了。
新增一个 Activity 继承 UnityPlayerActivity
#
在这里,我们添加一个 showMessage
函数用于给 Unity 调用,同时,在这个函数里面,我们通过 UnityPlayer.UnitySendMessage
来调用 Unity 中的函数。这个 UnityPlayer
定义于我们引入的 unity.jar 文件中。其中第一个参数是 Unity 场景中的对象名,第二个参数是需要调用的函数名,第三个参数是传递的参数:
public class MainActivity extends UnityPlayerActivity {
// 被用于 Unity 调用的函数
public void showMessage(final String message) {
runOnUiThread(() -> Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show());
// 调用 Unity 的函数
UnityPlayer.UnitySendMessage("Canvas", "ChangeText", "Hello from Android!");
}
}
此时,文件结构如下图所示:
构建模块 #
菜单栏中选择「Build」-「Make Module ‘mod’」。等待构建完成后,会在 mod/build/output/aar 目录下看到构建出来的包。
和 Unity 集成 #
接下来,我们需要将这个库和 Unity 集成,并让 Unity 以这个 Activity 为入口启动程序。
将这个 aar 包解压,放入 Unity 工程的 Plugins/Android/mod 目录下,然后在这个目录下建立一个 project.properties 文件,填入如下内容:
代码语言:javascript复制android.library=true
再在 Plugins/Android 目录下(和 mod 同级)建立一个 AndroidManifest.xml 文件,填入如下内容,注意其中的 ACTIVITY_NAME
需要换成 main Activity 的完整类名(完整包名加上类名)。如果有什么需要申请的权限,也可在此加入:
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unity3d.player"
android:installLocation="preferExternal"
android:versionCode="1"
android:versionName="1.0">
<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:xlargeScreens="true"
android:anyDensity="true"/>
<application
android:theme="@style/UnityThemeSelector"
android:icon="@drawable/app_icon"
android:label="@string/app_name"
android:debuggable="true">
<activity android:name="ACTIVITY_NAME"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
</activity>
</application>
</manifest>
要在 Unity 中调用 Android 的函数,需要用类似这样的方法实现。首先,我们要找到当前的 Android Activity,然后我们通过 Call
方法来调用其中的逻辑。其中第一个参数是方法名,后面的参数是需要传递的参数:
#if UNITY_ANDROID && !UNITY_EDITOR
using (AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
{
using (AndroidJavaObject activity = jc.GetStatic<AndroidJavaObject>("currentActivity"))
{
activity.Call("showMessage", "Hello from Unity");
}
}
#else
#endif
这一块代码用 #if UNITY_ANDROID && !UNITY_EDITOR
包裹,只在 Android 设备上生效。
另外我们还需要在 Unity 场景中添加刚刚 Android 代码中调用 Unity 时 Unity 侧的接收者(在本示例中为 Canvas
),这块具体操作直接参考示例工程即可。
Unity 构建 #
在 Unity 菜单中点击「File」-「Build Settings…」,在弹出的窗口中选择 Android 平台,然后构建即可。
一个小工具 #
上面这个流程有些是只用操作一次的(例如新建工程),但也存在一批需要反复操作的(例如编译 Android 工程、删除 Unity 的 Activity 等),这些需要反复操作的流程在每次修改 Android 工程中的代码后,都需要进行一次,非常麻烦还容易出错,因此,这里提供一个简单的小工具来简化这个工作。这个工具的安装需要用到 go,需要先安装一下 go 的环境。
这个小工具可以编译指定的 Android 模块,然后将 aar 压缩包解压到 Unity 工程中,删除 Unity 的 Activity class,并生成 project.properties 和 AndroidManifest.xml 文件。在生成 AndroidManifest.xml 的时候,提供了默认的文件模板,允许通过命令行参数指定需要申请的 Android 权限。
例如这样的命令:
代码语言:javascript复制upack -a ./AndroidSample -e com.example.mod.MainActivity -m mod -p android.permission.BATTERY_STATS ./Assets/Plugins/Android
可以将 Android 工程 AndroidSample 编译,然后将数据解压到 ./Assets/Plugins/Android 目录下,其中参数 -e
用来指定入口 Activity 的类型全名,参数 -m
用来指定 Android 模块名,-p
用来指定需要申请的权限,如果有多个权限需要申请,则可以增加多个 -p
参数。
在示例工程中也可以体验这个工具,每次修改这个 Android 工程中的代码,都可以执行一下工程根目录下的 update_android.bat 脚本,这个脚本会调用这个工具,重新构建 Android 工程并自动将相关内容解压到 Unity 工程中。