Jenkins+SVN+Unity自动打包搭建指南:从环境配置到常见坑 1. 打包这件事为什么会想到Jenkins大概每个做过Unity客户端开发的人都有过这样的经历策划催版本、测试要包、老板要看效果你一个人坐在电脑前打开Unity Hub挑个版本开个工程等它慢慢编译然后手动Build签名改包名传到群里再所有人“新包在这”。一天折腾两三次还行迭代高峰期一天七八个包人基本就废在打包上了。我之前在的那家小团队Unity打包一直是“谁有空谁打”用的还是最原始的办法——本地Build。后来新版本需求来了代码在SVN上多人协作每次合代码都要出包验证人力成本一下子就上来了。那段时间我们想了想决定用Jenkins搭一套自动打包流程把SVN拉代码、Unity编译、APK输出、版本号管理全串起来。这篇文章就围绕这套流程的完整落地来讲。我在实际搭建过程中踩了不少坑网上资料又散所以打算把自己趟出来的路子整理一遍从环境准备、Jenkins任务配置、Unity命令行调用、SVN集成、签名与版本管理到常见问题的排查方式尽量写成一套可以照着抄的作业。适合正在搞Unity自动出包的客户端开发、构建工程师或者是被打包流程折磨到想离职的可怜人。先说结论吧这套方案上手难度不高核心就三件事——Jenkins负责调度SVN负责代码版本Unity命令行负责把工程变成APK。只要把这三者的关系理清楚后面汇总基本都是细节问题。2. 整体方案拆解Jenkins、SVN、Unity怎么协作2.1 三者的角色划分一开始我脑子里其实挺乱的因为Jenkins、SVN、Unity这三样东西各自都有复杂的一面串在一起容易不知道谁先谁后。这里先把各自的责任边界划清楚。Jenkins在整条链路里扮演的是调度中心的角色。它负责监听任务触发条件手动点击、定时轮询SVN变化、接口调用等然后把“拉代码→执行构建→收集产物”这个流程串起来。它不直接参与Unity的渲染和编译只是作为一个平台把命令行指令按顺序跑完。SVN的角色是代码托管和数据源。Jenkins会在构建前从SVN上拉取指定路径、指定版本号的代码到工作目录。这一步很关键因为Unity打包出的APK必须基于最新或者指定版本的代码如果代码拉错了后面全白折腾。Unity本身提供了命令行模式的打包接口可以不用打开编辑器界面直接在后台执行构建逻辑。我们需要写一个编辑器扩展脚本C#通过Jenkins调用Unity可执行文件并传入脚本方法名让Unity自己完成场景加载、资源打包、APK生成这一整套动作。这三者的关系可以理解成Jenkins是包工头SVN是材料仓库Unity是加工车间。包工头从仓库取料送到车间加工最后把成品拿走去分发。2.2 为什么用Jenkins而不是其他方案市面上能实现自动打包的工具其实不少有TeamCity、GitLab CI、GitHub Actions等还有一些团队直接用批处理脚本配合任务计划程序来跑。那为什么我们最终选了Jenkins说实话最主要的原因是团队当时用的是SVN而不是Git。GitHub Actions和GitLab CI对Git的支持当然很好但对SVN要么不支持要么支持得很别扭。Jenkins对SVN有原生插件支持配置起来非常直接拉取代码这块不需要额外写很多脚本。另外Jenkins是自托管的构建机就是我们自己的一台Windows工作站Unity的许可证、Android SDK、JDK环境都装在那台机器上。用云端的CI服务反而麻烦因为云端环境是临时的每次构建都要重新准备Unity环境时间和存储成本都不划算。Jenkins装好后常驻在那台Windows机器上构建环境是稳定的、可复现的。还有一点是Jenkins的插件生态非常成熟。Unity构建这块虽然没有官方插件但是通过“Execute Shell”或者Windows下的“Execute Windows Batch Command”步骤可以非常灵活地拼装命令。SVN插件、参数化构建插件、构建后通知插件都是现成的基础流程不需要自己造轮子。2.3 单机版还是主从架构如果你只是给自己团队用构建频率不高单机版Jenkins完全够用。我一开始就是单机跑的一台Windows工作站既是Jenkins服务端也是Unity构建机。后面如果构建任务多了再考虑拆出独立的agent节点。单机版的好处是环境配置简单不用考虑主从之间工作目录、环境变量同步的问题。但有个隐患Jenkins本身的运行会占用一定内存和CPU如果同时跑Unity构建机器性能可能吃紧。我们的工程不算大构建一次大概3到5分钟单机跑下来感觉还行。如果你们是大型工程构建一次十几分钟甚至更久建议预留一台专门的构建机Jenkins和Unity都在那台机器上普通开发机不要承担构建任务。3. 环境准备这步没做好后面全是坑3.1 Windows下的基础软件清单先列一份我实际用的软件清单版本号不一定必须一致但尽量别差太远否则一些参数对不上会很头疼。软件版本建议说明Jenkins2.4x以上推荐用LTS版本稳定优先JDK8或11Jenkins 2.4xx以上对JDK版本有要求装JDK11比较稳妥Unity跟你项目一致建议固定一个版本用Unity Hub管理Android SDK根据Unity版本配套Unity自带或单独安装都可以SVN客户端TortoiseSVN或者命令行版Jenkins的SVN插件不依赖本机svn命令但命令行工具方便调试Android SDK Build-Tools对应版本用于APK签名等操作我自己用的是JDK11 Jenkins 2.414.2 Unity 2021.3.16f1 Android SDK Build-Tools 30.0.3这套组合跑得很稳。3.2 Unity的Windows环境特别注意事项Unity在Linux和macOS上跑命令行构建其实更省心但Windows上会多一些细节问题。第一点是Unity路径。Unity Hub安装的Unity编辑器默认在C:\Program Files\Unity\Hub\Editor\版本号\Editor\Unity.exe路径里带空格你在Jenkins的批处理命令里必须用引号包住整个路径否则命令直接报错找不到。第二点是Windows服务方式运行Jenkins时的权限问题。如果你把Jenkins安装成Windows服务默认是以Local System账户运行的这个账户可能没有加载Unity许可证的权限导致构建时报“No valid Unity license”这个经典错误。我后来改成以普通用户身份启动Jenkins服务或者干脆直接运行Jenkins的jenkins.war问题就解决了。第三点是Android SDK路径识别。Unity打包Android需要知道SDK路径可以在Unity编辑器里通过“Preferences → External Tools”配置也可以在命令行构建时通过-setDefaultProfiler之类的参数指定但你得确保Jenkins能读到这个SDK路径的环境变量。3.3 SVN账号认证的提前准备Jenkins拉取SVN代码时需要在Jenkins里配置SVN的凭据。这个凭据很简单就是SVN服务器的用户名和密码。但有个细节容易被忽略如果你们公司SVN账号绑定了IP或者需要二次验证那Jenkins这边可能拉不下来代码。我们当时就遇到过一个情况开发同事在本地TortoiseSVN保存了认证信息SVN服务器设置在非工作时间段禁止某些账号访问。结果Jenkins半夜定时构建时代码检出直接失败。排查了半天发现是用了一个权限受限的账号。后来专门给Jenkins建了一个独立的只读账号只允许访问需要打包的仓库目录问题才彻底解决。建议不要用个人账号作为Jenkins的SVN凭据。一旦这个人离职或者改密码构建任务就会挂掉。建一个专用的服务账号权限最小化出问题也好定位。4. Jenkins任务配置从零到能出包的关键步骤4.1 新建任务与基础配置登录Jenkins后点击“新建任务”输入一个任务名称比如Unity_Android_Build选择“构建一个自由风格的软件项目”。这种类型的任务最灵活适合我们这种需要组合多个构建步骤的场景。进入配置页面后先看“General”区域。我建议勾选“丢弃旧的构建”设置保留天数为7天、保留最大构建数为20。因为Unity构建产物非常大每个APK几十上百MB构建记录如果无限保留磁盘很快就满。再看“源码管理”区域选择“Subversion”然后填入SVN仓库地址。这里有一个Jenkins SVN插件很典型的用法仓库URL填到Repository URL在Credentials那里添加你的SVN账号密码。Jenkins会自动将代码检出到工作空间目录里的仓库名子目录下。有一个容易踩坑的点如果SVN仓库很大建议勾选“Use update”而不是每次全新checkout。因为Unity工程动辄几个GB资源每次全新checkout不仅慢还容易把构建机磁盘搞爆。设置成增量update只有变更的文件才会下载。4.2 参数化构建让每次打包灵活起来单纯的固定流程肯定不够用。比如你可能需要手动选择拉取哪个分支、构建Debug还是Release、版本号打多少。这些如果需要每次改配置就太蠢了用Jenkins的参数化构建可以完美解决。在“General”中勾选“参数化构建过程”然后添加参数。我常用的参数有这么几个参数名类型默认值说明SVN_BRANCHString Parametertrunk分支路径比如trunk、branches/test_xxxBUILD_TYPEChoice Parameterreleasedebug/release影响Unity里的打包配置VERSION_CODEString Parameter1.0.0版本号传给Unity脚本RELEASE_NOTEString Parameter无本次构建说明设置好这些参数后在SVN的Repository URL里可以直接引用比如https://svn.example.com/project/${SVN_BRANCH}。参数化构建的好处是同一个任务既能支持开发手动触发挑选分支又能支持定时构建使用默认参数灵活度很高。4.3 构建触发器手动、定时、轮询SVN构建触发器有三种常用方式我分别说一下。第一种是手动构建最简单点击“立即构建”即可。适用于准备发版、临时出包这类不需要频繁操作的场景。第二种是定时构建在“构建触发器”里勾选“定时构建”然后在日程表里填Cron表达式。比如H 2 * * *表示每天凌晨2点左右构建一次。这种适合每天晚上自动出一个最新的测试包。第三种是轮询SVN变更勾选“Poll SCM”日程表填H/5 * * * *表示每5分钟检查一次SVN有没有提交记录有变化才触发构建。这个功能很好用能够实现“代码一提交自动出包”的理想状态。但要注意Poll SCM是Jenkins定期向SVN服务器查询变更本身有延迟而且频繁轮询会给服务器增加压力。我的建议是日常开发用Poll SCM比如5分钟一次发版用定时构建临时需求手动触发。4.4 构建步骤调用Unity命令行这是整个流程里最核心的一步。在“构建”区域点击“增加构建步骤”选择“Execute Windows Batch Command”然后填入Unity命令。我实际使用的命令行大概是这样的set UNITY_PATHC:\Program Files\Unity\Hub\Editor\2021.3.16f1\Editor\Unity.exe set PROJECT_PATH%WORKSPACE%\UnityProject set EXECUTE_METHODBuildScript.BuildAndroid %UNITY_PATH% -batchmode -nographics -quit -projectPath %PROJECT_PATH% -executeMethod %EXECUTE_METHOD% -buildTarget Android -logFile %WORKSPACE%\unity_build_%BUILD_NUMBER%.log关键点拆解一下-batchmode表示以批处理模式运行不启动图形界面。-nographics表示不初始化图形设备纯命令行构建加速编译。-quit表示执行完方法后自动退出Unity。-executeMethod指定我们写在Unity工程里的静态构建方法名。-logFile把Unity的构建日志输出到指定文件方便排查问题。%WORKSPACE%和%BUILD_NUMBER%是Jenkins内置环境变量WorkSpace表示当前构建的工作目录BUILD_NUMBER表示构建序号。这里有个大坑需要提醒如果不加-quit参数Unity执行完构建方法后进程可能不退出导致Jenkins认为构建未结束一直挂在那。我刚开始漏了这参数每次构建任务都要手动杀掉Unity进程才能继续后来加上-quit才正常。还有一个细节是-logFile参数。如果不指定Unity日志会输出到Unity默认的日志路径在Jenkins的控制台输出里看不到具体报错。指定了logFile后构建结束后可以直接在Jenkins的日志归档里看到完整日志排查问题效率会高很多。4.5 构建后操作产物归档与通知构建成功后APK文件默认会生成到Unity工程里的Assets/../Build目录下。我们需要把它归档到Jenkins里方便下载。在“构建后操作”里选择“Archive the artifacts”然后填归档路径。比如**/Build/*.apk这个通配符会匹配工作空间下所有Build目录里的APK文件。归档后Jenkins每次构建的页面都会有一个“已构建的工件”下载链接直接点击就能下载APK。如果还需要通知团队可以安装“Email Extension”插件在“构建后操作”里配置邮件通知构建失败时发邮件给相关人。我们团队用的企业微信机器人通过Webhook方式在构建成功或失败时发一条消息到群里比邮件更及时。5. Unity侧的构建脚本写一个干净的BuildScript5.1 为什么必须写编辑器脚本Unity的命令行本身只能打开工程、执行一些基础操作如果没有一个编辑器脚本接收参数并调用BuildPipeline那-executeMethod就是一句空话。这个脚本本质上是一个静态方法Unity执行到这一行时会调用你指定的方法然后你在方法里写构建逻辑。我把这个脚本放在Assets/Editor/BuildScript.cs因为Editor目录下的代码不会被打进游戏包里只会在编辑器环境下生效。脚本的大致结构如下using System; using System.IO; using UnityEditor; using UnityEngine; public static class BuildScript { public static void BuildAndroid() { string buildType GetArg(-buildType); string versionCode GetArg(-versionCode); string branch GetArg(-branch); if (string.IsNullOrEmpty(buildType)) { buildType release; } PlayerSettings.bundleVersion string.IsNullOrEmpty(versionCode) ? 1.0.0 : versionCode; // 在这里可以设置包名、签名、IL2CPP选项等 PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, ScriptingImplementation.IL2CPP); string outputPath Path.Combine(Application.dataPath, ../Build/Android); if (!Directory.Exists(outputPath)) { Directory.CreateDirectory(outputPath); } string apkName $Game_{buildType}_v{versionCode}.apk; string outputFile Path.Combine(outputPath, apkName); var options new BuildPlayerOptions { scenes new[] { Assets/Scenes/Main.unity }, locationPathName outputFile, target BuildTarget.Android, options buildType debug ? BuildOptions.Development | BuildOptions.AllowDebugging : BuildOptions.None }; var report BuildPipeline.BuildPlayer(options); if (report.summary.result ! UnityEditor.Build.Reporting.BuildResult.Succeeded) { throw new Exception($Build failed with result {report.summary.result}); } Debug.Log($Build succeeded: {outputFile}); } private static string GetArg(string name) { string[] args Environment.GetCommandLineArgs(); for (int i 0; i args.Length; i) { if (args[i] name args.Length i 1) { return args[i 1]; } } return null; } }5.2 版本号和包名管理的细节在脚本里我从命令行参数里读取-versionCode然后赋值给PlayerSettings.bundleVersion。这样每次出包的时候Jenkins那边传入不同的版本号打出APK的版本号就跟着变。包名也可以做成参数比如com.company.product在脚本里用PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.Android, packageName)设置。尤其是测试环境、正式环境、预发布环境需要不同包名的时候这个功能特别有用否则同一个包名无法同时安装两个版本。有一个坑需要专门说如果你在Unity的Project Settings里手动设置了包名然后构建脚本里又覆盖了包名得注意执行顺序。比如你先调了SetApplicationIdentifier后面又在Player Settings界面修改过下次构建可能又变回去。公司里多人改配置的情况尤其严重。我的经验是包名的最终值只认构建脚本构建脚本里明确写死不要依赖编辑器里的默认设置。5.3 不同渠道的配置Unity打包经常要面对多渠道的问题——同一个游戏要出应用商店版、广告聚合版、国内SDK版、海外GooglePlay版每个渠道的包名、签名、服务器地址可能都不同。在构建脚本里我习惯用一个渠道参数-channel来区分。Jenkins构建时传入-channel googlePlay或-channel cnSdk脚本根据渠道值切换对应的包名、SDK配置和资源目录。string channel GetArg(-channel); string packageName channel googlePlay ? com.company.game.googleplay : com.company.game.cn;这个做法配合参数化构建效果非常好。同一个Jenkins任务通过选择不同的渠道参数就能打出不同的包不用维护多条构建任务。6. Android签名配置APK能出装上却闪退怎么排查6.1 Unity内配置签名 vs 外部签名Unity打包Android时签名可以在Unity的Project Settings里配置也可以打出一个未签名的APK然后用外部工具比如apksigner进行签名。两者的区别在于Unity内签名每次构建都用同一个keystore配置简单适合快速出测试包。外部签名可以让Jenkins在构建完成后对APK进行单独的签名操作适合需要分渠道不同签名的场景。我建议团队日常测试包直接用Unity配置好调试签名正式发版包单独走签名流程。因为Unity内签名如果每台打包机都要放keystore文件和密码泄漏风险会增大。外部签名的话keystore可以统一放在构建机上由Jenkins的构建脚本调用签名工具代码里不暴露密码。6.2 Keystore的存储和密码管理这部分我踩过很深的坑。刚开始图省事把keystore密码直接写在Jenkins的批处理命令里。结果有一次排查问题时意外发现构建日志里清清楚楚打着密码整个人有点冒冷汗。后来我把密码挪到了Jenkins的“凭据”管理里在批处理命令中通过环境变量引用set KEYSTORE_PASS%KEYSTORE_PASS%然后在Jenkins凭据里添加一个“Secret text”类型的凭据在构建步骤中绑定为环境变量。这样日志里就不会直接输出明文密码。在构建脚本里千万不要用echo打印密码哪怕你只是在调试。日志是会归档的时间一长别人翻到就是安全事故。6.3 常见签名报错处理签名相关的报错有很多种我列几个我遇到过的报错信息原因解决方式Keystore was tampered with, or password was incorrectkeystore密码错误或文件损坏检查keystore文件和密码是否匹配JKS keystore uses a proprietary format用的JKS格式部分工具不支持用keytool转换成PKCS12格式No valid keystoreUnity配置中keystore路径无效确认路径是构建机上的真实路径而不是本地路径APK signature verification failed设备安装时签名校验失败确认APK是用正确的签名工具和keystore签的有一种情况特别隐蔽Unity打包使用了Debug签名而你团队内部测试时手机里已经装了一个用正式签名的包。此时用Debug签名的APK去覆盖安装会提示“与现有应用签名不同”装上就闪退或者直接安装失败。解决办法就是保持统一内部测试全部用同一个签名或者卸载旧包重新安装。7. SVN与Jenkins集成的那些事7.1 检出路径与工作空间的关系Jenkins的SVN插件有个特点它会根据Repository URL自动在工作空间创建一个子目录。比如你在SVN URL里填的是https://svn.example.com/project/trunkJenkins会创建一个名为trunk的目录代码都在里面。后面你在Unity命令行里填写工程路径时必须带上这个子目录不能只写%WORKSPACE%。我一开始总是忘记结果Unity报projectPath does not exist的错误。一种更统一的做法是在源码管理里把“Local module directory”设置为一个固定的目录名比如unitycode。这样不管SVN URL怎么变代码始终检出到工作空间的unitycode目录下后续命令引用路径就稳定了。7.2 版本号与代码版本的对应自动构建系统还有一个很实用的需求通过构建产物反查代码版本。比如测试提到一个问题说“这个bug在某某包上出现”你得能快速知道这个包是用哪个SVN版本编译的。我用的办法是在构建脚本里把SVN版本号REVISION写进APK的文件名或者生成一个版本信息文件。Jenkins里有内置的环境变量SVN_REVISION可以直接在批处理里取到。echo SVN_REVISION%SVN_REVISION% %WORKSPACE%\version_info.txt然后把version_info.txt和APK一起归档。这样每次出包都能明确知道代码版本排查问题就快了。还有一种更彻底的方案在Unity构建脚本里通过File.WriteAllText把一个包含SVN版本号的文本写入StreamingAssets目录这样APK内部也带着版本信息运行时可以直接在游戏里查看。7.3 SVN变更日志与构建通知Jenkins的SVN插件会在构建完成后生成一份“Subversion 变更日志”记录本次构建包含哪些提交记录、修改了哪些文件。这个功能在“构建后操作”里勾选“Publish JUnit test result report”旁边的“Subversion”相关选项后会自动生成。我们团队用这个变更日志做了更进一步的联动构建完成后Jenkins把变更日志内容拼进企业微信机器人发送的消息里。这样测试人员收到通知时能直接看到本次包里包含了哪些代码变更不需要自己去SVN上查日志。这个细节极大提升了沟通效率建议有条件都搞上。8. 实操过程中的常见问题与排查实录8.1 Jenkins构建超时、一直卡住表现点击构建后任务一直显示在运行进度条不动最后超时失败。我遇到过几次原因基本都出在Unity进程没有正常退出。最常见的场景是Unity弹出一个模态对话框比如许可证过期、崩溃报告、以及“Unity正在从Hub打开工程是否继续”这类弹窗在batchmode下如果有弹窗进程就会挂住。解决办法有几个第一确认命令行加了-quit参数。第二在批处理里加一个超时自动杀进程的逻辑start /wait %UNITY_PATH% -batchmode -quit ... taskkill /F /IM Unity.exe /FI MEMUSAGE gt 1000 2nul可以用timeout命令或者在Jenkins任务配置里设置超时时间。我用的是在批处理末尾加taskkill /F /IM Unity.exe兜底确保万一Unity卡住构建能结束。但这里要小心taskkill /IM Unity.exe会把打开着Unity编辑器做开发的人给杀了。如果你这台构建机同时有人用来开发就不能这么干。所以理想的方案是构建机专用物理隔离。8.2 Unity报“Failed to load Android SDK”表现构建时Unity提示找不到Android SDK或者SDK版本不匹配。排查思路第一步确认Unity编辑器里Android SDK路径是否正确。打开Unity → Preferences → External Tools → Android SDK看看路径是否存在。第二步确认SDK包里有没有安装对应版本的Platform和Build-Tools。Unity 2021.3一般需要Android SDK Platform 30和Build-Tools 30.0.2以上。第三步检查环境变量。在Windows下Jenkins以服务方式运行时环境变量可能和本地用户不一样。可以在批处理里手动指定SDK路径set ANDROID_HOMEC:\Android\Sdk然后传给Unity。Unity命令行支持-androidSdkRoot参数直接指定%UNITY_PATH% -batchmode -nographics -quit -projectPath %PROJECT_PATH% -executeMethod %EXECUTE_METHOD% -buildTarget Android -androidSdkRoot %ANDROID_HOME% -logFile %WORKSPACE%\unity_%BUILD_NUMBER%.log8.3 IL2CPP编译巨慢怎么办Unity的Android构建如果选择IL2CPP ARM64编译速度会明显比Mono慢很多。尤其是在Windows机器上跑一次完整构建可能需要10分钟以上。如果Jenkins构建频率高开发人员等待的时间会很难受。我试用过几个优化手段开启Unity的增量构建。如果工程代码没变资源没变第二次构建会明显快很多。不要每次构建都强制全量Build。把构建机性能拉满CPU多核、内存32GB起步、系统盘用SSD。IL2CPP编译是CPU密集型操作机器性能直接影响出包速度。尽量避免同时跑多个Unity构建。多个Unity进程同时构建会互相抢占CPU和内存反而更慢。另外还有一个小优化Unity 2021.3版本开始支持“Build Pipeline”的并行编译选项可以在构建脚本里设置EditorUserBuildSettings.androidBuildType等参数减少编译时间。我实际优化后构建时间从10分钟降到6分钟左右。8.4 生成的APK在手机上安装失败这个问题分两类一类是安装时提示“应用未安装”另一类是安装成功但打开闪退。“应用未安装”最常见的原因是包名冲突或者签名不一致。我们之前遇到过一次同一个包名之前的包用正式签名新包却用了测试签名导致安装失败。解决方案就是检查签名是否匹配以及包名是否与设备上已安装应用冲突。“打开闪退”则要重点看Unity日志。如果APK可以装但启动即退多半是IL2CPP的库没打包全或者Android API Level不匹配。还有一个容易忽略的点如果你用BuildOptions.Development模式打包开启脚本调试时在某些国产ROM上可能启动异常。这种情况直接用Release包测试即可或者抓一下logcat看具体崩溃原因。8.5 Jenkins目录权限导致构建失败这个坑比较隐蔽但也比较常见。Jenkins如果是Windows服务方式运行默认账户可能是Local System它对某些目录没有写入权限。Unity构建过程中需要在工程目录、Library目录、Temp目录写入大量文件如果没有权限就会报Access denied。我的处理方案是在Jenkins的Manage Jenkins → Configure System里把Environment variables里的TEMP和TMP指向一个有权限的目录比如D:\jenkins_temp。同时确保工作空间目录%WORKSPACE%对Jenkins运行账户有完全控制权限。8.6 常见问题速查表问题现象可能原因解决方法Unity进程卡住不退出没加-quit参数或弹窗阻塞加-quit批处理末尾兜底杀进程构建提示license无效Jenkins服务账户加载不到Unity许可证用普通用户运行Jenkins服务拉不到SVN代码凭据失效或账号权限不足重新配置凭据使用专用账号生成的APK没有签名Unity配置里keystore路径错误检查Unity External Tools中的keystore设置构建日志里无输出没指定-logFile参数加上logFile并归档产物归档后在页面下载不了归档路径不对检查归档通配符如**/Build/*.apk9. 进阶玩法这套流程还能怎么扩展9.1 按分支自动出包如果你维护多个分支比如trunk、test分支、release分支可以在Jenkins里创建多个任务每个任务固定SVN地址的某个分支。然后通过Poll SCM监听到分支变化时只构建对应分支的包。这样代码往分支上一提交对应版本的APK也就出来了。当然也可以只用一个任务通过参数化构建手动选择分支。自动按分支出包适合发版节奏稳定、分支改动频繁的项目参数化构建适合临时需求多的阶段。9.2 对接企业微信/钉钉通知Jenkins本身有邮件通知功能但在国内团队实际使用体验很一般——大家不怎么看邮件。我后来接入了企业微信机器人构建完成后向群里推送一条消息包含构建编号、APK下载链接、本次变更日志。实现方式不复杂在Jenkins构建后的“Execute Windows Batch Command”里调用Webhook地址用curl或者PowerShell发送POST请求。一个简单的PowerShell示例$body { msgtype text text { content 构建成功 #$env:BUILD_NUMBERn下载: ${env:BUILD_URL}artifact/Build/Game_release.apk } } | ConvertTo-Json -Depth 3 Invoke-RestMethod -Uri https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyxxxx -Method POST -Body $body -ContentType application/json; charsetutf-89.3 把测试包上传到统一分发平台测试包光放在Jenkins里还不够测试人员未必有Jenkins账号也不一定方便从机器下载。我们团队后来用脚本把APK上传到自建的分发平台自动生成二维码测试手机扫码直接安装。这一步自动化后整个出包流程就彻底打通了从代码提交到测试机安装中间不需要任何人手工干预。10. 踩坑几年后我对这套方案的个人体会如果你问我搭建这套Jenkins SVN Unity自动打包系统最费时间的是哪部分我会说是前三天的环境配置和第一次跑通全流程。一旦第一个APK成功归档到Jenkins页面上后面的迭代优化都是水到渠成的事。我个人在实际操作中最大的体会是不要把自动化想得太复杂也不要过度设计。刚开始能跑通一个最简单的流程就足够了。比如第一步先做一个手动触发的构建任务固定拉trunk代码固定出一个release包先跑通。跑通之后再逐步加上参数化构建、渠道配置、通知推送、分支轮询这些高级功能。另外一个非常重要的心得是整个流程中最脆弱、最容易出问题的环节不是Unity不是SVN而是环境的一致性。构建机上的Unity版本、Android SDK版本、JDK版本必须全部记录在案不能随便升级。有一次我们升级了Unity的小版本结果Android SDK的兼容性变化构建出的APK在部分低版本安卓设备上崩溃排查了很久才定位到是Unity升级惹的祸。从那以后我都坚持“构建机不动原则”除非有明确需求否则不升级任何基础软件。最后再分享一个小技巧构建机的磁盘空间管理要提前规划。Unity工程加Library缓存动辄几十GB加上每次构建产生的APK和日志磁盘满了构建就会莫名其妙失败。我在Jenkins里开启了“保留最近N次构建”的清理策略同时定期手动清理Unity的Library缓存目录整体稳定了很多。这套方案落地后我们团队从“每天手动出包到崩溃”变成了“提交代码自动出包、睡醒拿包测试”的节奏效率提升非常明显。如果你正在被重复的打包流程折磨建议按这篇文章的步骤试一遍大概率能少走一半弯路。