Unity 2021 Android打包res冲突解决方案:创建统一Android Library

发布时间:2026/7/23 6:37:49

Unity 2021 Android打包res冲突解决方案:创建统一Android Library 1. 项目概述一次典型的Unity版本升级“阵痛”如果你正在从Unity 2019或2020版本升级到Unity 2021并且你的项目需要发布到Android平台那么你大概率会遇到一个让人头疼的打包错误。这个错误信息通常指向Android的res文件夹提示资源文件冲突或重复导致APK构建失败。这不仅仅是Unity 2021的“特色”更是Unity引擎在Android构建管线、Gradle版本以及Android SDK/NDK工具链整合上的一次重大调整所带来的连锁反应。我最近在将一个中型商业项目从Unity 2019 LTS迁移到Unity 2021 LTS时就深陷此坑。表面上看错误信息指向一个具体的文件路径但背后牵扯到的是Unity对Android LibraryAAR依赖管理方式的改变、Gradle构建脚本的升级以及我们开发者自身项目结构的历史遗留问题。简单来说Unity 2021默认使用了更新的Gradle和Android Gradle PluginAGP版本这些新版本对Android项目的资源合并规则更加严格。过去在旧版本Unity或旧版Gradle下可能被忽略或“蒙混过关”的资源冲突现在会被严格检查并报错。最常见的场景就是你项目中可能通过Plugins/Android目录引入了一个或多个第三方AAR库例如广告SDK、支付SDK、社交分享SDK这些AAR库内部都包含了自己的res资源文件如图标、布局、字符串。当Unity打包时它会尝试将所有依赖库的res文件夹合并到主APK的res中。如果不同库之间或者库与你的主项目之间存在同名的资源文件比如都叫ic_launcher.png的图标或者都定义了app_name的字符串新版本的构建系统就会果断报错而不是像以前那样可能随机选择一个覆盖另一个。解决这个问题的核心思路不再是简单地删除某个文件虽然有时临时生效而是系统地理解Unity 2021的Android构建流程并学会如何正确地创建、配置和管理你自己的Android Library模块。通过创建一个主Android Library来统一管理所有第三方依赖和自定义Android代码你可以精确控制资源的合并过程从根本上避免冲突。接下来我将手把手带你拆解这个报错并附上从零创建一个兼容Unity 2021的Android Library的完整流程以及如何将其无缝集成到你的Unity项目中。无论你是Unity新手还是有一定经验的开发者这套方法都能帮你建立起清晰的Android构建知识体系从容应对未来的版本升级。2. 核心问题拆解为什么res文件夹会冲突要解决问题必须先理解问题是如何产生的。Unity在构建Android应用时本质上是在幕后启动了一个标准的Gradle构建过程。你的Unity项目会被转换成一个Android项目模板而你放在Assets/Plugins/Android目录下的所有文件包括AndroidManifest.xml,res,libs,*.aar等都会被复制到这个模板的对应位置参与构建。2.1 资源合并冲突的根源在Android开发中res目录下的资源如图片、布局、字符串、颜色等都通过其文件名和所在的限定符目录如drawable-hdpi,values-zh来唯一标识。构建系统Gradle AAPT2的任务之一就是将所有模块包括主app模块和所有依赖库模块的资源收集起来合并到一个统一的资源表中。冲突发生的典型场景多个AAR库包含同名资源这是最常见的情况。例如你同时接入了AdMob和Unity Ads的SDK它们可能都提供了一个名为admob_app_icon.png或ic_notification.png的文件且都放在drawable目录下。在旧版本中构建系统可能只会发出警告或者后引入的库资源覆盖先引入的。但在Unity 2021搭配的新版AGP下这会直接导致构建失败。你的项目与AAR库包含同名资源你在自己的Plugins/Android/res下放置了自定义图标或字符串恰好与某个第三方库内的资源重名。AndroidManifest.xml中的属性引用冲突android:icon,android:label等属性引用了drawable/icon或string/app_name。如果多个库或主项目定义了同名的icon或app_name也会引发冲突。Unity 2021版本将内置的Gradle版本升级到了6.1.1以上AGP版本也同步更新。新版本为了构建的确定性和可重现性加强了对资源冲突的检查。它要求开发者必须明确处理这些冲突而不是依赖构建系统的默认行为。2.2 Unity构建Android项目的流程简析理解以下流程能帮你定位问题发生在哪个环节导出Gradle项目当你在Unity Editor中选择Build Settings-Build或Export Project时Unity会生成一个完整的Android Gradle项目目录。整合Plugins/Android该目录下的所有内容会被“扁平化”地拷贝到导出的Gradle项目的app模块主模块对应目录中。注意所有AAR文件都会被解压其内容classes.jar, res, AndroidManifest.xml等会与app模块的原有内容直接混合。执行Gradle构建Unity调用你指定的Gradle或使用它自带的执行assembleRelease或assembleDebug任务。这时Gradle开始解析所有依赖合并资源编译代码最终生成APK。报错点资源合并mergeReleaseResources或mergeDebugResources任务是构建早期的一个步骤。如果在此阶段检测到冲突构建就会停止并在Unity Console或Gradle日志中输出详细的错误信息。错误信息示例 A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade Android resource linking failed ...\build\intermediates\merged_manifests\release\AndroidManifest.xml:86: error: resource string/app_name (aka com.yourcompany.yourapp:string/app_name) duplicated.或者更直接地指向某个具体的res文件路径。注意很多开发者遇到错误后的第一反应是去错误日志里提到的临时构建目录下删除冲突文件。这可能在单次构建中侥幸成功但下次构建时文件又会被重新生成问题依旧。这是一种“掩耳盗铃”的做法无法根治问题。3. 治本方案创建统一的Android Library最优雅、最彻底的解决方案不是去“打补丁”而是重新组织你的Android部分代码和依赖。我们将创建一个独立的Android Library模块最终打包成AAR文件在这个模块中统一管理所有第三方AAR依赖、自定义res资源、AndroidManifest.xml以及Java/Kotlin代码。然后在Unity项目中只引用这一个“主AAR”。这样做的好处是隔离与封装所有Android端的逻辑和依赖被封装在一个模块内与Unity的C#代码解耦。解决资源冲突在Android Library模块内部你可以使用Gradle提供的标准工具如resourcePrefix来避免资源命名冲突或者手动处理第三方库之间的冲突。构建控制权你可以使用Android Studio和Gradle脚本精细控制这个库的构建过程例如启用混淆、指定依赖版本等。维护性高当需要更新某个SDK或添加新的Android功能时你只需要修改这个Library项目然后生成新的AAR替换到Unity中即可。下面我们开始手把手创建这个Android Library。3.1 环境准备与项目创建所需工具Android Studio建议使用较新版本如Arctic Fox 2020.3.1或更高以确保对最新AGP的良好支持。中文设置不是必须的但如果你需要可以在File - Settings - Appearance Behavior - Appearance中勾选Use custom font并选择中文字体或安装中文语言包插件。Java JDKUnity 2021通常需要JDK 8或JDK 11。建议安装JDK 11并确保JAVA_HOME环境变量指向它。你可以在Android Studio的File - Project Structure - SDK Location中查看和设置JDK路径。创建新项目打开Android Studio选择New Project。在模板选择界面不要选择Phone and Tablet下的Empty Activity。请选择Empty Views Activity。配置项目Name: 给你的库起个名例如UnityAndroidPlugin。Package name: 使用你的应用程序包名后面加上.plugin后缀以示区分例如com.yourcompany.yourapp.plugin。Save location: 选择一个你喜欢的目录。Language: 选择Java如果你熟悉Kotlin也可以选但本文以Java为例Unity传统插件也多以Java编写。Minimum SDK: 选择API 21: Android 5.0 (Lollipop)或与你Unity项目中Player Settings里设置的最低API级别一致。点击FinishAndroid Studio会创建并打开项目。3.2 将App模块转换为Library模块默认创建的是一个可运行的App模块。我们需要将其改为Library模块。打开项目根目录下的settings.gradle文件。你会看到类似的内容dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } } rootProject.name UnityAndroidPlugin include :app这表示当前项目包含一个名为app的模块。接下来修改app模块的构建配置。打开app目录下的build.gradle文件通常是app/build.gradle.kts或app/build.gradle。找到plugins部分将plugins { id com.android.application // 这是应用模块 id org.jetbrains.kotlin.android }修改为plugins { id com.android.library // 改为库模块 id org.jetbrains.kotlin.android }如果你用的是Groovy DSLbuild.gradle且没有plugins块则找到apply plugin: com.android.application并改为apply plugin: com.android.library。删除android块中的applicationId这一行。Library模块不需要应用ID。可选但推荐为了减少最终AAR的大小并避免不必要的冲突可以删掉dependencies块中非必需的依赖特别是implementation androidx.core:core-ktx:...和implementation androidx.appcompat:appcompat:...。但是如果你的插件代码需要用到这些库例如使用了AppCompat的控件则必须保留。一个纯粹的、只做JNI桥接或简单系统调用的插件可能不需要它们。同步Gradle点击Android Studio右上角的Sync Now或选择File - Sync Project with Gradle Files。现在app模块已经变成了一个Android Library模块。你可以运行Build - Make Module ‘app’来测试是否能成功构建。构建产物AAR文件会生成在app/build/outputs/aar/目录下通常有debug和release两个版本。3.3 配置Library以兼容Unity并避免资源冲突这是最关键的一步确保你的Library能被Unity正确引用且自身无资源冲突。1. 修改AndroidManifest.xml打开app/src/main/AndroidManifest.xml。作为Library它的Manifest最终会被合并到主App的Manifest中。你需要做以下调整移除application标签的所有属性如android:theme,android:label,android:icon等。这些属性应该由主App即你的Unity游戏来定义否则会引起合并冲突。通常一个Library的Manifest只包含它需要的权限uses-permission、组件声明activity,service,receiver以及uses-feature等。确保这些声明的android:name是唯一的。一个极简的Library Manifest可能长这样?xml version1.0 encodingutf-8? manifest xmlns:androidhttp://schemas.android.com/apk/res/android packagecom.yourcompany.yourapp.plugin !-- 声明插件需要的权限 -- uses-permission android:nameandroid.permission.INTERNET / !-- 声明插件提供的Activity -- application activity android:name.MyPluginActivity android:exportedfalse / !-- exported属性根据实际情况设置 -- /application /manifest2. 为资源添加前缀强烈推荐为了避免你的Library资源与主App或其他库资源重名Gradle提供了一个简单有效的方法为所有资源自动添加前缀。 在app/build.gradle文件的android块内添加android { // ... 其他配置 resourcePrefix uap_ // 你可以自定义前缀如 uap_ (Unity Android Plugin) }添加此后你在res目录下创建的所有资源布局文件除外其名称在编译时都会被自动加上uap_前缀。例如你定义了一个drawable/icon在最终的R文件中会变成drawable/uap_icon。这从根本上杜绝了与外部资源的命名冲突。注意这个前缀只对新创建的资源文件生效对于已经存在的资源你需要手动重命名。3. 处理第三方AAR依赖这是解决原始res冲突问题的核心。将所有你需要在Unity中使用的第三方SDK如广告、分析、支付等都作为依赖添加到这个Library模块中而不是直接放到Unity的Plugins/Android文件夹里。在app/build.gradle的dependencies块中添加dependencies { // 示例添加一些常见的SDK依赖 implementation com.google.android.gms:play-services-ads:21.5.0 // AdMob // implementation files(libs/some-local-sdk.aar) // 如果是本地的AAR文件 // 确保使用较新且兼容的版本旧版本SDK可能本身存在资源冲突或与新AGP不兼容 }关键点当多个库在同一个Gradle模块中被声明为依赖时Gradle会尝试解决它们之间的传递性依赖和资源冲突。如果两个库比如A和B都依赖了不同版本的同一个库CGradle通常会选择更高的版本。对于资源冲突Gradle的行为可以通过更精细的规则控制但首先确保所有SDK都是最新稳定版能减少很多问题。4. 编写你的插件Java代码在app/src/main/java/com.yourcompany.yourapp.plugin/目录下创建你的Java类。例如创建一个UnityBridge.java用于和Unity的C#端进行通信通过UnityPlayer.UnitySendMessage。package com.yourcompany.yourapp.plugin; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import com.unity3d.player.UnityPlayer; // 注意这个类需要额外处理见下文 public class UnityBridge { private static Activity getUnityActivity() { // 如何获取Unity的Activity是一个常见问题 // 一种常见做法是通过一个初始化方法由C#端将Activity实例传过来 // 或者通过反射调用UnityPlayer.currentActivity // 这里假设我们通过C#设置 return UnityPluginActivity.instance; } public static void showNativeView() { Activity activity getUnityActivity(); if (activity ! null) { Intent intent new Intent(activity, MyPluginActivity.class); activity.startActivity(intent); } } // 供Android调用的方法用于回调到Unity public static void sendMessageToUnity(String gameObject, String method, String message) { UnityPlayer.UnitySendMessage(gameObject, method, message); } }关于UnityPlayer类这个类并不在标准的Android SDK中它来自Unity的运行时库。你有两种方式处理方式一推荐保持Library纯净不在Library中直接引用UnityPlayer。所有需要与Unity交互的接口通过一个独立的“接口层”来定义具体的UnityPlayer.UnitySendMessage调用由Unity项目中的Java代码放在Plugins/Android来实现。Library只负责原生功能通过回调接口通知调用者。方式二将Unity安装目录下的classes.jar位于{Unity安装路径}/Editor/Data/PlaybackEngines/AndroidPlayer/Variations/mono或il2cpp/Development/Classes/拷贝到Library模块的libs目录并将其添加为compileOnly依赖compileOnly files(libs/classes.jar)。这样Library代码可以编译但最终打包AAR时不会包含这个jar需要Unity在运行时提供。3.4 构建与生成AAR在Android Studio左侧的Build Variants工具窗格中选择release构建变体。点击菜单栏的Build - Make Module ‘app’。构建成功后在app/build/outputs/aar/目录下找到app-release.aar文件。将其重命名为一个更有意义的名字例如unity-android-plugin-release.aar。4. 在Unity中集成自定义Android Library现在我们有了一个“干净”的、统一管理了所有第三方依赖的AAR文件。接下来就是将其集成到Unity项目中。准备Unity项目在Unity项目的Assets目录下创建标准的插件文件夹结构Assets/Plugins/Android。放置AAR文件将上一步生成的unity-android-plugin-release.aar文件拷贝到Assets/Plugins/Android目录下。处理主AndroidManifest.xml在Assets/Plugins/Android目录下创建或放置你的应用主AndroidManifest.xml文件。这个文件会与你Library中的Manifest合并。你需要在这里声明应用级别的属性如android:icon,android:label,android:theme以及应用所需的权限如果Library中已经声明了这里可以不用重复声明但声明了也没关系合并规则会处理。?xml version1.0 encodingutf-8? manifest xmlns:androidhttp://schemas.android.com/apk/res/android packagecom.yourcompany.yourapp application android:icondrawable/app_icon !-- 确保此资源存在于你的Unity项目或主资源中 -- android:labelstring/app_name android:themestyle/UnityThemeSelector !-- Unity Player Activity -- activity android:namecom.unity3d.player.UnityPlayerActivity android:exportedtrue !-- ... -- /activity !-- 你的Library中声明的Activity会自动合并进来 -- /application !-- 权限也会自动合并 -- /manifest移除旧的第三方SDK文件至关重要的一步将原先直接放在Assets/Plugins/Android下的所有第三方AAR、JAR以及它们可能附带的res文件夹、AndroidManifest.xml片段等全部移除或备份到别处。现在所有这些依赖都已经封装在你新的主AAR文件里了。只保留你自己的主AAR、主AndroidManifest.xml以及任何必须的、不与AAR冲突的自定义文件例如proguard-user.txt。配置Player Settings打开Unity的Project Settings - Player切换到Android平台。Other Settings:Package Name: 设置为你的应用包名com.yourcompany.yourapp注意与Library的包名com.yourcompany.yourapp.plugin区分开。Minimum API Level: 与你在Android Library中设置的一致。Publishing Settings:Keystore: 配置你的发布密钥。最重要的一步Build System选择Gradle。这是必须的因为我们要使用Gradle来解析AAR依赖。在Build System下方勾选Export Project。这会让Unity导出完整的Gradle项目而不是直接构建APK方便我们进行更深入的调试如果需要。编写C#桥接代码在Unity的Assets/Scripts或类似目录下创建一个C#脚本用于调用Android插件。using UnityEngine; public class AndroidPluginManager : MonoBehaviour { private static AndroidJavaClass _pluginClass; private static AndroidJavaObject _pluginInstance; private const string PluginPackageName com.yourcompany.yourapp.plugin.UnityBridge; void Awake() { // 初始化获取Unity的Activity并传递给插件如果插件需要 // 具体方法取决于你在Library中如何设计初始化接口 // 例如如果Library需要一个Context初始化 // using (AndroidJavaClass unityPlayer new AndroidJavaClass(com.unity3d.player.UnityPlayer)) // using (AndroidJavaObject currentActivity unityPlayer.GetStaticAndroidJavaObject(currentActivity)) // { // _pluginClass new AndroidJavaClass(PluginPackageName); // _pluginClass.CallStatic(init, currentActivity); // } } public void CallNativeFunction() { // 调用Library中的静态方法 using (AndroidJavaClass pluginClass new AndroidJavaClass(PluginPackageName)) { pluginClass.CallStatic(showNativeView); } } // 供Java端回调的方法必须为public public void OnNativeCallback(string message) { Debug.Log($Received callback from Android: {message}); // 处理回调逻辑 } }5. 构建、测试与疑难排查完成以上步骤后你就可以尝试构建了。首次构建在Unity中File - Build Settings选择Android平台点击Export如果你勾选了Export Project或Build And Run。首次构建可能会较慢因为Gradle需要下载所有依赖。解读构建错误如果构建失败请仔细阅读Unity Console中的错误日志。Gradle同步失败检查Assets/Plugins/Android目录下是否还有残留的旧版SDK文件特别是那些可能带有自己res文件夹的。确保你的主AAR包含了所有必要依赖。资源冲突依然存在这可能意味着在你的主AAR内部多个第三方库之间仍有冲突。这时你需要回到Android Studio的Library项目中通过分析./gradlew :app:dependencies命令的输出检查依赖树排除或升级有冲突的库版本。也可以在Library的build.gradle中使用exclude规则来排除特定的资源文件。android { packagingOptions { exclude res/drawable/conflicting_icon.png // 排除特定冲突文件 // 或者合并资源时选择第一个 pickFirst res/values/strings.xml } }类找不到ClassNotFoundException检查你的C#代码中调用的Java类名、方法名是否完全正确包括包名。确保Library已正确打包到AAR中并且没有使用compileOnly依赖了Unity特有的类如UnityPlayer而导致运行时缺失。调试技巧使用adb logcat在真机或模拟器上运行应用通过adb logcat -s Unity或adb logcat *:E来查看运行时日志捕捉崩溃信息。检查APK内容使用apkanalyzerAndroid SDK自带或APK Editor Studio等工具打开构建成功的APK查看res目录下的资源确认没有重复文件以及你的Library资源是否被正确添加带有前缀uap_。导出Gradle项目手动构建在Unity中勾选Export Project导出后使用Android Studio打开这个项目。你可以在Android Studio中直接进行Build或Debug这能获得更详细的Gradle和Android构建日志对于排查复杂的依赖问题非常有帮助。6. 进阶优化与扩展思考当你成功解决了res冲突并建立起自定义Android Library的工作流后可以考虑以下优化方向多构建变体Flavors在Android Library中配置productFlavors可以为不同环境开发、测试、生产编译不同版本的AAR例如集成不同的SDK密钥或配置。代码混淆ProGuard/R8在Library的build.gradle中启用minifyEnabled true并配置对应的ProGuard规则proguard-rules.pro可以保护你的Java代码并减小AAR体积。记得将需要被Unity C#端反射调用的类和方法规则设为-keep。资源精简定期检查你的Library中是否包含了不必要的资源文件例如第三方SDK带来的多种语言图片但你的应用只支持中文。可以通过Gradle的resConfigs或手动删除res目录下不需要的资源限定符文件夹来精简。持续集成CI将Android Library的构建脚本gradlew assembleRelease集成到你的CI/CD流程中确保每次Unity项目构建前都能使用最新、最稳定的AAR。这次从Unity 2021升级引发的res打包报错虽然过程曲折但迫使我去深入理解了Unity Android构建的黑盒掌握了用标准化Android开发流程来管理Unity插件依赖的方法。这套方法不仅解决了眼前的问题更为项目后续接入任何Android原生功能打下了坚实的基础。记住面对构建错误最忌讳的是盲目搜索错误信息并尝试各种“偏方”。静下心来理解工具链的原理用系统性的方法去重构才是工程师的解决之道。

相关新闻