写这篇东西的起因,是我连续在三个Unity项目里都被同一个问题逼疯了:项目工程越滚越大,同步备份的时候居然要拷好几十个G,一开始我还以为是场景贴图太多,顺手用资源分析工具扫了一遍,结果发现罪魁祸首是一个叫做Artifacts的文件夹。你说它大也就算了,最气人的是它还不受控,每次一构建Android版本,它就像吹气球一样膨胀。网上搜了一圈,大部分回答就一句"删掉就行了",可删完下次构建又长回来,等于白说。
这篇文章我打算彻底把这摊子事讲清楚,包括Artifacts文件夹在Unity工程里到底是干什么的、为什么它能把仓库撑爆、删的时候哪些能删哪些不能碰,以及最关键的:怎么从机制上让它"大不起来"。我的方案在Unity 2021、2022和2023版本上都实测过,核心方法通用,大部分结论也适用于从2020 LTS起步的老工程。如果你现在正蹲在一个动不动就几十GB的Unity项目前面发愁,这篇应该能帮你省下不少时间。
1. 先把Artifacts的底细摸清楚,再来谈清理
1.1 它不是Unity的核心目录,而是Gradle和构建插件的"临时堆放区"
很多从Unity 2019以前版本一路走过来的老开发者,对Asset、Library、Temp这些目录如数家珍,但对Artifacts就比较陌生,因为它是后来才频繁出现在工程根目录下的。我自己刚遇到的时候也以为是某个美术资源插件生成的,直到我把文件夹里面的内容打开,看到一堆classes.dex、libil2cpp.so、build.gradle临时文件,才意识到这其实是Gradle构建产物的缓存区。
在Unity构建Android目标时,编辑器会调用内置的Gradle来生成工程、编译Java/Kotlin代码、处理Manifest合并、打包资源。Gradle的工作方式决定了它不会把所有中间产物当场用完就丢,而是会缓存在一个专门的地方,方便增量构建时复用。放在工程根部还是放在系统用户目录,取决于Unity的配置方式——我见过默认配置下直接写在项目根目录的情况,也见过写在Library/Bee下面的情况,不同Unity版本和项目结构,路径会有差异。但无论它落在哪,只要你的项目接入了Android构建,就躲不开这类文件的堆积。
它压大的过程非常朴素:每多构建一次,Gradle就多生成一套中间产物,旧的不删,新的又堆上来,而这些里面还夹着大量符号文件、调试信息、FAT格式的多架构so库。如果是IL2CPP模式,还要把C#编译产物转换成C++再用NDK交叉编译,这中间生成的临时文件体积通常比原工程大好几倍。当你看到Artifacts文件夹动辄10GB、20GB时,不要觉得奇怪,这是构建链路的正常副产品,只是它没被妥善安置。
1.2 为什么这个文件夹能失控到影响开发效率
Artifacts文件夹带来的麻烦远比"占几个G硬盘"要深。首先是工程归档和团队协作,用Git管理Unity项目时,只要有人忘了把Artifacts加进忽略名单,一次构建就能往仓库里推进几百MB甚至上GB的二进制文件,几次提交下来仓库体积就彻底没法看了。如果是用Perforce这样按文件数计费或者有存储配额的系统,情况更糟糕。
其次是硬盘空间和编译速度的恶性循环。Artifacts堆积过多时,Unity编译过程中的临时文件遍历和清理本身就会变慢,构建一次可能要额外多出十几秒到几分钟的耗时。某些情况下,Windows的路径长度限制还会被这些深层嵌套的文件路径触发,导致Gradle直接报错,构建失败。等你回头想清理时,又会因为文件正在被占用而删不干净,还得先把Unity和Android Studio全部退出——这一整套折腾下来,一天的有效工作时间就这么没了。
所以,Artifacts文件夹大这件事,本质上并不是Unity自己失控,而是构建模式与磁盘管理策略不匹配的产物。明白了这一点,后面所有方案都围绕一个思路:把"每次构建生成的临时产物"与"需要长期保留的工程资产"彻底分离开。
2. 解决思路拆解:哪些能删,哪些要挡在仓库外
2.1 先分清Artifacts家族里的三类东西,再决定动手策略
很多人一看Artifacts大就直接整个目录右键删除,这是最粗暴也最容易出问题的方式。因为Artifacts这个路径下通常不只有Gradle缓存,还混着一些Unity构建系统自身生成的中间数据。我建议拆成三类来看:
第一类是可安全删除的缓存类。主要指的是Gradle的caches目录、transforms目录、以及已经完成使命的dex/so临时文件。这类文件的特点是删除后不影响任何源工程,最多就是下次构建时重新生成,耗一点编译时间。清理的时候瞄准这类内容,风险非常低。
第二类是构建期会重新生成但当前可能仍被引用的产物类。比如Library/Bee/artifacts下的增量编译对象、预编译头文件、资源索引等。如果Unity编辑器当前正开着这个工程,你强行删除这类文件,轻则编辑器重新导入花很长时间,重则引发构建缓存不一致,出现Gradle插件版本与构建文件不匹配的诡异报错。所以处理这一类时,必须先关闭Unity编辑器,或者在编辑器内通过菜单操作让引擎自己处理。
第三类是绝不能删的配置与链接类。有些工程的Artifacts目录里会放置Gradle的Wrapper配置、签名文件或构建脚本生成的定制配置,这类文件一旦丢失,整个Android构建链路就断了。极少数情况下,有些团队会把AAR、so库这类第三方依赖也复制进Artifacts目录来规避网络下载问题,这种情况更需要谨慎,别把依赖源的路径给清了。
我的处理原则很简单:构建后产物可以清、生成缓存可以清、但和"输入配置"相关的文件一律不动。
2.2 有限清理、策略调整、路径重定向:三种方案怎么选
解决Artifacts文件夹过大,市面上所有方案归纳起来就三类,按自动化程度和彻底性排序:
- 手动定期清理:适合个人项目和小团队。优点是没有额外配置成本,缺点是依赖人工记忆,容易忘记,而且清理过程中误删概率不小。如果一个月构建不了几次,Artifacts也长不到多离谱,这是性价比最高的选择。
- 构建策略调整:比如切换Gradle缓存模式、限制并行编译任务数、关闭不必要的ABI架构等。它能从源头上减少产物生成量,但不会清理已经存在的垃圾。适合项目本身依赖较多、需要长期控制增长趋势的场景。
- 路径重定向到系统临时目录或内存盘:这是目前对付这个最彻底的办法。把构建缓存指到工程目录之外,让Artifacts不再进版本库,也不会在工程目录里膨胀。代价是需要改Gradle配置或系统环境变量,对不熟悉Android构建链的开发者来说有一点门槛。
我自己的项目最终是"三重混合":日常靠.gitignore挡在仓库外,构建缓存通过环境变量指向临时目录,每个大版本结束后再手动做一次彻底清理。下面第三部分我按执行顺序把每一步写清楚。
3. 手把手实操:从安全清理到根治的完整过程
3.1 第一次动手前,先做好这五步准备
在决定删除或者重定向Artifacts之前,我建议你把下面这些准备步骤走一遍,每一步都是踩坑换来的经验:
第一步,确认Artifacts的真实路径。在Unity工程根目录下搜索Artifacts文件夹,同时也要检查Library/Bee/artifacts这个路径。不要只看根目录,因为很多Unity版本其实把真正的大头藏在Library下面。
第二步,查看Unity版本和Gradle版本。打开Unity编辑器里的Window > Package Manager,查看当前项目使用的Android Build Support版本,同时到Assets/Plugins/Android下看有没有自定义的gradle-wrapper.properties。这一步决定了你后面如果要做路径重定向,应该修改哪个层级。
第三步,确认当前工程没有正在运行的任务。关闭Unity编辑器、关闭Android Studio,如果开了模拟器也一并关掉。检查后台进程里有没有java或者Gradle相关进程占用文件,Windows下可以用任务管理器看,macOS下可以用Activity Monitor盯。
第四步,手动把这个文件夹做一个瘦身备份。不需要整个备份,只需要把里面gradle-wrapper.jar、gradle-wrapper.properties、以及任何自定义的.gradle配置文件拷出来放到工程外安全的地方。没有自定义配置的项目可以直接跳过这一步。
第五步,查看当前的忽略文件。打开项目根目录的.gitignore,确认里面是否已经有Artifacts相关条目。如果没有,后面会教你加,但先看一眼有备无患。
3.2 清理删除的具体操作和不同平台的注意事项
等准备步骤走完,就可以开始清理了。针对Windows和macOS我分别给出操作,因为文件占用和权限模型不太一样,直接抄通用命令在有些平台会失败。
Windows系统的操作流程:完全退出Unity后,打开文件资源管理器,进入工程根目录,把名为Artifacts的文件夹整个重命名成Artifacts_Backup,而不是直接删除。重命名这个动作本身就是在测试文件是否被占用,如果连重命名都成功不了,说明有后台进程还开着编辑器。确认重命名成功后再真正删除。这个操作比起直接删,多了层保险,因为万一构建引用了里面某个路径,你还有机会恢复。
macOS系统的操作流程:在Finder里把Artifacts拖到废纸篓同样会提示文件占用,合理做法是直接用终端命令看文件占用情况再删。执行下面这行,先查看哪些进程占用了被打开的句柄:
lsof +D /path/to/unity/project/Library/Bee/artifacts如果输出有java、Unity或Gradle进程,就先结束它们,再执行删除:
rm -rf /path/to/unity/project/Library/Bee/artifacts如果以上两种你都觉得费劲,更推荐直接在Unity编辑器里操作:在Unity菜单栏点击Assets,然后选择Reimport All并不合适,正确路径是在Edit > Preferences > External Tools里把Android构建的临时目录设置改掉,再在File > Build Settings > Clean里执行一次构建清理。这个方法让Unity自己删缓存,绝对不会误删配置类文件。
但这里我要特别提醒一点:清理Artifacts文件夹并不能减少已经进入版本库的历史体积。如果你之前已经把构建产物提交到了Git仓库,就算现在删了本地文件,仓库历史里还是躺着那几十个G。这种情况下必须额外对Git仓库做历史瘦身git filter-branch或者git filter-repo重写历史,这个后面第4节会单独说。
3.3 关键一步:把Gradle缓存重定向到工程之外
清理只是治标,真正治本的是让Artifacts不再积累到工程目录里。这里最常用的方法是修改Gradle的缓存目录,把它指到系统临时目录或者一个专门挂载的磁盘上。
Gradle本身支持通过环境变量GRADLE_USER_HOME来指定用户级缓存目录。但在Unity项目里,这个环境变量未必对所有构建环节都生效,因为Unity可能会用自己的路径解析逻辑去覆盖它。所以更稳妥的做法是在项目根目录下创建一个gradle.properties文件,在里面加两行:
org.gradle.caching=true org.gradle.cache.dir=/path/to/outside/project/gradle-cache注意,org.gradle.cache.dir这个参数在有些Unity捆绑的Gradle版本里会被读取,有些则不会。如果你的Unity版本恰好是那种不读取全局配置的,就需要走另一条路:在Assets/Plugins/Android/下找找有没有gradle.properties或mainTemplate.gradle文件,修改它们来注入参数。具体判断方式是看一眼你工程里的BaseProjectTemplate文件,里面有注释说明Unity支持哪些替换符。
另外还有一个专门针对Unity构建链路的方案:在Assets/Plugins/Android目录下创建一个gradleTemplate.properties文件,把缓存路径写进去。Unity在每次生成Gradle工程时会自动把这个模板文件的内容合并到生成的gradle.properties里,这样就能保证每次构建都使用你指定的外部缓存路径。我自己用的方案更简单粗暴一点,直接在系统环境变量里设置:
export GRADLE_USER_HOME="/Volumes/RamDisk/gradle"如果你的电脑内存足够大,也可以直接把缓存目录挂到一个内存盘上,这样Artifacts既不占SSD空间,读写速度还快,构建时尤其酸爽。内存盘的具体创建方式Windows和macOS各有工具,但设置完GRADLE_USER_HOME后,记得在Unity里清空一次原有的Artifacts目录,再把Unity和Gradle重启,让新路径生效。
3.4 拦截版本库:把Artifacts彻底挡在Git之外
路径重定向搞定后,还需要在版本库上补一层防线,因为如果团队里有同事的工程没有同步重定向,或者有人用了别的构建模式,Artifacts还是会通过提交溜进来。正确做法是在工程根目录的.gitignore里增加针对性的规则。
这里有一个容易踩的坑:.gitignore的写法如果不精确,会误伤同名的合法文件。比如直接写Artifacts会把任何一层目录下的Artifacts全忽略掉,这虽然符合预期,但光这样还不够,因为Library下的路径也需要覆盖。我的建议是直接加上这些条目:
# Unity generated project artifacts [Aa]rtifacts/ Library/Artifacts/ Library/Bee/ Library/Gradle/ Temp/ Obj/这里除了Artifacts,我把Library/Bee和Library/Gradle也一起忽略了。这三条是Unity构建链路里最占空间的三个目录,一条不漏地挡住,才能真正控制仓库体积。因为Unity在生成Android工程时,Gradle的构建中间文件会分布在这几个位置,只挡一个往往拦不干净。
另外,如果你是第一次给已有Git仓库加这些规则,历史提交里的文件并不会自动移除。即使你在本地删了文件夹,下一次git add -A也不会把"删除远程仓库文件"这个动作自动补上,你得主动提交一次删除变更。而这只是把之后的体积控制住,之前已经压在仓库历史里的二进制文件还是会在克隆仓库时被全量拉下来。
处理这个问题的可靠工具是git filter-repo,它在2023年之后成为Git官方推荐的历史清理工具(git filter-branch已经基本退居二线)。它的基本用法是先把远程仓库完整克隆到本地,然后执行:
git filter-repo --path Artifacts --path Library/Bee --path Library/Gradle --invert-paths执行完这行后,这些目录会从所有历史提交里被抹掉,仓库体积立刻小一个数量级。但因为这会改写历史提交哈希,所有协作者都需要重新克隆仓库,或者执行git pull --rebase来对齐。如果你的项目已经发布到公共平台并且有很多人拉取过,建议先在团队内部沟通好统一操作时间,否则会制造一堆冲突和混乱。
3.5 自定义构建管线:用命令行构建从源头控制产物
说到根治,其实还有一个很多Unity团队没充分利用的方式,那就是完全脱离Editor界面,用命令行来构建。命令行构建可以配合自定义脚本,在每次构建前自动清理缓存目录、构建后立刻删除中间产物,完全不让Artifacts有机会留在工程目录里。
我团队里的Android打包脚本大概是这样的思路,先看伪代码逻辑:
#!/bin/bash # 进入Unity工程目录 cd /path/to/unity/project # 先用Unity命令行执行一次清理构建 /Applications/Unity/Hub/Editor/2022.3.10f1/Unity.app/Contents/MacOS/Unity \ -batchmode \ -quit \ -projectPath . \ -executeMethod BuildScript.BuildAndroid \ -logFile build_log.txt # 构建完成后,清理所有缓存目录 rm -rf Library/Bee rm -rf Library/Gradle rm -rf Temp rm -rf Logs这里面最关键的是-executeMethod参数,需要你在项目里写一个静态构建方法。如果你们项目本身已经有自定义构建管线,这种方法本质上就是加几行清理逻辑而已。由于命令行执行不经过Unity编辑器的缓存依赖,删起来干净利落,不会遇到文件占用问题。同时命令行构建也更适合接入CI/CD,让构建产物直接输出到指定目录,而不是默认堆在工程里。
4. 日常维护与常见问题排查实录
4.1 为什么清理后Artifacts又长回来了?排查三大驱动因素
很多人按教程清理完,隔了一周发现Artifacts又回到几十GB,于是开始怀疑是不是没删干净。实际上它"长回来"的原因无非是这三大类:
第一类是外部SDK和插件触发的自动构建。很多第三方SDK在导入时会自动执行Gradle同步,或者调用Unity Package Manager下载依赖,这些操作本身就会触发Gradle生成中间文件。你会发现即使你什么都不干,只要打开工程,Artifacts就在悄悄增长。解决办法是检查Assets下是否有带Editor/目录的插件,这些插件很多在InitializeOnLoad时执行了Gradle相关命令。
第二类是多ABI构建模式。如果一个Android工程同时开启了ARMv7、ARM64和x86,IL2CPP会分别生成三套原生库,体积直接乘以三。用不到x86架构的话,在Player Settings > Other Settings > Target Architectures里只勾选ARM64,能省出一大截空间。
第三类是增量构建与本地Gradle缓存策略冲突。Gradle默认会缓存每个依赖的transform结果,这些文件会随着依赖数量线性膨胀。你清理后重新构建,缓存又会一点一点填回来,这是Gradle的机制决定的,不是你的操作有问题。
排查时我建议直接在构建日志里搜task :transform和Caching相关的关键字,能看到具体哪些任务是重建缓存的元凶。定位到具体任务后,再决定是改Gradle配置还是排掉这部分构建任务,比漫无目的地删要高效得多。
4.2 清理失败:文件占用、权限不足、路径过长,逐一击破
清理Artifacts最常碰到的三个失败场景,我逐一说说解决办法。
文件占用。Windows下最常见的报错是"文件正由另一进程使用"。常规的解决办法是打开任务管理器,找到所有Unity和java.exe进程,右键结束任务后再删。但有个隐蔽的坑是Unity Hub本身也会锁文件,特别是如果Hub正在做版本管理或者下载Android模块,你只关Unity是没用的,必须把Hub整个退出。另外,有些杀毒软件会把Unity的构建文件当作可疑程序扫描,也会造成短时的文件占用,这种情况下需要等待扫描完成再删,或者在杀毒软件里把工程目录加入白名单。
权限不足。macOS系统上删除Unity缓存目录时经常遇到Operation not permitted,这是因为TCC路径保护机制把Unity的应用数据文件夹保护起来了。解决方式是用Finder直接右键删除而不是用终端,Finder会以用户权限自动处理;如果还不行,就把工程目录整体拖到废纸篓里再清空,绕过TCC检查。
路径过长。Windows系统上Gradle构建产物很容易超过260字符路径限制,删除时资源管理器会提示"源路径太长"。遇到这种情况,最好的方式是先在命令行用robocopy镜像一个空目录到目标路径来清空它,命令是这样的:
robocopy C:\temp\empty_dir F:\your_project\Artifacts /MIR然后用普通的rd /s /q删除这个空壳目录,就能彻底清掉。这个思路本质上是利用robocopy的同步行为,把不存在的空目录结构镜像到长路径目录上,变相实现删除。
4.3 团队项目里的协调问题:我踩过的三个坑
处理Artifacts文件夹这件事,单纯自己搞定还不够,团队协作时会冒出一堆新问题。我在真实项目里踩过这几个坑,值得拿出来讲讲。
第一个坑是每个人本地的Unity版本不一致。团队里有同事用2021.3,有人用2022.3,Unity升级后构建生成的Gradle工程结构和Artifacts路径都有变化,导致构建链路过期缓存不兼容,报一堆奇怪的Gradle错误。解决办法是在项目根目录放一个ProjectVersion.txt,同时在团队文档里约定统一版本,不要各自为政。
第二个坑是不同角色对Artifacts的理解完全不同。程序端知道这是构建缓存,但美术和策划导入Unity时,如果恰好看到这个目录占用空间大,有时会手动去删除想"帮项目瘦身",结果把编辑器正在引用的构建配置删了,引起一连串报错。我的做法是在项目根目录放一个README或者用SVN/Git的提交锁,明确标注这些目录是自动生成、禁止手动操作。
第三个坑是换电脑后构建缓存丢失,首次构建极慢。路径重定向到临时目录后,一旦换机器或者清理系统临时文件,所有Gradle缓存都要重新下载和转换,第一次构建可能长达20甚至30分钟。团队协作者往往没有这个心理预期,容易误判为工程卡死。在文档里提前说明首次构建慢是正常现象,把预期管理做好,能省掉很多不必要的疑问。
4.4 一个挽救临床案例:仓库已经膨胀到无法克隆怎么办
最后分享一个比较极端的处理过程。去年有个项目,因为长期没人管Artifacts目录,配合某些大体积第三方SDK,整个Git仓库推到了惊人的40GB。后来有个新同事想克隆这个仓库,结果网络传输加上本地解压耗时两个小时还没结束,最后还因为磁盘空间不足直接失败。
我接手后的操作顺序是这样的:先在一台内存大、带宽好的服务器上用git clone --filter=blob:none --no-checkout这种partial clone模式把仓库拉下来,这种方式只拉取提交历史树,不拉全部文件内容,体积会小很多。然后在这个部分克隆的仓库里用git filter-repo把注册表中所有Artifacts、Library/Gradle相关的历史文件全部重写掉,再强制执行git gc --aggressive --prune=now整理对象库。最后面向全团队统一执行一次删除规则同步,给所有协作者发一份新的clone URL,用干净的新仓库替代老仓库。
这次实操之后,仓库体积从40GB降到了约4GB,哪怕算上Unity场景资源和第三方插件,这个体量也完全在正常范围了。新同事再克隆只需要几分钟,构建工具链、Gradle缓存、ABI产物这些体量巨大的文件彻底和版本库说再见,换来的是开发体验的质变。顺带说一下,--filter=blob:none是Git 2.19以后的功能,如果你还在用老版本Git,记得先升级到2.19以上再尝试partial clone。
5. 一些后续可以顺手做的事
清理完成后,有几件小事我建议你顺手做掉,虽然不直接影响Artifacts大小,但能大幅提升日常维护的容错率。
首先是给工程加一个"构建产物统计"的小脚本。我写了一个非常简单的工具脚本放在Editor目录下,它的作用是扫描Library/Bee等几个已知的大目录,把每个目录的当前体积输出到构建日志里。这样每次打包时看一眼日志,如果发现哪个目录体积异常增长,能第一时间定位。整个脚本大概几十行C#,但长期收益非常值得,它把"检查Artifacts是否异常"从手工操作变成了每次构建的常规动作。
其次是设置好当前工程的备份策略。如果项目使用外部存储或云盘同步,建议把Artifacts和Temp目录都设为不同步状态。很多云盘工具允许设置忽略文件夹,这一步能避免构建时云盘后台同步大量临时文件,白白占用带宽也拖慢构建速度。
最后是如果有条件,可以考虑给CI服务器和本地开发机各规划一个独立的构建缓存盘。本地用SSD就够了,CI服务器用更大的普通机械盘也行,最重要的是与源代码分离。当缓存盘不在项目目录里,不管怎么构建,Artifacts都不会再膨胀到工程里,这个问题才算从根上断掉了。