
在移动游戏发行中,我们常需要将同一款游戏发布到华为、小米、OPPO、vivo、TapTap等多个应用商店。每个渠道都要求接入其专属 SDK(登录、支付、防沉迷等),同时可能需要修改包名、应用图标、闪屏、渠道号等配置。如果每次手动处理,不仅繁琐而且极易出错。本文将介绍如何利用 Android Gradle 的 Product Flavors 机制,在 Unity 项目中实现一套代码生成多个渠道包,做到渠道隔离、自动化打包。
com.yourcompany.game.huawei)。huawei, xiaomi, oppo),每个 flavor 可以独立设置包名、版本号、依赖库、资源文件等。huaweiRelease。main 目录合并,实现渠道专属逻辑。我们采用 Unity 导出 Android 工程 + 在 Android 工程中配置多渠道 的混合方案:
AndroidJavaObject 调用 Android 端的 ChannelManager 类,不关心具体渠道实现。IChannel,声明登录、支付、防沉迷等方法。HuaweiChannel, XiaomiChannel)。ChannelManager 中通过反射加载当前渠道的 ChannelFactory,创建渠道实例。app/build.gradle 中定义 productFlavors,为每个渠道设置完整的包名(applicationId)、混淆文件、依赖库。sourceSets 为每个渠道指定专属源码目录(如 src/huawei/java)。manifestPlaceholders 动态替换 AndroidManifest.xml 中的渠道号。./gradlew assembleXxxRelease 即可生成对应渠道的 APK。创建 ChannelManager.cs,负责与 Android 通信:
using UnityEngine;public class ChannelManager : MonoBehaviour{ private static AndroidJavaObject m_ChannelImpl; void Start() => Init(); void Init() {#if UNITY_ANDROID && !UNITY_EDITOR try { AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); AndroidJavaObject activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"); AndroidJavaClass channelClass = new AndroidJavaClass("com.yourcompany.channel.ChannelManager"); m_ChannelImpl = channelClass.CallStatic<AndroidJavaObject>("getInstance", activity); } catch (System.Exception e) { Debug.LogError($"Channel init failed: {e.Message}"); }#endif } public void Login() => m_ChannelImpl?.Call("login"); public void Pay(string productId) => m_ChannelImpl?.Call("pay", productId); public void Logout() => m_ChannelImpl?.Call("logout"); public string GetChannel() => m_ChannelImpl?.Call<string>("getChannelName");}将此脚本挂载到启动场景的 GameObject 上即可。
IChannel// IChannel.javapackage com.yourcompany.channel;import android.app.Activity;public interface IChannel { void init(Activity activity); void login(); void pay(String productId); void logout(); String getChannelName();}ChannelManager(入口类)// ChannelManager.javapackage com.yourcompany.channel;import android.app.Activity;import android.util.Log;public class ChannelManager { private static ChannelManager sInstance; private IChannel mChannelImpl; private ChannelManager(Activity activity) { try { // 通过反射加载当前 flavor 下的 ChannelFactory Class<?> factory = Class.forName("com.yourcompany.channel.ChannelFactory"); mChannelImpl = (IChannel) factory.getMethod("create", Activity.class).invoke(null, activity); } catch (Exception e) { Log.e("ChannelManager", "Failed to create channel", e); } } public static synchronized ChannelManager getInstance(Activity activity) {if (sInstance == null) sInstance = new ChannelManager(activity);return sInstance; } public void login() { if (mChannelImpl != null) mChannelImpl.login(); } public void pay(String pid) { if (mChannelImpl != null) mChannelImpl.pay(pid); } public void logout() { if (mChannelImpl != null) mChannelImpl.logout(); } public String getChannelName() { return mChannelImpl != null ? mChannelImpl.getChannelName() : "unknown"; }}目录结构:
app/src/├── main/java/.../IChannel.java, ChannelManager.java├── huawei/java/.../ChannelFactory.java, HuaweiChannel.java├── xiaomi/java/.../ChannelFactory.java, XiaomiChannel.java...华为工厂类:
// src/huawei/java/.../ChannelFactory.javapackage com.yourcompany.channel;import android.app.Activity;public class ChannelFactory { public static IChannel create(Activity activity) {return new HuaweiChannel(activity); }}华为实现类:
// src/huawei/java/.../HuaweiChannel.javapackage com.yourcompany.channel;import android.app.Activity;public class HuaweiChannel implements IChannel { private Activity mActivity; public HuaweiChannel(Activity activity) { mActivity = activity; init(activity); } @Override public void init(Activity a) { /* 初始化 HMS SDK */ } @Override public void login() { /* 调用华为登录 */ } @Override public void pay(String pid) { /* 调用华为支付 */ } @Override public void logout() { /* 华为登出 */ } @Override public String getChannelName() { return"huawei"; }}其他渠道依此类推,只需替换 SDK 调用即可。
在 Unity 的 Player Settings → Android → Publishing Settings 中勾选:
Assets/Plugins/Android/AndroidManifest.xml)Assets/Plugins/Android/mainTemplate.gradle)注意:
productFlavors既可以写在mainTemplate.gradle(对应 app 模块),也可以写在launcherTemplate.gradle(如果项目中有该模板)。两者选其一即可,效果相同。
mainTemplate.gradle在 android 块中添加 productFlavors 和 sourceSets:
android { defaultConfig { // 基础包名,可作为默认值(可选) applicationId "com.yourcompany.gamename" // ... } flavorDimensions "channel" productFlavors { huawei { dimension "channel" applicationId "com.yourcompany.gamename.huawei" manifestPlaceholders = [CHANNEL_NAME: "huawei"] proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules-huawei.pro' } xiaomi { dimension "channel" applicationId "com.yourcompany.gamename.xiaomi" manifestPlaceholders = [CHANNEL_NAME: "xiaomi"] proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules-xiaomi.pro' } // 其他渠道... } sourceSets { main { java.srcDirs = ['src/main/java'] res.srcDirs = ['src/main/res'] } huawei { java.srcDirs = ['src/huawei/java'] } xiaomi { java.srcDirs = ['src/xiaomi/java'] } // ... }}dependencies { implementation project(':unityLibrary') huaweiImplementation 'com.huawei.hms:hwid:6.4.0.300' xiaomiImplementation fileTree(dir: 'libs/xiaomi', include: ['*.jar', '*.aar']) // ...}说明:
applicationId 可以直接为每个渠道设置独立的完整包名,更加直观和灵活。defaultConfig 中的 applicationId 作为基础值,当某个 flavor 未显式指定 applicationId 时会继承该值(但建议每个渠道都显式声明,避免混淆)。defaultConfig.applicationId,如果要保留,请确保 flavor 中的 applicationId 覆盖它。AndroidManifest.xml 占位符在 Assets/Plugins/Android/AndroidManifest.xml 的 <application> 标签中添加:
<meta-data android:name="CHANNEL_NAME" android:value="${CHANNEL_NAME}"/>Gradle 会在构建时自动替换 ${CHANNEL_NAME} 为对应渠道的占位符值。
为每个渠道创建独立的 ProGuard 文件,并在 productFlavors 中引用:
huawei { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules-huawei.pro'}如果某个渠道需要额外的权限或 Activity,可以在 src/渠道名/AndroidManifest.xml 中声明,构建时会自动与主清单合并。
华为的 agcp 插件默认会对所有渠道生效,并在非华为渠道构建时因为找不到包名而报错。可以通过判断任务名是否包含 "huawei" 来条件应用插件:
if (getGradle().getStartParameter().getTaskRequests().toString().toLowerCase().contains("huawei")) { apply plugin: 'com.huawei.agconnect'}导出 Android 工程后,在项目根目录执行以下命令即可生成对应渠道的 APK:
# 生成华为渠道 Release 包./gradlew assembleHuaweiRelease# 生成小米渠道 Release 包./gradlew assembleXiaomiRelease# 一次性生成所有渠道 Release 包./gradlew assembleRelease生成的 APK 位于 app/build/outputs/apk/渠道名/release/ 目录下。可以将这些命令集成到 Jenkins 或其他 CI 系统中,实现自动化构建。
通过 Gradle Product Flavors,我们可以在 Unity 项目中实现一套代码定制化生成数百个渠道包,而不需要在 Unity 工程中编写任何平台相关逻辑。这套方案具备以下优点:
build.gradle 添加一个 flavor,并创建对应的源码目录和实现类。希望本文能帮助 Unity 开发者高效解决多渠道打包问题。如果有任何疑问或更好的建议,欢迎交流讨论!