Jenkins 构建 Unity 打包 .apk 同时生成 .aab

news/2025/2/23 9:52:36

Jenkins 构建 Unity 打包 .apk 同时生成 .aab

Android App Bundle简称 AAB,想了解更多关于 AAB 的知识,请看官网 https://developer.android.google.cn/guide/app-bundle/faq?hl=zh-cn

APK 打包部分在复用上一篇 Jenkins 构建 Unity打包APK

一、新建一个 Pipeline 任务

添加 Pipeline 脚本 jenkins_scripts\Pipeline\android_distribute_pipeline

// Android Distribute 打包 apk
pipeline {
    agent any
	
    stages {
        stage('Test Parameter') {
            steps {
                script {
				    // shell 脚本目录
					ANDROID_DISTRIBUTE_SHELL_PATH="${env.WORKSPACE}/jenkins_scripts/shell/android_distribute.sh"
					// 给 shell 脚本添加权限并执行
					sh "chmod +x ${ANDROID_DISTRIBUTE_SHELL_PATH} && ${ANDROID_DISTRIBUTE_SHELL_PATH}"
                }
            }
        }
    }
}

添加 Shell 脚本 jenkins-script\shell\ci_android_distribute.sh

#!/bin/sh

#!/bin/bash

echo "this is android_distribute.sh"
# 输出工作目录
echo "WORKSPACE=${WORKSPACE}"

# Unity 安装目录
UNITY_PATH=/Applications/Unity/Hub/Editor/2022.3.26f1/Unity.app/Contents/MacOS/Unity
# Unity 项目目录 Assets、Library、ProjectSettings 文件夹在 PROJECT_PATH 路径下
PROJECT_PATH="${WORKSPACE}/Project"
# bundleTool 路径 https://developer.android.com/tools/bundletool?hl=zh-cn
BUNDLE_TOOL_PATH="${WORKSPACE}/jenkins_scripts/Tools/bundletool-all-1.18.0.jar"

# 通过 export 将变量标记为环境变量,并传递给 Unity 使用
# 导出到 Unity 后都是 字符串
# 在 Unity 中通过 string value = Environment.GetEnvironmentVariable(key); 获取
# bool 类型的传递过去是字符串 "true" 和 "false"
export WORKSPACE_PATH="${WORKSPACE}"
export EXPORT_PATH="${WORKSPACE}/Export"
export KEY_STORE_PATH="${WORKSPACE}/jenkins_scripts/Tools/user.keystore"
# 生成的 apk 名字
export APK_NAME="${JOB_BASE_NAME}_${BUILD_ID}_${BRANCH_NAME:18}.apk"
# 生成的 apk 路径
export EXPORT_APK_PATH="${EXPORT_PATH}/${APK_NAME}"
# 生成 .aab 文件
export BUILD_AAB="true"
# aab 生成路径
export GOOGLE_PLAY_AAB_PATH="${EXPORT_PATH}/googlePlay.aab"
# build-apks 命令从aab 生成的 apk 组路径
OUT_PUT_APKS_PATH_1="${EXPORT_PATH}/output_1.apks"
# extract-apks 从 OUT_PUT_APKS_PATH_1的APK 集中提取设备专用 APK
OUT_PUT_APKS_PATH_2="${EXPORT_PATH}/output_2.apks"
# extract-apks 使用的设备规范 JSON
SAMSUNG_S9_PATH="${WORKSPACE}/jenkins_scripts/Tools/Samsung_S9.json"    


echo "UNITY_PATH=${UNITY_PATH}"
echo "PROJECT_PATH=${PROJECT_PATH}"
echo "BUNDLE_TOOL_PATH=${BUNDLE_TOOL_PATH}"
echo "APK_NAME=${APK_NAME}"
echo "EXPORT_APK_PATH=${EXPORT_APK_PATH}"
echo "BUILD_AAB=${BUILD_AAB}"
echo "GOOGLE_PLAY_AAB_PATH=${GOOGLE_PLAY_AAB_PATH}"
echo "OUT_PUT_APKS_PATH_1=${OUT_PUT_APKS_PATH_1}"
echo "OUT_PUT_APKS_PATH_2=${OUT_PUT_APKS_PATH_2}"
echo "SAMSUNG_S9_PATH=${SAMSUNG_S9_PATH}"


# 下面是调用 Unity 的命令
# 在 Assets 文件夹下任意目录 创建文件夹 Editor
# 新建 PojectExport.cs  删除继承 MonoBehaviour   
# 添加一个 public static void Export() 方法
# 下面命令通过 PojectExport.Export 调用
$UNITY_PATH -projectPath $PROJECT_PATH \
-buildTarget android \
-executeMethod ProjectExportApk.ExportApkAndAAB \
-logfile - \
-batchMode -quit \
-GMMode

echo "Export apk and aab success"

#BundleTools
java -jar ${BUNDLE_TOOL_PATH} build-apks --bundle=${GOOGLE_PLAY_AAB_PATH} --output=${OUT_PUT_APKS_PATH_1}
java -jar ${BUNDLE_TOOL_PATH} extract-apks --apks=${OUT_PUT_APKS_PATH_1} --output-dir=${OUT_PUT_APKS_PATH_2} --device-spec=${SAMSUNG_S9_PATH}


echo "this is android_distribute.sh end"


# ProjectExportApk.ExportApkAndAAB 方法中生成 .apk 和 .aab 文件
# Android 上线 Google Play 只需要将生成的 .aab 上传到 GooglePlay 即可

# bundletool 命令的作用,官方文档 https://developer.android.com/tools/bundletool?hl=zh-cn
# java -jar ${BUNDLE_TOOL_PATH} build-apks --bundle=${GOOGLE_PLAY_AAB_PATH} --output=${OUT_PUT_PATH} 
# 作用是使用 bundletool 工具从 Android App Bundle(.aab 文件)生成 APK 集合
# 并将生成的 APK 集合打包成一个名为“APK set archive”的文件,文件扩展名为 .apks
# 这个命令会将指定的 .aab 文件转换为 APK 集合,并将结果输出到指定的路径(OUT_PUT_PATH)。


# java -jar ${BUNDLE_TOOL_PATH} extract-apks --apks=${OUT_PUT_PATH} --output-dir=${OUT_APKS_PATH} --device-spec=${SAMSUNG_S9_PATH} 
# 作用是从之前生成的 APK 集合(.apks 文件)中提取出针对特定设备配置的 APK
# --device-spec 参数指定了一个 JSON 文件(SAMSUNG_S9_PATH),该文件描述了目标设备的特性(如屏幕大小、CPU 架构等),从而确保提取的 APK 适合该设备

# 从上面两条 bundletool 命令的作用,我们了解到,只是用 .aab 生成 apk 组到 .apks,然后再通过 --device-spec 指定的 Json 配置文件
# 从 .apks 文件中提取出满足特定设备的 apk
# .aab 文件还是ProjectExportApk.ExportApkAndAAB 方法中生成的,没有做任何处理
# 那么为什么要添加上面两条 bundletool 命令?
# 这在开发和测试阶段是非常有用的,因为它允许开发者在本地测试和验证 APK 的功能,特别是在不同的设备配置下确保其在目标设备上能正常运行。
# 对于上传到 Google Play 的生产版本,这些步骤可能不是必须的,但常常在 CI/CD 流程中用于验证构建的有效性。
# 这些命令在比较典型的开发工作流中是有用的,尤其是在测试和验证阶段,当开发者需要验证构建后的 APK 是否如预期那样工作时
三、项目配置

配置 Jenkins 构建 Unity打包APK 是一样的
修改 Editor/ProjectExportApk.cs

using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using Google.Android.AppBundle.Editor;

public class ProjectExportApk : Editor
{

    private static BuildOptions s_BuildOptions = BuildOptions.CompressWithLz4HC;


    [MenuItem("Tools/ExportAPK")]
    public static void ExportAPK()
    {
        Debug.Log("ExportApk ExportAPK start");

        // 切换平台到 Android 分支
        bool switchAndroid = EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android);
        if (!switchAndroid)
        {
            Debug.LogError("ExportApk Switch Android Error");
            return;
        }

        Debug.Log("ExportApk Switch Android success");
        string exportPath = WorkExportPath();
        if (Directory.Exists(exportPath))
        {
            Directory.Delete(exportPath, true);
        }
        Directory.CreateDirectory(exportPath);

        PlayerSettings.applicationIdentifier = "com.DeCompany.Project";

        string keystorePath = GetKeyStorePath();
        Debug.Log("keystorePath:" + keystorePath);
        // 配置 keystore 信息
        PlayerSettings.Android.keystoreName = keystorePath;
        PlayerSettings.Android.keystorePass = "123456";
        PlayerSettings.Android.keyaliasName = "testapk";
        PlayerSettings.Android.keyaliasPass = "123456";
        PlayerSettings.Android.useCustomKeystore = true;

        PlayerSettings.Android.bundleVersionCode = 2;
        PlayerSettings.Android.useAPKExpansionFiles = false;

        EditorUserBuildSettings.buildAppBundle = EnvironmentUtil.GetBool("BUILD_AAB", false);
        // 生成符号文件
        EditorUserBuildSettings.androidCreateSymbols = AndroidCreateSymbols.Public;
        EditorUserBuildSettings.exportAsGoogleAndroidProject = false;

        var options = s_BuildOptions;
        // 是否连接 profiler
        bool connectProfiler = false;
        if (connectProfiler)
        {
            options |= BuildOptions.Development;
            EditorUserBuildSettings.development = true;
            EditorUserBuildSettings.connectProfiler = true;
            EditorUserBuildSettings.buildWithDeepProfilingSupport = true;
        }

        EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Gradle;
        PlayerSettings.bundleVersion = "1.1.1";
        PlayerSettings.productName = "TestProduct";
        var targetArchitectures = AndroidArchitecture.ARM64 | AndroidArchitecture.ARMv7;
        PlayerSettings.Android.targetArchitectures = (AndroidArchitecture)targetArchitectures;

        // PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, defs);

        List<string> levels = new List<string>();
        foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes)
        {
            if (!scene.enabled) continue;
            // 获取有效的 Scene
            levels.Add(scene.path);
        }

        string apkPath = GetApkPath();
        Debug.Log("apkPath:" + apkPath);
        BuildPipeline.BuildPlayer(levels.ToArray(), apkPath, BuildTarget.Android, options);
    }

    public static void ExportApkAndAAB()
    {
        ExportAPK();
        if (!EditorUserBuildSettings.buildAppBundle)
        {
            return;
        }

        string aabPath = GetAABPath();
        var buildPlayerOptions = AndroidBuildHelper.CreateBuildPlayerOptions(aabPath);
        var assetPackConfig = new AssetPackConfig();
        assetPackConfig.SplitBaseModuleAssets = true;
        Google.Android.AppBundle.Editor.Internal.AppBundlePublisher.Build(buildPlayerOptions, assetPackConfig, false);
    }

    public static string WorkExportPath()
    {
        return EnvironmentUtil.GetString("EXPORT_PATH", Application.dataPath);
    }

    private static string GetKeyStorePath()
    {
        return EnvironmentUtil.GetString("KEY_STORE_PATH", string.Empty);
    }

    private static string GetApkPath()
    {
        return EnvironmentUtil.GetString("EXPORT_APK_PATH", "output.apk");
    }

    private static string GetAABPath()
    {
        return EnvironmentUtil.GetString("GOOGLE_PLAY_AAB_PATH", "googleplay.aab");
    }

}

ExportAPK() 方法中没有做任何修改,复用的打包 APK 的代码

生成 aab 用到了 AndroidBuildHelper、AssetPackConfig、AppBundlePublisher 类
这三个类包含在命名空间Google.Android.AppBundle.Editor中,是插件 com.google.android.appbundle 提供的
插件官方 gitHub 地址 https://github.com/google/play-unity-plugins
在这里插入图片描述
github 打开插件并下载,解压目录找到 GooglePlayPlugins 文件夹打开
在这里插入图片描述
com.google.android.appbundle 文件夹就是需要的插件
到 Unity 项目 Assets 文件夹下创建文件夹 GooglePlayPlugins,复制场面解压出来的 com.google.android.appbundle 文件夹粘贴到 Assets 文件夹下的 GooglePlayPlugins 文件夹

关于 bundletool 更多命令的使用请看 官网 https://developer.android.com/tools/bundletool?hl=zh-cn

例如:将 APK 部署到连接的设备
生成一组 APK 后,bundletool 可以将其中适当的 APK 组合部署到已连接的设备。
如果您的已连接设备搭载 Android 5.0(API 级别 21)或更高版本,bundletool 会推送在该设备上运行您的应用所需的基础 APK、功能模块 APK 和配置 APK。

如果您的已连接设备搭载 Android 4.4(API 级别 20)或更低版本,bundletool 会搜索兼容的多 APK,以将其部署到您的设备。

如需从 APK 集部署您的应用,请使用 install-apks 命令并使用 --apks=/path/to/apks 标志指定 APK 集的路径,如以下命令所示。如果您连接了多个设备,请添加 --device-id=serial-id 标志来指定目标设备。

bundletool install-apks --apks=/MyApp/my_app.apks

http://www.niftyadmin.cn/n/5863284.html

相关文章

C++17中std::chrono::duration和std::chrono::time_point的舍入函数

文章目录 1. std::chrono::duration的舍入函数1.1 floor1.2 ceil1.3 round 2. std::chrono::time_point的舍入函数2.1 示例 3. 舍入函数的应用场景3.1 时间测量3.2 数据记录3.3 时间同步 4. 总结 在C17中&#xff0c; std::chrono库提供了一组强大的时间处理工具&#xff0c;包…

androidnetflix手机版遥控器操作

Application.java // 记录Activity的生命周期 /*** hide*//* package */ final void attach(Context context) {attachBaseContext(context);JoyarHelper.getInstance().attachBaseContext(context);mLoadedApk ContextImpl.getImpl(context).mPackageInfo;}/* package */ vo…

【IO】java IO流的类型及IO模型

文章目录 分类字节流输入流输出流 字符流输入流输出流 字节缓冲流字符缓冲流4中常见的IO模型BIO&#xff08;同步阻塞模型&#xff09;同步非阻塞模型NIO&#xff08;多路复用模型&#xff09;AIO异步 分类 根据数据流向分为&#xff1a;输入流、输出流&#xff08;以内存为中…

Spring MVC中环境配置的实战应用

在现代的Spring MVC应用中&#xff0c;环境配置是一个非常重要的环节。通过合理配置环境&#xff0c;我们可以轻松地在开发环境、测试环境和生产环境之间切换&#xff0c;而无需修改代码。本文将通过一个具体的实例&#xff0c;展示如何在Spring MVC中设置环境配置&#xff0c;…

0222-leetcode-1768.交替合并字符串、389找不同、

1768.交替合并字符串 题目 给你两个字符串 word1 和 word2 。请你从 word1 开始&#xff0c;通过交替添加字母来合并字符串。如果一个字符串比另一个字符串长&#xff0c;就将多出来的字母追加到合并后字符串的末尾。 返回 合并后的字符串 。 示例 1&#xff1a; 输入&…

[VSCode]彻底卸载和重装,并搭建Java开发环境

VSCode彻底卸载 由于当初是朋友帮忙装的&#xff0c;所以准备卸载,自己装一遍 从控制面板找到 vscode 将其卸载。 此时仅仅是删除了应用软件 删除安装插件 在图示路径中找到 .vscode 文件夹&#xff0c;将其删除&#xff0c;即可彻底清除安装的插件 C:\Users\user\.vscode …

组合优化问题的机器学习研究——以图匹配问题为例

【OR Talk NO.17 | 组合优化问题的机器学习研究——以图匹配问题为例】https://www.bilibili.com/video/BV1Zf4y1S7Zr?vd_source7c2b5de7032bf3907543a7675013ce3a 定义&#xff1a; 什么是图匹配&#xff1f; 在三个图片上提取点&#xff0c;包括内点、外点、噪声点&#x…

Win11 24h2 不能正常使用ensp的问题(已解决)

因为Win11 24h2的内核大小更改&#xff0c;目前virtualbox在7.1.4中更新解决了。所以Win11 24H2系统版本无法使用 5.x.xx的virtualbox版本&#xff0c;virtualbox对于这个5.x.xx版本早已停止维护&#xff0c;所以这个以后不会有调整。 对应的报错代码是 virtualbox错误代码&…