7.Unity和Android交互总结

  1. 7.总结
    1. 7.1 核心要点速览
      1. 系列在解决什么问题
      2. jar 与 aar
      3. Unity 与 Android Studio 要对齐的开关
      4. 把「可安装应用」改成「给 Unity 引用的库」
      5. 接入 Unity 运行时:classes.jar
      6. UnityPlayerActivity 与 MainActivity
      7. AndroidManifest.xml 要点
      8. 产出 aar
      9. Unity 侧调用 Android:入口与常用 API
      10. 把插件放进 Unity
      11. 打 Android 包时处理 aar 与 Unity 自带类的冲突
      12. Android 侧调用 Unity
      13. Android 四大组件与 Unity 导出的心智模型
      14. AndroidManifest.xml 在系统侧干什么
      15. Unity 里打开原生 Activity(嵌入 Android UI)
      16. Android 宿主里打开 Unity 画面
      17. 两种嵌入方向的取舍
    2. 7.2 面试题精选
      1. 基础题
        1. 1. jar 和 aar 有什么区别?Unity 侧更常见接哪种?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. 为什么要把 Android 工程从 application 改成 library,并删掉 applicationId?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. Android 四大组件分别做什么?和 Unity 导出后的对应关系是什么?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        4. 4. AndroidManifest.xml 的唯一性与启动入口
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
      2. 进阶题
        1. 1. Unity 里怎样拿到 Java 的 MainActivity 并调用成员方法?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. 导入 aar 后 Unity 打包报类重复或冲突,通常要对 aar 做哪些处理?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. Android 如何回调 Unity 脚本?UnitySendMessage 三个参数各是什么?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        4. 4. 在 Unity 里打开第二个原生 Activity,为什么常要先导出 Android 工程再用 Android Studio 打包?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        5. 5. 安装后桌面出现两个应用图标,一般怎么改?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        6. 6. Android 回调跑在子线程时,如何安全切回 Unity 主线程?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
      3. 深度题
        1. 1. 实例成员与静态成员在 Unity 侧为什么都用 androidJavaObject 调?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. UnitySendMessage 传参只有字符串,项目里怎么传结构化数据?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. 混合开发时,为何更常推荐「Unity 内嵌 Android」而不是「Android 内嵌 Unity」?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        4. 4. IL2CPP 与 Mono 作为脚本后端,对 Android 原生插件形态有什么差异?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        5. 5. Release 开启 R8 / ProGuard 后,通过字符串反射调 Java 可能出什么问题?如何规避?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章

7.总结


7.1 核心要点速览

系列在解决什么问题

  • 要在 Android 真机包里让 C# 与 Java/Kotlin 互通:一边调系统能力(相册、推送、支付 SDK),一边让原生代码回调 Unity 里的逻辑。
  • 工程习惯:先在 Android Studio 里做一个 Library 模块,打出 jar / aar,再丢进 Unity 的 Plugins/Android,最后和 Unity 工程一起导出 APK。

jar 与 aar

类型 大致包含 和 Unity 交互时的常见用法
jar 类与清单等,不带 res 资源 老 Eclipse 流程里更常见
aar src + res + lib 打成的归档 Android Studio 导出给 Unity 时更常用

和 C# 里引 dll 一样,都是「把别人写好的库塞进工程」;差别在于 **aar 里能带布局、图片等 res**,jar 多半只有字节码,缺资源时还要自己补。

:渠道只给 aar,你拖进 Plugins/Android,清单和 libs 会跟着合并进最终 APK。

Unity 与 Android Studio 要对齐的开关

  • Unity 先 切换到 Android 平台,在 Player Settings 里设好 包名Minimum API Level,和后面 AS 工程保持一致。
  • Android Studio 新建 Phone / Tablet、Empty Activity;若本机没有对应 SDK / Build-Tools,在 SDK Manager 里装到与 Unity 勾选版本一致。
  • 模板工程里常带 **测试目录、无用 res**,按项目规范清掉;删包名相关目录时,有的向导要 执行两次 才删净。

把「可安装应用」改成「给 Unity 引用的库」

appbuild.gradle 里:

  • com.android.application 改成 **com.android.library**。
  • defaultConfig 里 **删掉 applicationId**(库模块不需要应用 id)。
  • 右上角 Sync Now 同步 Gradle。

这样打出来的才是能塞进 Unity Plugins 的 aar,而不是独立 APK 工程。

接入 Unity 运行时:classes.jar

  • 从 Unity 安装目录取:
    Data\PlaybackEngines\AndroidPlayer\Variations\mono 或 il2cpp\Release\Classes 下的 **classes.jar**(不同脚本后端二选一对应路径)。
  • 复制到 AS 工程 **app\libs**,对 jar 右键 → Add As Library,Gradle 才会参与编译。

UnityPlayerActivityMainActivity

  • 将 Unity 自带的 UnityPlayerActivity 相关源码从
    PlaybackEngines/AndroidPlayer/Source/... 整包拷到 app/src/main/java 下(保持包结构)。
  • 让 **MainActivity 继承 UnityPlayerActivity**,在 onCreate 里 **注释掉 setContentView**,避免再套一层布局把 Unity 画面盖住。
  • 示例在子类里加 TestFun(实例)、**TestStaticFun(静态)**,用来验证 Unity → Android 调用是否通。

AndroidManifest.xml 要点

  • application 里删掉模板自带的无效配置,避免和 Unity 合并清单时冲突。
  • 承载 Unity 的 activity 上增加:
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
  • MainActivity类名建议写全限定名,合并清单时更清晰。

产出 aar

  • 菜单 **Build → Make Module app**(或当前 app 模块名)。
  • 编译区若出现 红色提示,有时是 编码显示问题,再执行一次 Make 可能消失;若 Gradle 在 拉依赖,等下载完成后再编。
  • 在模块 build/outputs/aar/ 下取 **.aar**,拷进 Unity 的 Plugins/Android

库模块、classes.jarMainActivity 继承关系、清单里的 unityplayer.UnityActivity 缺一环,后面 JNI 或导出都会怪在「上一环没配对」上,排查时按这条链往回查即可。

Unity 侧调用 Android:入口与常用 API

UnityPlayer 是 Unity 在 Android 上挂好的 Java 入口;**currentActivity** 就是当前前台那个 Activity(一般是继承 UnityPlayerActivityMainActivity)。拿到它的 AndroidJavaObject 之后,Java 里写的 字段、实例方法、静态成员 才能从 C# 这边触达。

  • 实例Get<int>("testI")Set<int>("testI", 11)Call<string>("TestFun")
  • 静态GetStatic / SetStaticCallStatic,同一套 API 命名,只是对应 JVM 里的 static
  • 也可以用 new AndroidJavaClass("你的.完整类名") 专门调某个 上的静态成员;和「只持有 currentActivity 再调 *Static」二选一即可,项目里别混用两套风格

编辑器里跑 Windows/Mac 时,JNI 行为不完整,以真机或模拟器上打出来的包为准;调试 UI 用 UGUI 文本比纯 Debug.Log 更直观。

最小调用骨架(与系列正文一致):

using (var jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
using (var act = jc.GetStatic<AndroidJavaObject>("currentActivity"))
{
    string s = act.Call<string>("TestFun");
}

把插件放进 Unity

Assets/Plugins/Android 下放 aar 和 **AndroidManifest.xml**(子目录规则按 Unity 版本与合并策略来,核心是 库和清单一起进打包管线)。

打 Android 包时处理 aar 与 Unity 自带类的冲突

打包密钥与工程路径尽量 非中文;若直接导入 aar 报错,按控制台提示处理;实践中常把 aar 当 zip 解压,删掉冲突项再压回:

操作 目的
删掉 aar 里 libs/classes.jar Unity 导出时会再带 **classes.jar**,避免 重复
删掉 aar 内 **classes.jar 里的 UnityPlayerActivity.class**(文中删 unity3d 整个包路径 Unity 自带 **UnityPlayerActivity**,避免 类冲突
删掉 classes.jar 里的 BuildConfig.class 库模块的构建配置类 不需要 再进 Unity 合并流程,减少冲突

若仍报 相同错,多半是 没删干净 或 Unity/Gradle 缓存;清干净后重新 Make aar、替换插件、再生成应用,错误信息会进入下一步。

Android 侧调用 Unity

Java 侧用 **UnityPlayer.UnitySendMessage**,本质是往 Unity 主线程消息队列里塞一条 字符串消息,由运行时去 按 GameObject 名字找脚本、再调方法

  • C# 方法必须在 MonoBehaviour、挂在 激活物体上;方法名、物体名要和字符串 完全一致(大小写敏感)。
  • 签名:**UnityPlayer.UnitySendMessage(场景里物体名, 方法名, string 或 null)**。系列示例:物体名 **Lesson03**,方法 **TestAndroidCallUnity**,第三个参数传 "AndroidCallUnity" 这种纯文本。
  • 结构体、多参数没有直接通道,先 JSON / 自定义分隔串,到 C# 再解析。

改完 MainActivity 里调 Unity 的那段 Java重打 aar → 覆盖 Unity 插件 → 再按前文对 aar 做三类删减 → 出 Android 包 → 真机验证。

Android 四大组件与 Unity 导出的心智模型

组件 角色(结合本系列)
Activity 一屏界面;Unity 导出后 整局游戏跑在继承 UnityPlayerActivity 的那一页
Service 后台长任务、无界面,如上传、音乐播放
BroadcastReceiver 收系统或其它应用的广播
ContentProvider 跨应用数据共享

写原生业务时 十有八九在写 Activity;本系列讲的 JNI、UnitySendMessage、多 Activity,也都绕不开 Activity + Manifest

AndroidManifest.xml 在系统侧干什么

  • 文件名固定 AndroidManifest.xml每个 APK 一份总表:组件、权限、应用图标/主题、桌面点哪个 Activity 启动,全在这里登记。
  • 常见标签:manifestuses-permissionapplicationactivitymeta-data(例如 Unity 的 unityplayer.UnityActivity)、intent-filteraction / category)。

后面合并 Unity 导出清单 + 插件清单 + unityLibrary 时,脑子里先有这张表,谁声明 LAUNCHER、谁只声明普通 Activity,就不容易乱。

Unity 里打开原生 Activity(嵌入 Android UI)

Unity 进程里 MainActivity 仍是 Java 侧的入口;你在上面加 OpenActivity(),里面 new Intent(this, SecondActivity.class) + startActivity,就等于从游戏 切到原生 XML 布局那一屏

  • Android:新建 Empty Activity,Manifest 里多一条 activityOpenActivity() 写在继承 UnityPlayerActivity 的类里即可(类名、布局名与工程一致)。
  • Unity:缓存 AndroidJavaClass / AndroidJavaObject,按钮里 Call("OpenActivity")OnDestroyDispose + 置空,避免 JNI 引用挂着不放。

为什么要 Export Android Project 再用 Android Studio 打包:第二屏若用了 ConstraintLayout、AndroidX、Material,依赖写在 build.gradle 里由 Gradle 拉;Unity 编辑器里 直接 Export APK 往往 配不齐这些库。导出 Gradle 工程 → AS Sync → 缺啥 implementation 啥,比对着报错改。

常见坑(对照正文排查)

现象 常见处理方向
运行闪退、ClassNotFound 缺依赖,在对应模块 build.gradleimplementation
安装完桌面 两个图标 合并后 **多个 Activity 都带了 MAIN + LAUNCHER**,在 unityLibrary 或插件 Manifest去掉多余 LAUNCHER
布局 inflation 报错 补 **constraintlayout**、开 android.useAndroidX=true
AppCompat 主题报错 给 Activity 配兼容主题,或该 Activity **直接继承 Activity**(按正文场景选)

Android 宿主里打开 Unity 画面

原生 App **先起自己的 MainActivity**,按钮里 startActivity(new Intent(this, UnityPlayerActivity.class)),全屏进 Unity。

工程拼接顺序(与第 6 篇一致):Unity Export Android Project → 拷贝 unityLibrary 进宿主 → settings.gradleinclude ':unityLibrary'gradle.propertiesunityStreamingAssets=...app/build.gradleimplementation project(':unityLibrary') → Sync。

Manifest:宿主与 unityLibrary 各有一份,要分清 谁是桌面入口(LAUNCHER)Unity 的 Activity 是否还多带一份 LAUNCHER,否则双图标。

两种嵌入方向的取舍

  • 原生为主、再进 Unity:关掉 Unity 那一屏时,经常变成整个进程结束,想「回到上一屏原生界面」栈行为 不好预期(正文把它当硬限制讲)。
  • Unity 为主、内嵌原生页:游戏界面 startActivity 打开第二屏原生,和「手游里弹 WebView / 登录页」同一类结构,本系列默认推荐这条

选型前在真机上 按返回键、finish Activity 各试一遍,比只看文档靠谱。


7.2 面试题精选

基础题

1. jar 和 aar 有什么区别?Unity 侧更常见接哪种?

题目

请说明 jar 与 aar 在内容组成上的差异,并说明在 Android Studio 出库 → Unity Plugins/Android → 打 APK 这条链路里,为什么多数团队更倾向交付 aar

深入解析
  • jar:偏 纯类文件;老工具链里常见。
  • aar:Android Library 打出来的 归档,里层通常有 编译产物 + res + libs + 依赖描述,和 Unity 插件「一坨丢进 Plugins」的工作流更贴。
  • Unity 侧 并不解析 Gradle,你能在 AS 里 Make Module 出 aar,比在 Unity 里手搓 jar + 补资源省心。
答题示例

jar 更像「只有代码」;aar 把资源、三方 jar 一起打好了,拖进 Unity 不容易漏文件。

Android Studio 做库模块默认就是 aar;跟 Unity 对接时一般也是 aar 进 Plugins

参考文章
  • 1.交互项目创建

2. 为什么要把 Android 工程从 application 改成 library,并删掉 applicationId?

题目

build.gradle 里把 com.android.application 改成 com.android.library 并移除 applicationId,这样做的目的是什么?若保留 application 配置会有什么问题?

深入解析
  • application 插件:生成的是 可安装 APK 的应用模块,有独立 applicationId,打出来是「一个 App」。
  • library 插件:生成的是 被别的工程或 Unity 依赖的库,最终要和 Unity 导出的 主 APK 合并,不能自己占一个独立应用 id 去当入口。
  • 若不改:你得到的是 另一个 App 工程,而不是 可嵌入 Unity Player 的 aar;Unity 导入插件时期望的是 库产物,合并清单时也会按 库模块 规则处理。
答题示例

application 打出来是独立应用;Unity 需要的是 Android Library,导出 aar 给主工程引用。

库模块不能有 applicationId 这一套「独立包名安装」的配置,否则模块性质不对,也没法按插件方式并进 Unity 的 Android 打包管线。

参考文章
  • 1.交互项目创建

3. Android 四大组件分别做什么?和 Unity 导出后的对应关系是什么?

题目

简述 Activity、Service、Broadcast Receiver、Content Provider 各解决什么问题;并说明 Unity 游戏导出为 Android 安装包后,在组件层面可以怎样理解。

深入解析
  • Activity:一屏界面;Service:后台长任务;BroadcastReceiver:收广播;ContentProvider:跨应用数据。
  • 写业务时 Activity 占大头;Unity 导出 APK,本质是 一个(继承 UnityPlayerActivity 的)Activity 里跑游戏循环,和原生同事对齐 界面、权限、启动入口 都围绕它展开。
答题示例

四个组件分工不同:有界面的是 Activity,后台跑的是 Service,收系统广播的是 Receiver,管跨应用数据的是 Provider。

Unity 打成 Android,可以简单记成 「整包游戏挂在一个 Activity 上」,JNI、UnitySendMessage、多 Activity,都是在 Activity + Manifest 这套模型上叠功能。

参考文章
  • 4.Android开发的必备原理

4. AndroidManifest.xml 的唯一性与启动入口

题目

说明每个应用对清单文件的命名与份数要求,并解释带 MAINLAUNCHERintent-filter 和桌面图标启动的关系。

深入解析
  • 每应用 必须且通常仅一个 AndroidManifest.xml,声明组件、权限、元数据等。
  • intent-filter 声明组件能响应的 IntentMAIN + LAUNCHER 表示该 Activity 作为 启动器入口,桌面图标指向它。
  • Unity 插件里的清单会与主工程 合并,冲突时需理解 谁在声明 LAUNCHER Activity
答题示例

清单文件名必须是 AndroidManifest.xml,里面写组件、权限、包信息、默认启动 Activity 等。

intent-filter 里配 MAINLAUNCHER 的 Activity 会出现在桌面,点图标就进这个 Activity。Unity 导出也会带自己的启动 Activity 配置,和插件清单要能 合并 不打架。

参考文章
  • 4.Android开发的必备原理

进阶题

1. Unity 里怎样拿到 Java 的 MainActivity 并调用成员方法?

题目

请写出从 C# 获取当前 Activity 并调用 TestFun(返回 string)的典型代码骨架,并说明 com.unity3d.player.UnityPlayercurrentActivity 各自代表什么。

深入解析
  • com.unity3d.player.UnityPlayer:Unity 随包带的 Java 类;currentActivity 静态字段指向 当前前台 Activity(一般是你的 MainActivity)。
  • GetStatic<AndroidJavaObject>("currentActivity") 拿到 JNI 句柄后,**Call<string>("方法名")** 调 Java 实例方法;无参就只传方法名字符串。
  • **using / Dispose**:避免 JNI 局部引用 堆积;长生命周期脚本里尤其别忘在 OnDestroy 里释放。
答题示例

UnityPlayer 里取 currentActivity,就是当前跑游戏的那个 Activity 对象。

在这个 AndroidJavaObjectCall<string>("TestFun"),等价于在 Java 里 **mainActivity.TestFun()**。

using 包一层,出了作用域自动 Dispose,省得 JNI 引用泄漏。

参考文章
  • 2.Unity调用Android

2. 导入 aar 后 Unity 打包报类重复或冲突,通常要对 aar 做哪些处理?

题目

说明为什么要从 aar 里删除 libs/classes.jar、为什么要从 classes.jar 里去掉 UnityPlayerActivity 相关类以及 BuildConfig.class

深入解析
  • **libs/classes.jar**:Unity 打包 Android 时会 再次打入 自己的 **classes.jar**,aar 里再带一份会 重复
  • **UnityPlayerActivity**:宿主 Activity 由 Unity 运行时提供,aar 里再带一份 UnityPlayerActivity.class 会与 合并后的 dex 冲突;删除 unity3d下对应类文件即可。
  • **BuildConfig**:库模块生成的 构建配置类 参与 Unity 合并时 不需要,且易 多模块冲突,删掉可减少无意义冲突。
答题示例

aar 里自带的 libs/classes.jar 和 Unity 自带的重复,要删掉 aar 里那份。

classes.jar 里的 UnityPlayerActivity Unity 自己会带,不删会类重复;一般删 unity3d 整个目录下的那份。

BuildConfig 是库模块构建配置,Unity 打包用不上还容易冲突,也从 jar 里抠掉。

参考文章
  • 2.Unity调用Android

3. Android 如何回调 Unity 脚本?UnitySendMessage 三个参数各是什么?

题目

说明 UnityPlayer.UnitySendMessage 的三个参数含义,以及 C# 侧方法要满足哪些前置条件才能被调到。

深入解析
  • 三参:Hierarchy 里物体名public 方法名、**仅 Stringnull**。
  • C#:**MonoBehaviour、物体处于激活状态、方法名与第二参完全一致**。
  • 常见写法:Java 在 TestFun 返回前 调一次 UnitySendMessage,把原生结果 推回 Unity 改 UI
  • 线程与性能(高频追问)UnitySendMessage 须在 Unity 主线程侧处理;Android 若在 非主线程(如网络、部分 SDK 回调)里直接调,可能 不稳定或无效。Unity 提供 AndroidApplication.InvokeOnUnityMainThread,把 委托切回 Unity 主线程再调 C#(具体 API 以当前 Editor 文档为准)。另:实现上依赖 按字符串找物体 + 反射式派发不适合每帧高频(社区与实测里常作为瓶颈点讨论);高频路径更倾向 缓存引用、原生插件直调、或换通道
答题示例

三个参数:物体名方法名字符串载荷;没有多参数重载。

脚本挂在激活物体上,public void Foo(string msg) 这种接就行。

要传结构体就先 JSON 序列化,Unity 里再反序列化。

原生回调若在 后台线程,别直接 UnitySendMessage,用 InvokeOnUnityMainThread 包一层再调 Unity;别在热路径里狂发消息,成本主要是 字符串查找 + 派发

参考文章
  • 3.Android调用Unity

4. 在 Unity 里打开第二个原生 Activity,为什么常要先导出 Android 工程再用 Android Studio 打包?

题目

说明「Unity 直接打 APK」与「Export Project + AS 打包」在依赖与 Gradle 配置上的差异,并联系 ConstraintLayout、AndroidX 等场景。

深入解析
  • Unity 直接 Export APK 时,Gradle 依赖树由 Unity 生成脚本管;你新加的原生布局若依赖 AndroidX / ConstraintLayout,往往要在 **模块 build.gradle 里显式 implementation**,这一步 在 AS 里改最直观
  • Export Project 后打开 Gradle 工程,**Sync → 看报错 → 补依赖 / 改 gradle.properties**,和写普通 Android 工程一样排。
答题示例

第二屏用了 XML 布局和 Support/AndroidX 库,依赖写在 Gradle 里,Unity 内置打包不一定帮你配全。

导出工程用 Android Studio 打开,缺包就加 implementation,再处理 AndroidXR8 开关之类,和原生同事排障同一套流程。

嵌入多 Activity 时 Export Project + AS 比编辑器里硬导出 APK 可控得多

参考文章
  • 5.Unity中嵌入Android内容

5. 安装后桌面出现两个应用图标,一般怎么改?

题目

解释「双 LAUNCHER」成因,并说明在 Unity 导出工程 + 多模块清单合并时,常在哪个模块的 AndroidManifest.xml 里动刀。

深入解析
  • 每个带 MAIN + LAUNCHERactivity 都会在启动器里 占一个图标
  • Unity 导出 + 插件 + unityLibrary 合并 Manifest 时,很容易出现 两个入口都带 LAUNCHER;保留 业务真正要启动的那一个,另一份 **删掉 LAUNCHERcategory**(或整段 intent-filter),只留 普通 activity 声明 即可。
答题示例

桌面出现两个图标,本质是 两个 Activity 都被标成「可启动」

打开合并后的 **AndroidManifest.xml**,重点查 unityLibrary 和插件 里是否多了一条 LAUNCHER,把多余那条去掉,只留主工程入口。

重装 APK,启动器里应只剩一个图标。

参考文章
  • 5.Unity中嵌入Android内容

6. Android 回调跑在子线程时,如何安全切回 Unity 主线程?

题目

说明为何 UnityPlayer.UnitySendMessage 与对 AndroidJavaObject 的调用通常应落在 Unity 主线程;并说明 AndroidApplication.InvokeOnUnityMainThread 的典型用法(如第三方 SDK 异步回调)。

深入解析
  • Android 侧 网络、蓝牙、部分系统回调 常在 非 UI 线程;若直接调 Unity 的 JNI 封装,易出现 时序问题、崩溃或无效调用
  • Unity 提供 **UnityEngine.Android.AndroidApplication.InvokeOnUnityMainThread**(以当前版本 Scripting API 为准),把 委托投递到 Unity 主线程执行,再在回调里 UnitySendMessage、改 UI、写 C# 状态
  • Android UI 线程Unity 主线程 不是同一套调度;是否允许在非主线程调 JNI官方文档与实测 为准,上线前 真机 + 线程日志 验收。
答题示例

SDK 回调在 worker 线程,不要直接 UnitySendMessage

AndroidApplication.InvokeOnUnityMainThread 把一段 lambda 丢回 Unity 主线程里执行,再在回调里改 UI 或发消息。

上线前用 真机 + Log 线程名 验证一遍,比纯猜线程安全。

参考文章
  • 2.Unity调用Android
  • 3.Android调用Unity

深度题

1. 实例成员与静态成员在 Unity 侧为什么都用 androidJavaObject 调?

题目

Get/SetGetStatic/SetStatic 分别对应 Java 哪类成员?为什么示例里静态变量和静态方法仍通过 currentActivity 对应的 AndroidJavaObject 调用,而不是另写 new AndroidJavaClass("包名.MainActivity")

深入解析
  • 实例:JNI 需要 对象引用Get/Set/Call 走实例成员。
  • 静态:对应 jclass 上的 static 成员;Unity 封装成 GetStatic / CallStatic 系列。文档和示例里常见写法是:**拿着 currentActivityAndroidJavaObject,再调 *Static***,由底层解析到 正确的 jclass
  • 也可以 new AndroidJavaClass("完整类名") 只表示 类本身,再调静态成员;两种都合法,别在同一模块里混用两套句柄语义
  • JNI 引用与生命周期(加分项)AndroidJavaObject 底层持有 JNI 局部/全局引用;长生命周期脚本里 Dispose() + 置空 可避免 引用堆积(与通用 JNI「局部引用要及时释放」同一类问题)。
答题示例

实例成员用 Get/Call,静态成员用 GetStatic/CallStatic,名字上已经区分了。

示例从 currentActivityAndroidJavaObject,静态方法仍在这个对象上 CallStatic,属于 Unity 官方也这么写的封装路径。

想更「纯」一点可以 new AndroidJavaClass("包名.MainActivity") 只表示类;团队里定一种风格即可。

别忘了 AndroidJavaObject 底层是 JNI,该 Dispose 就 Dispose,长驻单例尤其要注意。

参考文章
  • 2.Unity调用Android

2. UnitySendMessage 传参只有字符串,项目里怎么传结构化数据?

题目

说明该 API 的参数限制,并给出一种在 Unity 与 Android 之间传递复杂数据的工程化做法(不必贴完整解析代码)。

深入解析
  • 接口约定:第三参 Stringnull没有 多参数或二进制直传。
  • 常见做法:JSON 字符串、约定 分隔符Base64 等,接收端再反序列化或拆分。
  • 数据量大或要高频率交互时,应评估 别通道(文件、Socket、双向 JNI 等),不要 用超长字符串硬塞 UnitySendMessage
  • 主线程与 ANR(Android 侧常追问)UnitySendMessage 触发的 C# 逻辑跑在 Unity 主线程;若在里面做 重计算、同步 IO,会 拖帧;Android 官方文档里对 Unity 游戏 ANR 也有专项说明——主线程长耗时 是典型风险之一。
答题示例

第三参只能是 string 或 null,结构体、字节数组都要先 编码成字符串

工程上 JSON 最省事;协议简单也可以用 自定义分隔符 拼一条再 Split

载荷很大或要 高频双向 通信,应改用 文件、Socket、直接 JNI 调 C# 封装 等,UnitySendMessage 只适合做 低频通知

收到消息后 别在主线程里解析巨型 JSON 或读文件,大载荷拆 分帧后台线程预处理,再切回 Unity 线程,避免 卡顿和 ANR

参考文章
  • 3.Android调用Unity

3. 混合开发时,为何更常推荐「Unity 内嵌 Android」而不是「Android 内嵌 Unity」?

题目

结合「Android 中嵌入 Unity」一文对 Activity 退出行为的描述,说明 Android 宿主启动 UnityPlayerActivity 这一路径的主要风险,以及「Unity 中嵌入 Android」在工程上的优势。

深入解析
  • 原生先进 UnityUnityPlayerActivity 往往在 单独任务栈 或与宿主 强绑定;用户按返回或 finish 时,进程级行为容易表现为 直接退出应用,而不是回到上一个原生 Activity
  • Unity 里 startActivity 开原生页:主界面仍是 Unity,原生页是 子屏,返回栈更接近常见 「游戏 → 二级页」 的心智;正文也把这种 Unity 为主 的接法当默认推荐。
答题示例

原生里 startActivity(UnityPlayerActivity) 再关 Unity 屏,返回栈和进程生命周期往往不符合「只想回到原生首页」的预期,真机上多按几次返回就清楚。

Unity 里 Call("OpenActivity") 打开原生第二屏,主进程仍是 Unity,更像手游里 弹设置页、用户协议 那类需求。

混合开发能选架构时,优先 Unity 包一层原生;若必须 原生壳包 Unity,提前和程序、测试 对齐返回键、杀进程 的验收标准。

参考文章
  • 5.Unity中嵌入Android内容
  • 6.Android中嵌入Unity内容

4. IL2CPP 与 Mono 作为脚本后端,对 Android 原生插件形态有什么差异?

题目

从 Unity Manual 中「Android 原生插件」相关说明出发,说明 IL2CPPMono.so / 静态库 / C++ 源码 等支持上的区别,以及对 包体与迭代 的取舍。

深入解析
  • Unity 文档对 Android 原生插件 的说明里:脚本后端 会影响 可用的原生插件类型(例如 IL2CPP 可涵盖 共享库、静态库、C++ 源码 等形态,Mono 侧通常以 共享库 .so 为主——以当前 Unity 版本 Manual 为准)。
  • IL2CPP:AOT,运行时 性能与包体/编译时间 与 Mono 权衡不同,线上手游 Android 发布 常见选型。
  • Mono:JIT,迭代与包体 往往更轻,但 原生侧能力集 与 IL2CPP 不完全一致。
  • 口述顺序:先对齐 Manual 里的表(支持哪些插件类型),再补 选 IL2CPP 的动机(性能、64 位、商店/合规等)。
答题示例

文档里写得很清楚:IL2CPP 和 Mono 能接的原生插件类型不一样,IL2CPP 一侧更全,Mono 往往只接 .so 这一类。

线上 Android 包常见 IL2CPP;编辑器里迭代有时用 Mono 换编译速度,以团队规范为准。

具体 表格式当前 Unity 版本 Manual 为准,别背死老版本。

参考文章
  • 2.Unity调用Android
  • Unity Manual:Scripting backendsAndroid native plug-ins(与当前 Editor 版本一致)

5. Release 开启 R8 / ProGuard 后,通过字符串反射调 Java 可能出什么问题?如何规避?

题目

说明 代码压缩与混淆AndroidJavaClass("包名.类名")、JNI 按类名找方法 的影响;并说明 -keep 规则自定义 proguard-user 文件 在 Unity Android 工程里的典型用法。

深入解析
  • Android R8 / ProGuard删除未直接引用代码混淆类名/方法名;而 Unity 侧 字符串里写死的类名 在静态分析里 不可见,容易导致 **ClassNotFoundExceptionNoSuchMethodError**。
  • 工程上应对:对 JNI 暴露的 Java 包加 -keep(或 Unity 提供的 用户 ProGuard 配置入口),保证 类名、被调方法名 不被裁掉或混淆错;Unity 版本升级 后需 回归 Release 包
  • aar 里删 BuildConfig 是不同问题:前者是 发布 shrink,后者是 与 Unity 合并重复类
答题示例

Release 一开混淆,我 new AndroidJavaClass("com.foo.Sdk") 字符串还在,类在 dex 里被改名或删了,运行时直接崩。

JNI 入口包-keep class com.foo.** { *; } 一类规则,打进 user proguard,或按 Unity 文档放在 指定路径

每次 升级 Gradle / Unity,打 Release 在真机跑一遍 SDK 全链路,比 Debug 全绿更有说服力。

参考文章
  • 2.Unity调用Android

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏