Jenkins 构建 Unity 打包 .apk 同时生成 .aab
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 anystages {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/bashecho "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 \
-GMModeecho "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;// 是否连接 profilerbool 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;// 获取有效的 Scenelevels.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
相关文章:
Jenkins 构建 Unity 打包 .apk 同时生成 .aab
Jenkins 构建 Unity 打包 .apk 同时生成 .aab Android App Bundle简称 AAB,想了解更多关于 AAB 的知识,请看官网 https://developer.android.google.cn/guide/app-bundle/faq?hlzh-cn APK 打包部分在复用上一篇 Jenkins 构建 Unity打包APK 一、新建一…...
LTO优化详解
嵌入式开发过程中的LTO优化是什么意思?能够用来解决什么问题?它都优化了哪些?仅开启LTO真的可以优化SIZE的问题吗? 请分别回答我刚才提到的问题? 嗯,用户问关于嵌入式开发中的LTO优化,我需要详细解释这个概…...
嵌入式之条件编译
在C编程中,条件编译是一种在编译时根据特定条件选择性地包含或排除代码的技术。它通过预处理器指令来实现,允许程序员根据不同的编译环境、平台或配置来控制哪些代码被编译。条件编译在跨平台开发、调试、功能开关场景中非常有用。 1. 预处理器指令 条…...
清华大学第五弹:《DeepSeek与AI幻觉》
作者:清华大学新闻与传播学院新媒体研究中心、人工智能学院(新媒沈阳团队) 时间:2025年2月 完整版下载地址:夸克网盘分享 一、AI幻觉的定义与分类 定义 学术定义:模型生成与事实不符、逻辑断裂或脱离上下…...
理解构件的3种分类方法
对于Java程序员来说,理解“构件分类方法”是非常重要的,因为这直接关系到如何高效地管理和复用软件组件。以下是针对三种常见分类方法的具体解释: 构件的3种分类方法 1. 关键字分类法 (Keyword Classification Method) 关键字分类法是最直…...
分布式数据库解析
title: 分布式数据库解析 date: 2025/2/20 updated: 2025/2/20 author: cmdragon excerpt: 通过金融交易、社交平台、物联网等9大真实场景,结合Google Spanner跨洲事务、DynamoDB毫秒级扩展等38个生产级案例,揭示分布式数据库的核心原理与工程实践。内容涵盖CAP定理的动态…...
Zotero 快速参考文献导出(特定期刊引用)
目录 一、添加样式 每次投期刊时每种期刊的引用方式不一样,就很麻烦。发现zeotero添加期刊模板再导入很方便 一、添加样式 然后就能导出自己想要的期刊格式的引用了...
库的制作与原理(一)
1.库的概念 库是写好的,现成的可以复用的代码。本质上库是一种可执行的二进制形式,可以被操作系统载入内存执行。库有俩种:静态库 .a[Linux] .lib[windows] 动态库 .so[Linux] .dll[windows] 就是把.c文件变成.o文件,把…...
go 日志框架
内置log import ("log""os" )func main() {// 设置loglog.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)// 自定义日志前缀log.SetPrefix("[pprof]")log.Println("main ..")// 如果用format就用PrintF,而不是…...
JavaScript 最佳实践
我只选取了我还没完全贯彻的条目罗列如下. 1.函数命名 函数名由动词开头,如getName(); 2.布尔类型命名 若函数返回布尔值,则函数名以is/can等开头. 3.常量命名约定 常量名全大写并以下划线""连接. 4.变量类型透明化 定义变量时,应将其立即初使化为一个与其同类型…...
Rust配置笔记
1.Node.js下载配置 2.c环境配置 C我是用vs装的点击这个installer 点击修改 选择C环境就行,这个时候它就帮忙配置环境了 3.Rust下载配置 4.装napi-rs框架 npm install -g napi-rs/cliRust下载网站 下完之后直接打开 一开始下包会比较慢,多等等 下好之后跑项目前第一件事配置…...
大模型WebUI:Gradio全解12——LangChain原理及agents构建Gradio UI(1)
大模型WebUI:Gradio全解12——LangChain原理及agents构建Gradio UI(1) 前言本篇摘要12. LangChain原理及其agent构建Gradio UI12.1 LangChain介绍12.1.1 概念12.1.2 用途12.1.3 文档参考文献前言 本系列文章主要介绍WEB界面工具Gradio。Gradio是Hugging Face发布的简易WebU…...
具有整合各亚专科医学领域知识能力的AI智能体开发纲要(2025版)
整合各亚专科医学领域知识能力的AI代理的开发与研究 一、引言 1.1 研究背景 在科技飞速发展的当下,人工智能(AI)已成为推动各行业变革的关键力量,医疗领域也不例外。近年来,AI 在医疗行业的应用取得了显著进展,从医学影像诊断到疾病预测,从药物研发到个性化医疗,AI 技…...
机器学习在脑卒中预测中的应用:不平衡数据集处理方法详解
机器学习在脑卒中预测中的应用:不平衡数据集处理方法详解 目录 引言 脑卒中的全球影响机器学习在医疗预测中的挑战类别不平衡问题的核心痛点数据预处理与特征选择 数据来源与清洗缺失值处理方法类别特征编码特征选择技术处理类别不平衡的四大方法 SMOTE(合成少数类过采样技术…...
数据表的存储过程和函数介绍
文章目录 一、概述二、创建存储过程三、在创建过程中使用变量四、光标的使用五、流程控制的使用六、查看和删除存储过程 一、概述 存储过程和函数是在数据库中定义的一些SQL语句的集合,然后直接调用这些存储过程和函数来执行已经定义好的SQL语句。存储过程和函数可…...
为AI聊天工具添加一个知识系统 之117 详细设计之58 思维导图及观察者效应 之2 概念全景图
(说明:本文和上一篇问题基本相同,但换了一个模型 deepseek-r1) Q1227、在提出项目“为使用AI聊天工具的聊天者加挂一个专属的知识系统”后,我们已经进行了了大量的讨论-持续了近三个月了。这些讨论整体淋漓尽致体现了…...
Error [ERR_REQUIRE_ESM]: require() of ES Module
报错信息: 【报错】Message.js 导入方式不对,用的是 ES Moudle 的语法,提示使用 import 引入文件 项目开发没有用到 js-message 依赖,是 node-ipc 依赖中用到的 js-message 依赖, node-ipc 中限制 js-message 版本&a…...
GStreamer源码安装1.24版本
从官网下载 1.24的源码包 https://gitlab.freedesktop.org/gstreamer/gstreamer/-/tree/1.24?ref_typeheads#getting-started ,尝试过使用git clone 的方式,但速度贼慢,就选择了下载源码包的方式安装依赖 sudo apt install libssl-dev g me…...
从CNN到Transformer:遥感影像目标检测的未来趋势
文章目录 前言专题一、深度卷积网络知识专题二、PyTorch应用与实践(遥感图像场景分类)专题三、卷积神经网络实践与遥感影像目标检测专题四、卷积神经网络的遥感影像目标检测任务案例【FasterRCNN】专题五、Transformer与遥感影像目标检测专题六、Transfo…...
从 x86 到 ARM64:CPU 架构的进化与未来
在计算机发展的历史长河中,x86、x64 和 ARM64 这三大主流 CPU 架构各自书写了辉煌的篇章。它们不仅代表了技术的进步,更承载着无数创新者的梦想与努力。 x86:从 16 位到 32 位的辉煌之路 诞生与崛起 1978 年,英特尔(…...
Java数据结构第十二期:走进二叉树的奇妙世界(一)
专栏:数据结构(Java版) 个人主页:手握风云 目录 一、树型结构 1.1. 树的定义 1.2. 树的基本概念 1.3. 树的表示形式 二、二叉树 2.1. 概念 2.2. 两种特殊的二叉树 2.3. 二叉树的性质 2.4. 二叉树的存储 三、二叉树的基本操作 一、树型结构 1.…...
基于ffmpeg+openGL ES实现的视频编辑工具-添加贴纸(八)
在当下丰富多元的音视频编辑应用领域,添加贴纸已然成为一项广受欢迎的功能,它能够为音视频作品注入独特的趣味与创意元素。本文将深入探究音视频添加贴纸背后所涉及的技术原理与实现路径。 一、技术原理概述 音视频从本质上来说,是由一系列连续的图像帧(针对视频部分)以…...
【AI时代】基于AnythingLLM+ Ollama + DeepSeek 搭建本地知识库
一、本地安装Ollama及DeepSeek 参考教程: https://blog.csdn.net/Bjxhub/article/details/145536134 二、下载并安装AnythingLLM AnythingLLM下载地址: https://anythingllm.com/ 傻瓜式安装即可 可以自定义安装路径。三、配置AnythingLLM并使用 3.…...
leetcode刷题记录(一百一十五)——64. 最小路径和
(一)问题描述 64. 最小路径和 - 力扣(LeetCode)64. 最小路径和 - 给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。说明:每次只能向下…...
UE5 编辑器辅助/加强 插件搜集
1. Actor Locker 地址:https://www.fab.com/listings/ec26ac5e-4720-467c-a3a6-b5103b6b74d0 使用说明:https://github.com/Gradess2019/ActorLocker 支持:5.0 – 5.5 简单的编辑器扩展。它允许你通过世界轮廓窗口/热键/上下文菜单在编辑器视…...
怎么在Github上readme文件里面怎么插入图片?
环境: Github 问题描述: 怎么在Github上readme文件里面怎么插入图片? https://github.com/latiaoge/AI-Sphere-Butler/tree/master 解决方案: 1.相对路径引用 上传图片到仓库 将图片文件(如 .png/.jpgÿ…...
什么是矩阵账号?如何高效运营tiktok矩阵账号
…...
SpringSecurity初始化的本质
一、对SpringSecurity初始化的几个疑问 通过前面第一次请求访问的分析我们明白了一个请求就来后的具体处理流程 对于一个请求到来后会通过FilterChainProxy来匹配一个对应的过滤器链来处理该请求。那么这里我们就有几个疑惑。 FilterChainProxy什么时候创建的?过滤器链和对应的…...
自注意力机制和CNN的区别
CNN:一种只能在固定感受野范围内进行关注的自注意力机制。CNN是自注意力的简化版本。自注意力:具有可学习感受野的CNN。自注意力是CNN的复杂形态,是更灵活的CNN,经过某些设计就可以变为CNN。 越灵活、越大的模型,需要…...
Vue 不同大版本与 Node.js 版本匹配的详细参数
Vue 2.x 与 Node.js 版本匹配: Vue 2.x 细分版本建议 Node.js 版本理由支持状态2.0 - 2.610.x - 14.x这些 Node.js 版本在 Vue 2.x 早期开发和维护阶段广泛使用,能提供稳定运行环境,适配当时常用构建工具和依赖包Node.js 10.x 维护期已结束;…...
