1. 项目概述:为什么Unity 2021的打包流程变了?
如果你最近把Unity项目升级到了2021 LTS或更高版本,并且在为Android平台打包时,突然发现以前放在Assets/Plugins/Android/res目录下的那些资源文件(比如启动图、图标、字符串、布局文件)不生效了,甚至打包直接报错,别慌,你不是一个人。这其实是Unity 2021引入的一个重大变更,官方称之为“Gradle构建系统的现代化”。简单来说,Unity决定不再直接支持将Android原生资源(res)和清单文件(AndroidManifest.xml)直接放在Unity项目的特定目录下进行合并,而是强烈推荐甚至强制要求开发者将这些资源迁移到Android Archive(AAR)库或Android Library模块中。
这个变化对于长期使用Unity开发Android应用的团队来说,无异于一次“地震”。过去那种简单粗暴的把res文件夹一扔就完事的时代结束了。背后的核心原因,是Unity希望其Android构建流程与Google官方推荐的Gradle构建系统(特别是Android Gradle Plugin)保持更紧密的同步和兼容性。直接操作res目录的方式过于“黑盒”,容易引发资源冲突、合并错误,并且在复杂的多模块、多渠道打包场景下难以维护。迁移到AAR,本质上是将Android端的配置“模块化”和“标准化”,让Unity更像一个纯粹的“游戏引擎客户端”,而把Android平台特定的配置交给更专业的Gradle和Android Studio工具链来处理。
所以,这篇指南就是为你准备的。无论你是独立开发者还是团队中的技术负责人,都需要掌握这套新的“标准操作流程”。我们将一步步拆解如何将你的Android资源(特别是res和AndroidManifest.xml)从Unity项目中剥离,封装成一个独立的AAR库,并最终在Unity中无缝集成。这个过程涉及Android Studio项目创建、Gradle配置、资源处理、打包调试等一系列实操,我会把每个环节的“为什么”和“怎么做”都讲清楚,并附上我踩过的坑和总结的最佳实践。
2. 核心思路与方案选型:AAR vs. Android Library模块
当Unity告诉你旧方法不行了,摆在面前的主要有两条路:一是创建独立的Android Library模块(最终也会打包成AAR),二是直接创建一个可发布的AAR库。对于绝大多数Unity开发者而言,尤其是项目结构相对简单、不需要在Android端进行复杂多模块开发的情况,直接创建一个独立的Android应用模块,并将其输出为AAR供Unity使用,是最清晰、最可控的方案。这也是本指南采用的核心路径。
为什么是AAR,而不是继续寻找其他旁门左道?首先,AAR是Android官方支持的二进制分发格式,它不仅仅包含编译后的代码(classes.jar),还能完美封装资源(res)、清单文件(assets)、原生库(jniLibs)以及ProGuard规则等。这意味着你可以将启动Activity的定义、应用图标、主题样式、权限声明、第三方SDK初始化等所有Android端配置,完整地打包进一个.aar文件。Unity在构建时,会像引用其他第三方AAR(比如Firebase、AdMob的SDK)一样,将其解压并合并到最终的APK中。这种方式的优势非常明显:
- 隔离与复用:Android端的配置与Unity的C#游戏逻辑完全分离。你可以独立地在Android Studio中开发、调试和测试这些原生组件(比如一个自定义的登录界面Activity),而无需频繁打开Unity编辑器。这个AAR也可以在不同Unity项目间复用。
- 构建可靠性:使用Gradle来构建AAR,能最大程度地保证与当前Android构建工具的兼容性,避免因Unity内部Gradle版本过旧或配置方式特殊导致的诡异问题。
- 维护清晰:所有Android相关的文件(Java/Kotlin代码、res资源、Gradle依赖)都集中在一个标准的Android项目结构中,任何接手项目的Android工程师都能立刻理解并开展工作。
- 应对商店政策:一些应用商店对APK的构建方式有特定要求(例如对原生库的压缩对齐),通过标准的Gradle流程生成的AAR,能更好地满足这些合规性检查。
相比之下,尝试修改Unity的Gradle模板(mainTemplate.gradle)或使用后处理脚本强行合并资源,属于“打补丁”式的方案,初期可能见效快,但随着Unity版本升级或项目复杂度增加,维护成本会急剧上升,且容易引入难以排查的构建错误。因此,拥抱变化,采用AAR方案,是一次值得的、面向未来的投资。
3. 实战第一步:在Android Studio中创建并配置AAR模块
我们的目标是创建一个纯净的Android模块,它唯一的目的就是为Unity项目提供所需的Android资源。这里我假设你已安装Android Studio(建议使用较新稳定版,如Flamingo或Giraffe)并配置好JDK。
3.1 创建新项目与模块
打开Android Studio,选择“New Project”。在模板选择界面,不要选择“Phone and Tablet”下的“Empty Activity”。我们应该创建一个最基础的项目。更推荐的做法是选择“No Activity”,因为我们的AAR模块不需要一个默认的启动Activity(这个Activity将由Unity提供)。将项目命名为UnityAndroidResources(或其他你喜欢的名字),包名(Package name)建议设置为com.yourcompany.yourapp.unityandroid这样的格式,以清晰表明其从属关系。语言选择Java或Kotlin均可,本例以Java为例。Minimum SDK选择与你的Unity项目中Player Settings->Other Settings->Minimum API Level一致的版本,确保兼容性。
项目创建完成后,你会发现Android Studio自动生成了一个名为“app”的模块。这个模块是一个可运行的Android应用模块,但我们需要的其实是一个Android Library模块。因此,我们需要新建一个库模块。
在项目面板中,右键点击项目根目录,选择New->New Module。在弹出的窗口中,选择Android Library。将模块命名为unity-aar(名称清晰易懂),确保Package name自动继承或修改为合适的子包名,例如com.yourcompany.yourapp.unityaar。同样,Minimum SDK与之前保持一致。点击“Finish”,Android Studio会自动创建这个库模块,并在项目的settings.gradle文件中包含它。
现在,你的项目结构应该包含两个模块:app(可运行应用) 和unity-aar(我们的目标库)。app模块在这个上下文中其实可以删掉,或者保留用于简单的独立测试。为了项目整洁,我通常保留app模块但清空其内容,仅用于验证unity-aar模块的打包结果。我们的核心工作将在unity-aar模块中进行。
3.2 迁移资源与清单文件
接下来,将你Unity项目中旧有的Assets/Plugins/Android目录下的关键内容迁移过来。主要关注两部分:
res目录:在Unity项目中找到Assets/Plugins/Android/res。将其整个目录复制到Android Studio项目的unity-aar/src/main/res/目录下。如果res目录不存在就创建它。这里可能包含drawable-*(图标、图片)、layout(布局文件,虽然Unity很少用,但可能有)、values(字符串、颜色、样式等)子目录。确保结构一致。AndroidManifest.xml文件:找到Unity项目中的Assets/Plugins/Android/AndroidManifest.xml。注意:Unity项目本身在构建时也会生成一个基础的Manifest。我们迁移的这份通常是用于覆盖和添加自定义配置的,比如额外的权限、Activity定义、Meta-data(用于接入SDK)等。将这个文件复制到unity-aar/src/main/目录下。
关键操作与注意事项:
- 合并清单冲突:Unity最终打包的APK只会有一个
AndroidManifest.xml。它由三部分合并而成:Unity引擎自带的、你放在Assets/Plugins/Android下的、以及现在这个AAR模块中的。如果AAR中的Manifest与Unity生成的或旧插件中的内容有冲突(例如定义了相同android:name的Activity),会导致合并失败。因此,在迁移时,需要仔细审查AAR模块中的Manifest,通常它只应包含增量部分,即Unity基础Manifest中没有的内容。例如,如果你只是添加了网络权限和一個启动屏Activity,那么AAR的Manifest就只写这些即可,无需复制整个Unity的Manifest。 - 资源ID冲突:AAR模块中的资源(如图标)会与Unity项目中的资源,以及其他可能引用的AAR中的资源一起被编译。要确保资源名称唯一,避免出现两个
ic_launcher.png但内容不同的情况,这会导致不可预知的覆盖。建议为你的AAR资源加上前缀,例如yourcompany_ic_launcher.png。 - 清理旧文件:从Unity项目的
Assets/Plugins/Android目录下移走res和AndroidManifest.xml后,建议彻底删除或移出Unity项目,防止Unity旧的构建系统错误地引用它们,引发混淆。
3.3 配置模块的build.gradle
这是至关重要的一步,决定了AAR的输出是否兼容Unity。打开unity-aar模块下的build.gradle文件(通常是unity-aar/build.gradle.kts或unity-aar/build.gradle)。
你需要进行以下几项关键配置:
确保应用插件为
com.android.library:这表示这是一个库模块,而不是应用模块。plugins { id 'com.android.library' // 如果有其他插件,如kotlin,也在这里添加 }配置命名空间(Namespace):在
android块中,确保设置了namespace,这与你的包名一致,用于生成R.java文件时资源的最终包名。android { namespace 'com.yourcompany.yourapp.unityaar' compileSdk 34 // 建议与Unity中设置的Target API Level一致或更高 defaultConfig { minSdk 24 // 与你的Unity项目Minimum API Level一致 targetSdk 34 // 建议与Unity中设置的Target API Level一致 // versionCode 和 versionName 对于纯资源的AAR不是必须的,但可以设置 versionCode 1 versionName "1.0" } // ... 其他配置 }设置编译选项(关键!):Unity的IL2CPP脚本后端需要AAR库支持Java 8或更高版本的语言特性。同时,为了避免与Unity内部类冲突,必须启用资源名称混淆(资源收缩)。但这对于资源库来说可能过于激进,一个更稳妥的方法是确保资源ID固定。
android { // ... 同上 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } // 如果你使用Kotlin,还需要配置kotlinOptions }实际上,对于纯资源的AAR,更常见的做法是禁用代码压缩和资源收缩,因为我们不包含业务代码,且需要确保资源ID稳定。在
buildTypes中配置:android { buildTypes { release { // 禁用代码混淆 minifyEnabled false // 禁用资源收缩(Shrinking) shrinkResources false // 如果你有ProGuard规则文件,可以在这里指定,但通常不需要 // proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } debug { minifyEnabled false shrinkResources false } } }处理依赖:如果你的AAR需要引用第三方库(例如某个SDK),在
dependencies块中添加。但要注意,这些依赖最终会传递到Unity的构建中。如果Unity项目本身也通过其他方式引入了相同库的不同版本,可能会引发冲突。对于资源型AAR,应尽可能保持“干净”,不引入额外依赖。如果必须引入,请使用api声明(Gradle旧版本用compile)以确保依赖传递。
完成配置后,点击Android Studio右侧Gradle面板中unity-aar->Tasks->build->assemble或assembleRelease,即可在unity-aar/build/outputs/aar/目录下生成unity-aar-release.aar文件。这个文件就是我们最终要交给Unity的。
4. 在Unity中集成与使用生成的AAR
生成AAR文件后,回到Unity编辑器。集成过程相对简单,但有几个细节必须注意。
4.1 放置AAR文件
在Unity项目的Assets文件夹下,创建或找到Plugins/Android目录。将上一步生成的unity-aar-release.aar文件复制到这个目录下。注意:是直接放置.aar文件,而不是解压它。Unity的Gradle构建系统会自动识别和处理这个文件。
此时,你的Assets/Plugins/Android目录结构应该类似于:
Assets/ └── Plugins/ └── Android/ ├── unity-aar-release.aar └── (其他可能需要的 .jar 文件或原生库 .so 文件)之前迁移走的res目录和AndroidManifest.xml文件不应该再出现在这里。
4.2 配置Unity Player Settings
打开Edit->Project Settings->Player,切换到Android平台选项卡。确保以下设置与你的AAR模块配置兼容:
Other Settings:
- Minimum API Level: 与AAR模块中
minSdk设置一致。 - Target API Level: 建议与AAR模块中
targetSdk设置一致。 - Scripting Backend: IL2CPP(推荐)。确保AAR支持Java 8。
- Target Architectures: 根据你的需求选择(ARMv7, ARM64)。如果你的AAR包含了原生库(.so文件),需要确保架构匹配。
- Minimum API Level: 与AAR模块中
Publishing Settings:
- 检查Keystore配置是否正确,用于签名发布APK。
4.3 处理潜在的清单合并与资源冲突
这是迁移过程中最容易出错的地方。Unity在构建时,会使用它自己的“清单合并工具”来合并来自引擎、所有AAR以及你可能通过其他插件提供的AndroidManifest.xml文件。
- 调试合并错误:如果构建失败并提示清单合并错误,Unity通常会生成一个合并报告。在构建日志中仔细查找错误信息。你可以尝试在命令行使用Gradle构建并添加
--debug标志来获取更详细的信息,但更直接的方法是:在Unity构建失败后,去临时构建目录(通常位于项目目录/Temp/gradleOut/)下查看合并后的中间AndroidManifest.xml文件,分析冲突所在。 - 使用
manifestPlaceholders:如果你的AARAndroidManifest.xml中需要一些动态值(比如App ID),可以在AAR模块的build.gradle中使用manifestPlaceholders来定义占位符,然后在Unity中,可以通过自定义Gradle模板(mainTemplate.gradle)来提供这些值。但这属于进阶用法,初期建议在AAR中直接使用固定值。 - 资源ID稳定化:为了确保每次构建时,AAR中的资源ID(在R.java中)保持不变,避免因ID变动导致Unity中可能存在的通过反射调用资源的行为失效,可以在AAR模块的
build.gradle中尝试启用resourcePrefix,但这对已有项目可能不适用。最根本的办法是确保AAR模块本身代码和资源稳定,且构建环境一致。
5. 构建、测试与疑难排错实录
一切配置就绪后,就可以尝试构建了。在Unity中,File->Build Settings->Build。建议先构建一个Development版本的APK进行测试。
5.1 常见构建错误与解决方案
错误:
Manifest merger failed- 问题:清单文件合并冲突。最常见的原因是多个来源定义了相同的组件(如Activity、Service)且属性不一致。
- 排查:查看完整错误日志,找到冲突的节点。例如,可能是你的AAR和另一个第三方SDK的AAR都定义了
android:name="com.unity3d.player.UnityPlayerActivity"并试图修改其属性。 - 解决:
- 使用
tools:replace或tools:ignore:在AAR的AndroidManifest.xml中,对冲突的属性使用这些指令。例如,如果你想覆盖android:theme,可以这样写:<activity android:name=".CustomActivity" android:theme="@style/MyTheme" tools:replace="android:theme" />。这告诉合并工具用你的值替换掉其他来源的值。 - 检查依赖传递:可能是某个传递依赖的AAR带来了冲突的清单。尝试在AAR模块的
build.gradle中,对可疑的依赖使用exclude指令,或者查看其清单内容。 - 简化AAR清单:确保你的AAR清单只包含绝对必要的内容,移除任何可能与Unity基础清单或其他通用库重复的定义。
- 使用
错误:
Resource linking failed或找不到资源符号- 问题:构建时无法解析资源ID,例如
error: resource android:attr/lStar not found。 - 排查:这通常是因为AAR模块的
compileSdkVersion或所依赖的库版本与Unity内部使用的Android SDK版本不兼容。lStar是较高版本SDK中引入的属性。 - 解决:
- 统一编译版本:将AAR模块的
compileSdkVersion降低到与Unity编辑器内置的Android SDK版本相匹配。你可以在Unity的Preferences->External Tools下查看Android SDK路径。或者,升级Unity的Android SDK到更新版本。 - 使用支持库:如果必须使用高版本API的特性,确保在AAR中正确引用了对应的AndroidX支持库(如
appcompat),并且版本与compileSdkVersion匹配。
- 统一编译版本:将AAR模块的
- 问题:构建时无法解析资源ID,例如
错误:
Duplicate class found- 问题:类重复。这通常是因为多个AAR或JAR包包含了相同的类(例如不同版本的Gson、OkHttp)。
- 排查:错误信息会指出具体的重复类名。
- 解决:
- 排除传递依赖:在Unity项目的
mainTemplate.gradle(如果你使用了它)或AAR模块的build.gradle中,使用exclude指令。例如:implementation('com.some.library:1.0') { exclude group: 'com.google.code.gson', module: 'gson' }。 - 强制统一版本:在
mainTemplate.gradle的根节点使用configurations.all块来强制所有依赖使用同一版本。但此法需谨慎,可能引发其他兼容性问题。 - 使用
packagingOptions:在mainTemplate.gradle的android块内添加packagingOptions { pickFirst 'path/to/duplicate/class' }来指定使用第一个找到的类文件。这是最后的手段。
- 排除传递依赖:在Unity项目的
AAR中的资源在运行时找不到
- 问题:APK能安装,但应用运行时崩溃,日志显示
android.content.res.Resources$NotFoundException。 - 排查:首先确认APK中是否包含该资源。可以使用
adb shell和aapt工具检查APK内容。其次,检查访问资源的代码是否正确使用了AAR模块的包名来生成资源ID。在Unity中,如果通过C#调用原生插件(AndroidJavaClass)来访问资源,需要使用完整的资源ID路径,例如com.yourcompany.yourapp.unityaar.R.drawable.your_icon。 - 解决:确保在Java/Kotlin代码中通过正确的
R类引用资源。如果是从Unity C#端调用,确保传递的资源ID是正确的整型值,这可能需要通过反射在运行时从AAR的R类中获取。
- 问题:APK能安装,但应用运行时崩溃,日志显示
5.2 调试技巧与最佳实践
- 分阶段构建:不要试图一次性迁移所有资源。先从最简单的开始,比如只迁移一个图标和一段字符串,确保AAR能正确集成并打包。成功后再逐步加入更复杂的资源、Activity和权限。
- 善用Unity的构建日志:构建失败时,仔细阅读Unity Console中的错误日志,通常会有指向性的错误信息。将日志复制到文本编辑器中搜索关键错误词。
- 对比分析APK:构建一个使用旧方式(直接放res目录)能成功的APK,再构建一个使用新AAR方式的APK。使用像
APK Analyzer(Android Studio内置)或jadx-gui这样的工具,对比两个APK的AndroidManifest.xml和res目录结构,能直观地发现差异。 - 维护独立的测试工程:创建一个极其简单的Unity测试工程,专门用于集成和测试这个AAR。这能避免在主项目复杂环境中干扰判断。
- 版本控制AAR项目:将Android Studio的AAR模块工程纳入版本控制(如Git)。这样,任何对Android端资源的修改都有清晰的历史记录,并且可以方便地在不同分支上为不同的Unity项目版本提供对应的AAR。
迁移到AAR方案初期确实会增加一些复杂度,但一旦流程跑通,它将为你的项目带来更健壮的构建系统、更好的团队协作界面以及更顺畅的持续集成/持续部署(CI/CD)体验。这步棋,值得走。