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 引用的库」
在 app 的 build.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 才会参与编译。
UnityPlayerActivity 与 MainActivity
- 将 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.jar、MainActivity 继承关系、清单里的 unityplayer.UnityActivity 缺一环,后面 JNI 或导出都会怪在「上一环没配对」上,排查时按这条链往回查即可。
Unity 侧调用 Android:入口与常用 API
UnityPlayer 是 Unity 在 Android 上挂好的 Java 入口;**currentActivity** 就是当前前台那个 Activity(一般是继承 UnityPlayerActivity 的 MainActivity)。拿到它的 AndroidJavaObject 之后,Java 里写的 字段、实例方法、静态成员 才能从 C# 这边触达。
- 实例:
Get<int>("testI")、Set<int>("testI", 11),Call<string>("TestFun")。 - 静态:
GetStatic/SetStatic、CallStatic,同一套 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 启动,全在这里登记。 - 常见标签:
manifest、uses-permission、application、activity、meta-data(例如 Unity 的unityplayer.UnityActivity)、intent-filter(action/category)。
后面合并 Unity 导出清单 + 插件清单 + unityLibrary 时,脑子里先有这张表,谁声明 LAUNCHER、谁只声明普通 Activity,就不容易乱。
Unity 里打开原生 Activity(嵌入 Android UI)
Unity 进程里 MainActivity 仍是 Java 侧的入口;你在上面加 OpenActivity(),里面 new Intent(this, SecondActivity.class) + startActivity,就等于从游戏 切到原生 XML 布局那一屏。
- Android:新建 Empty Activity,Manifest 里多一条
activity;OpenActivity()写在继承UnityPlayerActivity的类里即可(类名、布局名与工程一致)。 - Unity:缓存
AndroidJavaClass/AndroidJavaObject,按钮里Call("OpenActivity");OnDestroy里Dispose+ 置空,避免 JNI 引用挂着不放。
为什么要 Export Android Project 再用 Android Studio 打包:第二屏若用了 ConstraintLayout、AndroidX、Material,依赖写在 build.gradle 里由 Gradle 拉;Unity 编辑器里 直接 Export APK 往往 配不齐这些库。导出 Gradle 工程 → AS Sync → 缺啥 implementation 啥,比对着报错改。
常见坑(对照正文排查)
| 现象 | 常见处理方向 |
|---|---|
| 运行闪退、ClassNotFound | 缺依赖,在对应模块 build.gradle 加 implementation |
| 安装完桌面 两个图标 | 合并后 **多个 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.gradle 写 include ':unityLibrary' → gradle.properties 配 unityStreamingAssets=... → app/build.gradle 加 implementation 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 的唯一性与启动入口
题目
说明每个应用对清单文件的命名与份数要求,并解释带 MAIN 与 LAUNCHER 的 intent-filter 和桌面图标启动的关系。
深入解析
- 每应用 必须且通常仅一个
AndroidManifest.xml,声明组件、权限、元数据等。 intent-filter声明组件能响应的 Intent;MAIN+LAUNCHER表示该 Activity 作为 启动器入口,桌面图标指向它。- Unity 插件里的清单会与主工程 合并,冲突时需理解 谁在声明 LAUNCHER Activity。
答题示例
清单文件名必须是
AndroidManifest.xml,里面写组件、权限、包信息、默认启动 Activity 等。
intent-filter里配MAIN和LAUNCHER的 Activity 会出现在桌面,点图标就进这个 Activity。Unity 导出也会带自己的启动 Activity 配置,和插件清单要能 合并 不打架。
参考文章
- 4.Android开发的必备原理
进阶题
1. Unity 里怎样拿到 Java 的 MainActivity 并调用成员方法?
题目
请写出从 C# 获取当前 Activity 并调用 TestFun(返回 string)的典型代码骨架,并说明 com.unity3d.player.UnityPlayer 与 currentActivity 各自代表什么。
深入解析
com.unity3d.player.UnityPlayer:Unity 随包带的 Java 类;currentActivity静态字段指向 当前前台 Activity(一般是你的MainActivity)。GetStatic<AndroidJavaObject>("currentActivity")拿到 JNI 句柄后,**Call<string>("方法名")** 调 Java 实例方法;无参就只传方法名字符串。- **
using/Dispose**:避免 JNI 局部引用 堆积;长生命周期脚本里尤其别忘在OnDestroy里释放。
答题示例
UnityPlayer里取currentActivity,就是当前跑游戏的那个 Activity 对象。在这个
AndroidJavaObject上Call<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里的UnityPlayerActivityUnity 自己会带,不删会类重复;一般删unity3d整个目录下的那份。
BuildConfig是库模块构建配置,Unity 打包用不上还容易冲突,也从 jar 里抠掉。
参考文章
- 2.Unity调用Android
3. Android 如何回调 Unity 脚本?UnitySendMessage 三个参数各是什么?
题目
说明 UnityPlayer.UnitySendMessage 的三个参数含义,以及 C# 侧方法要满足哪些前置条件才能被调到。
深入解析
- 三参:Hierarchy 里物体名、public 方法名、**仅
String或null**。 - 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,再处理 AndroidX、R8 开关之类,和原生同事排障同一套流程。嵌入多 Activity 时 Export Project + AS 比编辑器里硬导出 APK 可控得多。
参考文章
- 5.Unity中嵌入Android内容
5. 安装后桌面出现两个应用图标,一般怎么改?
题目
解释「双 LAUNCHER」成因,并说明在 Unity 导出工程 + 多模块清单合并时,常在哪个模块的 AndroidManifest.xml 里动刀。
深入解析
- 每个带
MAIN+LAUNCHER的activity都会在启动器里 占一个图标。 - Unity 导出 + 插件 +
unityLibrary合并 Manifest 时,很容易出现 两个入口都带 LAUNCHER;保留 业务真正要启动的那一个,另一份 **删掉LAUNCHER的category**(或整段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/Set 与 GetStatic/SetStatic 分别对应 Java 哪类成员?为什么示例里静态变量和静态方法仍通过 currentActivity 对应的 AndroidJavaObject 调用,而不是另写 new AndroidJavaClass("包名.MainActivity")?
深入解析
- 实例:JNI 需要 对象引用;
Get/Set/Call走实例成员。 - 静态:对应 jclass 上的 static 成员;Unity 封装成
GetStatic/CallStatic系列。文档和示例里常见写法是:**拿着currentActivity的AndroidJavaObject,再调*Static***,由底层解析到 正确的 jclass。 - 也可以
new AndroidJavaClass("完整类名")只表示 类本身,再调静态成员;两种都合法,别在同一模块里混用两套句柄语义。 - JNI 引用与生命周期(加分项):
AndroidJavaObject底层持有 JNI 局部/全局引用;长生命周期脚本里Dispose()+ 置空 可避免 引用堆积(与通用 JNI「局部引用要及时释放」同一类问题)。
答题示例
实例成员用
Get/Call,静态成员用GetStatic/CallStatic,名字上已经区分了。示例从
currentActivity拿AndroidJavaObject,静态方法仍在这个对象上CallStatic,属于 Unity 官方也这么写的封装路径。想更「纯」一点可以
new AndroidJavaClass("包名.MainActivity")只表示类;团队里定一种风格即可。别忘了
AndroidJavaObject底层是 JNI,该 Dispose 就 Dispose,长驻单例尤其要注意。
参考文章
- 2.Unity调用Android
2. UnitySendMessage 传参只有字符串,项目里怎么传结构化数据?
题目
说明该 API 的参数限制,并给出一种在 Unity 与 Android 之间传递复杂数据的工程化做法(不必贴完整解析代码)。
深入解析
- 接口约定:第三参 仅
String或null,没有 多参数或二进制直传。 - 常见做法: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」在工程上的优势。
深入解析
- 原生先进 Unity:
UnityPlayerActivity往往在 单独任务栈 或与宿主 强绑定;用户按返回或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 原生插件」相关说明出发,说明 IL2CPP 与 Mono 在 .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 backends、Android native plug-ins(与当前 Editor 版本一致)
5. Release 开启 R8 / ProGuard 后,通过字符串反射调 Java 可能出什么问题?如何规避?
题目
说明 代码压缩与混淆 对 AndroidJavaClass("包名.类名")、JNI 按类名找方法 的影响;并说明 -keep 规则、自定义 proguard-user 文件 在 Unity Android 工程里的典型用法。
深入解析
- Android R8 / ProGuard 会 删除未直接引用代码 并 混淆类名/方法名;而 Unity 侧 字符串里写死的类名 在静态分析里 不可见,容易导致 **
ClassNotFoundException、NoSuchMethodError**。 - 工程上应对:对 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