在有的应用场景下,需要把 unity 项目导出作为 lib 运行在终端平台上,之前也零碎记了一些开发过程中遇到的问题,此文总结一下经验,并长期更新此场景下的问题。
官方示例项目:https://github.com/Unity-Technologies/uaal-example

android 平台工程导出

导出方法

image.png

file —— build settings,将平台切换为 android 平台,导出即可。

image.png

导出的工程目录结构如上,unityLibrary 目录下的工程可以 build 出一个 aar 的包。

launcher 目录下的工程依赖 unityLibrary 工程,可以用来 build 出来一个 apk 应用。

mono 模式和 il2cpp 模式

导出时,在 player settings —— other settings —— scripting backend 中,可以选择 mono 模式或者 il2cpp 模式。

Mono模式:

  • 运行时需要Mono虚拟机(Mono Runtime),占用一定的内存和CPU资源。
  • 在编译时,Unity会将C#代码编译为字节码,然后在Android设备上运行时使用Mono虚拟机将字节码转换为机器码。这种方式在一定程度上会影响游戏的性能。
  • Mono模式下编译出的apk文件比较小,但运行时需要额外的Mono虚拟机支持。
  • Mono只支持打包为32位指令集(使用32位指令集的计算机内存最大只有4G)。
  • 适用于简单的游戏或应用程序,不需要特别高的性能要求。

IL2CPP模式:

  • 运行时不需要Mono虚拟机,减少了内存和CPU资源的占用。
  • 在编译时,Unity会将C#代码编译为C++代码,然后使用C++编译器将其转换为机器码。这种方式可以提高游戏的性能。
  • IL2CPP模式下编译出的apk文件比较大,但运行时不需要额外的Mono虚拟机支持。
  • IL2CPP支持64位(兼容32位)
  • 适用于要求高性能的游戏或应用程序,可以提高游戏的帧率和运行速度。

如果不需要太高的性能,可以选择Mono模式,它可以使得游戏包更小,占用更少的存储空间。但如果需要高性能,可以选择IL2CPP模式,以提高游戏的性能和运行速度。

通信

unity 中调用 android 方法

1
...
// 初始化一个 java 类
AndroidJavaClass jc = new AndroidJavaClass("com.tencent.ivh.IvhSDKCallback");
// java类中实现一个单例方法,调用这个方法获取类的实例
this.callbackObj = jc.CallStatic<AndroidJavaObject>("getInstance");
// 调用类的方法,并传入参数
callbackObj.Call("OnChatResponse", JsonMapper.ToJson(chatRsp));
// 调用类的方法,并获取返回值
string str = callbackObj.Call<string>("OnCredential");
...

android 中调用 unity 方法

要在 android 中调用 unity 的方法,需要在场景中创建一个 gameobject,然后在 gameobject 上挂载一个 MonoBehaviour 组件,MonoBehaviour 上的 public 方法可以被 android 调用。

但是有一个限制,只能接收一个字符串类型的参数,更复杂的数据结构需要自行序列化和反序列化。

1
2
3
4
5
package com.unity3d.player;
...
// 调用 unity
UnityPlayer.UnitySendMessage("sdk", "Init", JSONObject.toJSONString(options));
...

以上代码中, sdk 是场景中挂载的 gameobject 名,Init 是 gameobject 上挂载的 MonoBehaviour 暴露出来的 public 的方法名。

在 android 中渲染 unity 视图

获取渲染 view

可以直接使用 com.unity3d.player.UnityPlayer 类,也可以继承后实现自己的 UnityPlayer 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.unity3d.player;
public class MyUnityPlayer extends UnityPlayer {
private Context mContext;
public MyUnityPlayer(Context context) {
super(context);
this.mContext = context;
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
}

@Override
public void addView(View child) {
if(child instanceof SurfaceView) {
((SurfaceView) child).setZOrderOnTop(true);
((SurfaceView) child).setZOrderMediaOverlay(true);
((SurfaceView) child).getHolder().setFormat(PixelFormat.TRANSLUCENT);
}
super.addView(child);
}
}

传入一个 activity 对象,实例化 unity 视图类,并从中获取 view 对象,加入到应用视图中

1
2
3
4
5
// 需要传入一个存活状态的 activity 对象
UnityPlayer ivhSDK = new IvhSDK(this, new MyUnityPlayer(this));
RelativeLayout layout = (RelativeLayout) findViewById((R.id.vhView));
View vhView = this.ivhSDK.mUnityPlayer.getView();
layout.addView(vhView);

生命周期绑定

UnityPlayer 类提供了一些和 android 生命周期对应的生命周期函数。

1
2
3
4
5
6
7
public void resume();
public void pause();
public void destroy();
public void windowFocusChanged(boolean hasFocus);
public void lowMemory();
public void configurationChanged(Configuration newConfig);
...

需要将这些生命周期函数和实例化 Player 时传入的 activity 的对应的生命周期函数绑定起来,在 activity 的生命周期函数中调用 Player 对应的生命周期函数。

这个绑定操作非常重要,如果生命周期函数绑定不当,会导致一些很难排查的问题。

windowFocusChanged 绑定不当造成的视图卡死

例如没有绑定 windowFocusChanged,可能会导致渲染视图在 pause 然后 resume 后无法恢复,一直处于暂停状态,如 这个帖子 就提到了这种问题。。

destroy 绑定不当造成横竖屏切换时应用 crash

横竖屏切换导致的crash

image.png

漏掉 destroy 绑定会导致这种很难排查的,匪夷所思的 crash。

生成带符号的 libunity.so 以排查问题

使用 il2cpp 模式导出时,会将一些 unity 自身和渲染的逻辑打包在动态库中,端上运行 crash 后比较难排查问题。

复现问题后,可以导出带符号的 debug 版本,结合 addr2line 工具来分析错误堆栈,定位到具体的逻辑。

unity il2cpp导出时生成符号表排查crash问题

生成带符号的动态库:

image.png

使用 addr2line 定位问题

image.png

其它问题和注意事项

unity bug: game_view_content_description 配置缺失会导致无法启动

使用此模块的 Android 应用中,需要在 src/main/res/values/strings.xml 中加上 game_view_content_description 字段配置,:


<string name=p_name”>VpaDemo
Game view

此配置缺失会导致应用无法启动,目前来看是 unity 一些版本的 bug,我在 unity pro 2021.3.6f1c1 中导出的项目存在此问题。

unity bug: 帧同步设置导致部分设备偶现长时间运行后渲染卡死

也是一个很难排查的问题,见 unity android 长时间运行时 Filter0 线程造成渲染卡死的问题

image.png

在 android 平台上有一个默认打开的 “Optimized Frame Pacing” 选项,大部分设备下运行都不会有问题,但是少量设备偶现长时间运行后,渲染帧率越来越低,直到完全卡死。

有一个名为 Filter0 的线程会吃满1核的CPU,排查得让人头秃,最后发现是 unity 的一个 bug

il2cpp 代码优化导致资源包中的动画失效

playerSettings —— stripEngineCode 代码优化开启后可能导致资源包中的 classID 找不到等问题,参考 https://www.jianshu.com/p/e7120f025281

比如我遇到的是 Could not produce class with ID 74,去 unity 的 ClassIDReference文档 里边一查,确实就是 AnimationClip 类找不到了。在Assets目录下创建 link.xml,内容写

<linker>
  <!--Preserve types and members in an assembly-->
  <assembly fullname="UnityEngine.AnimationModule">
    <type fullname="UnityEngine.AnimationClip" preserve="all"/>
  </assembly>
</linker>

重新导出打包,问题解决。 注意,assembly 名称,可以在 IDE 中点类名进去看头部声明查找到。

抗锯齿设置

如果目标设备可能涉及低 dpi 的设备,导出项目时可以打开抗锯齿设置(不同渲染管线打开方法不同),抗锯齿默认是关闭的。

image.png

☞ 参与评论