当前位置:首页>安卓APP>Unity · 每日知识点:安卓多渠道一键打包

Unity · 每日知识点:安卓多渠道一键打包

  • 2026-09-03 17:54:43
Unity · 每日知识点:安卓多渠道一键打包

在移动游戏发行中,我们常需要将同一款游戏发布到华为、小米、OPPO、vivo、TapTap等多个应用商店。每个渠道都要求接入其专属 SDK(登录、支付、防沉迷等),同时可能需要修改包名、应用图标、闪屏、渠道号等配置。如果每次手动处理,不仅繁琐而且极易出错。本文将介绍如何利用 Android Gradle 的 Product Flavors 机制,在 Unity 项目中实现一套代码生成多个渠道包,做到渠道隔离、自动化打包。

1. 为什么需要多渠道打包?

  • 渠道 SDK 差异:不同渠道要求接入各自的登录、支付、广告等 SDK,这些 SDK 不能在同一个 APK 中共存(类冲突、初始化冲突)。
  • 包名差异:部分渠道要求独立包名(如华为渠道包名可能为 com.yourcompany.game.huawei)。
  • 资源差异化:不同渠道可能需要不同的应用图标、启动画面、渠道号等。
  • 自动化构建:技术团队需要能通过命令行一键生成所有渠道包,便于集成到 CI/CD 流程。

2. 核心概念:Product Flavors 与 SourceSet

  • Product Flavors:Gradle 提供的产品变种机制。我们可以定义多个 flavor(如 huawei, xiaomi, oppo),每个 flavor 可以独立设置包名、版本号、依赖库、资源文件等。
  • Build Variant:Flavor 与 Build Type(Debug/Release)的组合,例如 huaweiRelease。
  • SourceSet:每个 flavor 可以拥有自己的源码和资源目录,构建时该目录下的文件会与 main 目录合并,实现渠道专属逻辑。

3. 整体方案设计

我们采用 Unity 导出 Android 工程 + 在 Android 工程中配置多渠道 的混合方案:

  1. Unity 层:只编写通用 C# 脚本,通过 AndroidJavaObject 调用 Android 端的 ChannelManager 类,不关心具体渠道实现。
  2. Android 层:
    • 定义公共接口 IChannel,声明登录、支付、防沉迷等方法。
    • 为每个渠道提供独立实现(如 HuaweiChannel, XiaomiChannel)。
    • 使用工厂模式,在 ChannelManager 中通过反射加载当前渠道的 ChannelFactory,创建渠道实例。
  3. Gradle 配置:
    • 在 app/build.gradle 中定义 productFlavors,为每个渠道设置完整的包名(applicationId)、混淆文件、依赖库。
    • 使用 sourceSets 为每个渠道指定专属源码目录(如 src/huawei/java)。
    • 通过 manifestPlaceholders 动态替换 AndroidManifest.xml 中的渠道号。
  4. 构建流程:
    • Unity 开发者导出 Android 工程(勾选 Export Project),得到“母包”。
    • 在命令行中执行 ./gradlew assembleXxxRelease 即可生成对应渠道的 APK。

4. Unity 端实现(C#)

创建 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 上即可。

5. Android 端实现(Java)

5.1 定义公共接口 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();}

5.2 编写 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"; }}

5.3 为每个渠道提供工厂类和实现类(以华为为例)

目录结构:

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 调用即可。

6. Gradle 配置详解(关键)

6.1 启用自定义 Gradle 模板

在 Unity 的 Player Settings → Android → Publishing Settings 中勾选:

  • Custom Main Manifest(生成 Assets/Plugins/Android/AndroidManifest.xml)
  • Custom Main Gradle Template(生成 Assets/Plugins/Android/mainTemplate.gradle)

注意:productFlavors 既可以写在 mainTemplate.gradle(对应 app 模块),也可以写在 launcherTemplate.gradle(如果项目中有该模板)。两者选其一即可,效果相同。

6.2 修改 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 时会继承该值(但建议每个渠道都显式声明,避免混淆)。
  • 注意:Unity 导出的工程可能已经有 defaultConfig.applicationId,如果要保留,请确保 flavor 中的 applicationId 覆盖它。

6.3 配置 AndroidManifest.xml 占位符

在 Assets/Plugins/Android/AndroidManifest.xml 的 <application> 标签中添加:

<meta-data android:name="CHANNEL_NAME" android:value="${CHANNEL_NAME}"/>

Gradle 会在构建时自动替换 ${CHANNEL_NAME} 为对应渠道的占位符值。

7. 进阶技巧

7.1 渠道专属混淆规则

为每个渠道创建独立的 ProGuard 文件,并在 productFlavors 中引用:

huawei {    proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules-huawei.pro'}

7.2 渠道专属 AndroidManifest 片段

如果某个渠道需要额外的权限或 Activity,可以在 src/渠道名/AndroidManifest.xml 中声明,构建时会自动与主清单合并。

7.3 条件化应用华为插件

华为的 agcp 插件默认会对所有渠道生效,并在非华为渠道构建时因为找不到包名而报错。可以通过判断任务名是否包含 "huawei" 来条件应用插件:

if (getGradle().getStartParameter().getTaskRequests().toString().toLowerCase().contains("huawei")) {    apply plugin: 'com.huawei.agconnect'}

8. 命令行打包

导出 Android 工程后,在项目根目录执行以下命令即可生成对应渠道的 APK:

# 生成华为渠道 Release 包./gradlew assembleHuaweiRelease# 生成小米渠道 Release 包./gradlew assembleXiaomiRelease# 一次性生成所有渠道 Release 包./gradlew assembleRelease

生成的 APK 位于 app/build/outputs/apk/渠道名/release/ 目录下。可以将这些命令集成到 Jenkins 或其他 CI 系统中,实现自动化构建。

9. 总结

通过 Gradle Product Flavors,我们可以在 Unity 项目中实现一套代码定制化生成数百个渠道包,而不需要在 Unity 工程中编写任何平台相关逻辑。这套方案具备以下优点:

  • 代码隔离:各渠道代码、资源、依赖完全分离,避免冲突。
  • 构建自动化:支持命令行一键打包,易于集成到 CI/CD。
  • 维护成本低:新增渠道只需在 build.gradle 添加一个 flavor,并创建对应的源码目录和实现类。
  • 灵活性高:可独立控制每个渠道的包名、版本、混淆规则、AndroidManifest 内容等。

希望本文能帮助 Unity 开发者高效解决多渠道打包问题。如果有任何疑问或更好的建议,欢迎交流讨论!

最新文章

随机文章