
简介Android SDK Tools 26.1.1 面向使用 Unity 2018/2019 打包 Android APK 的开发者提供与旧版引擎兼容的 SDK 工具集合重点缓解新版工具配置繁琐、路径识别不稳等常见问题。压缩包共 1828 个文件约 146.29MB以 jar 库、gif/png 界面资源、xml/properties 配置、html/css 文档以及 exe/bat 工具脚本为主并保留 monitor、apkanalyzer、android-sdk-license 等组件目录结构完整便于按需接入 Unity 的 SDK 路径。资源内含 SDK Manager、ADB、Zipalign 等常用工具可用于管理 Android 平台与构建工具、连接设备安装调试 APK并对安装包做对齐优化适合搭建本地打包环境时对照排查。其中 SDK Manager 负责平台与构建工具下载ADB 用于真机联调与安装卸载Zipalign 可减少安装包冗余并改善启动表现完整目录还便于核对许可文件与工具版本降低缺失组件导致的构建失败。已有 2833 人学习下载适合需要快速跑通 Unity Android 打包流程、理解 SDK 目录组织与兼容性要求的初中级开发者参考使用。1. 一个 26.1.1 的 SDK Tools 包为什么还卡着 Unity3D 打包 apk 的脖子前阵子接手一个 2017 年的老工程要求一周内交出可安装的 apk。机器上装的是刚更新的 Android Studio结果 Unity3D 打包 apk 直接报Failed to find Build Tools revision 26.0.2换了三台机器都一样。折腾半天才定位到问题不在 Unity也不在工程而在这台机器上根本没有一套版本固定、目录干净的 Android SDK Tools 26.1.1。新版 IDE 自带的 SDK 目录结构、build-tools 版本、许可证状态全是新的老工程认不出来。这个 rar 解决的就是这件事。它把 sdkmanager、platform-tools、build-tools 这一套按 API 26 时代的版本固定下来配一份 JDK 8再指给 Unity 的 External Tools能把「编得过」和「装得上」两件事同时确定下来。适合两类人手里压着老 Unity 工程、必须出包的人以及想搞清楚 Unity 出 apk 这条链路每一环到底依赖什么的人。新手照着走能出包熟手可以直接跳到第 4 章的排查表。2. 拆包落盘26.1.1 里四个目录的分工与版本咬合2.1 tools / platform-tools / build-tools 三者的边界很多人把 SDK 当成一个大而全的东西解压完就往 Unity 里一指然后开始怀疑人生。实际上从 25.3.0 之后SDK Tools 被拆成了几块职责完全不同的东西26.1.1 是这套拆分比较稳定的一版。目录关键可执行在 Unity 出包链路里的角色缺了会怎样tools/binsdkmanager、avdmanager安装/卸载其它 SDK 包的入口本身不参与编译无法补装 build-tools 和 platformsplatform-toolsadb、fastboot设备连接、安装、日志抓取Unity 报找不到 adbBuild And Run 灰掉build-tools/26.0.2aapt、aapt2、dx、apksigner、zipalign资源编译、dex 生成、签名、对齐报Failed to find Build Tools revisionplatforms/android-26android.jar编译期 classpath 里的 API 桩报Failed to find target with hash string android-26这里有个反直觉的点tools目录里是不带build-tools 和 platforms 的。rar 解压出来只是一套管理工具剩下两个目录必须用 sdkmanager 现场装。这也解释了为什么很多人解压完直接指给 Unity依然报找不到 build-tools。另一个容易混的是aapt和aapt2。26.0.x 这个版本里两个都还在Unity 2017 系列默认走 aapt2018 之后走 aapt2。如果工程里 Gradle 插件版本和 build-tools 版本不咬合就会出现「aapt 能过、aapt2 报资源链接错误」这种一半成功一半失败的现象后面第 4 章会专门讲。2.2 26.1.1 与 API 26、Unity 版本的对应关系版本咬合这件事老工程比新工程敏感得多。26.1.1 对应的是 Android 8.0也就是 API 26配套的 build-tools 是 26.0.x 系列。它对应的 Unity 大致是 2017.x 到 2018.x 这一段的前期版本。判断标准很简单看工程里ProjectSettings/ProjectSettings.asset的targetSdkVersion和AndroidTargetSdkVersion字段。如果写的是 26 或者AndroidApiLevel26那这套工具链是匹配的如果工程已经改成 28、29硬塞 26 的 build-tools 会缺 API 定义编译期直接报cannot find symbol。还有两个参数值得先说清楚因为它们在后面所有步骤里都会反复出现minSdkVersion安装门槛。26 时代 Unity 默认给 16 或 19意味着 Android 4.1/4.4 以上都能装。这个值调低不会报错但会限制你能用的 API。targetSdkVersion行为门槛。它不影响编译影响的是运行时系统按哪套规则对待你的应用。26 对应的行为变更里有两条特别容易踩通知必须走 NotificationChannelfile://形式的 URI 共享被禁止必须换 FileProvider。apk 能编出来、能装上一调分享就崩八成是这里。提示不要为了「看起来更新」把 targetSdkVersion 往上调。26 的工具链配 26 的 targetSdk是这套组合最省事的用法。2.3 解压落盘与环境变量三条路径一次配对落盘路径本身有讲究。放在带空格或者中文的目录下sdkmanager 和 Gradle 在部分系统上会直接报错报错信息还很难懂。我一般统一放到一个纯英文、无空格的短路径下。# 解压到一个不含空格和中文的目录 mkdir -p /opt/android-sdk-26.1.1 unzip AndroidSDKTools26.1.1.rar -d /opt/android-sdk-26.1.1 # 确认 tools 目录真实位置rar 里通常还套了一层 ls /opt/android-sdk-26.1.1 # 期望看到: tools/ (此时还没有 platform-tools 和 build-tools)接下来配环境变量。26 时代官方主推的是ANDROID_HOMEANDROID_SDK_ROOT是后来才成为主流的。两个都设上不会冲突反而能兼容新旧脚本这是我在一堆老 CI 脚本里被坑过之后养成的习惯。# JDK 8 是硬性要求sdkmanager 26.x 在 JDK 9 上会直接抛异常 export JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 export ANDROID_HOME/opt/android-sdk-26.1.1 export ANDROID_SDK_ROOT/opt/android-sdk-26.1.1 # 把 platform-tools 和 build-tools 加进 PATH方便直接敲 adb / apksigner export PATH$ANDROID_HOME/platform-tools:$ANDROID_HOME/build-tools/26.0.2:$PATH # 验证 JDK 版本必须是 1.8.x java -version参数说明JAVA_HOME必须指向JDK而不是 JRE。区分方法很简单看目录下有没有bin/javac。只有 JRE 的话 sdkmanager 启动就报Unable to locate Java Runtime这个报错文案有误导性很多人以为是没装 Java。Windows 下对应的做法是在系统环境变量里加同样三个变量PATH里追加%ANDROID_HOME%\platform-tools和%ANDROID_HOME%\build-tools\26.0.2。注意别在 Unity 的路径框里填带尾部斜杠的路径Unity 拼接时会拼出双斜杠部分版本解析失败。2.4 补齐 build-tools 与 platformssdkmanager 的两条命令环境变量生效之后用 sdkmanager 把缺的两块装上。这一步是整套流程里最容易卡住的地方因为 26.1.1 的 sdkmanager 在网络不好时会静默重试很久。# 1. 先看当前已安装和可用的包确认 sdkmanager 本身能跑通 sdkmanager --list # 2. 装齐三件套platform-tools、目标平台、build-tools sdkmanager platform-tools platforms;android-26 build-tools;26.0.2 # 3. 接受全部许可证必须做否则 Unity batchmode 构建会静默失败 yes | sdkmanager --licenses # 4. 复查安装结果 sdkmanager --list_installed命令说明包名里的分号是 sdkmanager 的路径分隔符platforms;android-26就对应磁盘上的platforms/android-26目录不能写成platforms/android-26那是文件路径不是包名。第 3 步单独拿出来说是因为它是整套流程里最隐蔽的坑。--licenses是交互式的会逐个包问 y/n。手动敲的时候没问题一旦放进 CI 或者批处理脚本里没人应答命令会挂住或者直接跳过SDK 目录里就会留下一个.licenses缺失的状态。Unity 在 batchmode 下遇到这个状态不会弹窗提示只会返回一个 exit code 非零日志里翻半天才能看到SDK license not accepted这种一行带过的信息。yes |的作用就是自动对所有提问回 y。装完之后ls一下根目录应该是tools、platform-tools、build-tools、platforms、licenses五项齐全。缺任何一项Unity 侧都会以不同形式报错下面这张表可以对着看缺失项Unity 侧典型报错build-tools/26.0.2Failed to find Build Tools revision 26.0.2platforms/android-26Failed to find target with hash string android-26licensesSDK license not accepted / 构建静默退出platform-toolsUnable to locate adb3. 接进 Unity从 External Tools 到命令行出包的完整链路3.1 Preferences → External Tools 三件套怎么填SDK 装好之后第一件事是让 Unity 认到它。入口在Edit → Preferences → External ToolsmacOS 在Unity → Preferences里面有三个关键输入框SDK、JDK、NDK。字段填什么填错的典型表现SDK/opt/android-sdk-26.1.1SDK 根目录不是 tools 子目录报找不到 build-toolsJDKJDK 8 的根目录含bin/javac报 Unable to locate Java RuntimeNDK走 IL2CPP 才需要Mono 后端可以留空IL2CPP 构建时卡在 stripping这里有个高频误操作SDK 框里填成.../26.1.1/tools。填 tools 目录的话Unity 会去tools/build-tools/下面找永远找不到。判断方法很简单填完之后看下方有没有出现绿色的 SDK 版本提示行没有就是填错了。JDK 还有一个历史包袱要提Unity 2017/2018 时代对 JDK 9 及以上支持很差Gradle 插件版本也跟不上。所以哪怕机器上装了新版 JDK这一栏也建议明确指向 JDK 8 的路径而不是依赖系统默认。我见过太多「命令行 javac 是 1.8但 Unity 里跑的是 11」的情况原因是 Unity 启动时继承了另一套环境变量。3.2 PlayerSettings 里和 26 强相关的四个开关External Tools 只决定「工具在哪」真正决定「打出来什么」的是 PlayerSettings。下面四个开关在 26 这套工具链下必须逐个确认。Minimum API Level建议设成 Android 4.4 (API 19) 或更高。设成 16 也能编但 26 的 build-tools 在打包某些 ABI 的 so 时会有兼容提示。Target API Level必须和 SDK 里的 platform 对应即 API 26。这一项如果设得比本地 platform 高构建会直接中断。Scripting BackendMono 编译快、包体小IL2CPP 性能好、反编译难度高。26 时代 IL2CPP 需要 NDK如果 NDK 没配选 IL2CPP 会在链接阶段报错。老工程优先 Mono除非有明确要求。Target ArchitecturesARMv7基本必选模拟器调试要加x86。注意这里的勾选直接决定 apk 里打进几套lib*.so后面第 6 章验证包体时会用上。还有一个不在 PlayerSettings 里但同样关键的开关Build Settings → Build System。26 时代 Unity 已经默认走 GradleInternal 构建系统在新版本里被移除但如果工程是从更老版本升级上来的这个值可能还残留着 Internal。用 Internal 构建出来的包不带 aapt2 处理链路和第 2 章装的 build-tools 26.0.2 是错配的。3.3 出包脚本把面板操作变成可复现的代码面板点一次两次还行一天出十个包就必须脚本化。Unity 支持-executeMethod调用一个静态方法把 Build Settings 里的操作全部代码化。using System; using UnityEditor; using UnityEditor.Build.Reporting; using UnityEngine; public static class BuildScript { // 命令行入口Unity -executeMethod BuildScript.BuildAndroid public static void BuildAndroid() { // 1. 强制走 Gradle避免老工程残留 Internal 构建系统 EditorUserBuildSettings.androidBuildSystem AndroidBuildSystem.Gradle; EditorUserBuildSettings.development false; // 2. 锁死 API 等级和本地装的 platform 保持一致 PlayerSettings.Android.minSdkVersion AndroidSdkVersions.AndroidApiLevel19; PlayerSettings.Android.targetSdkVersion AndroidSdkVersions.AndroidApiLevel26; // 3. 后端与 ABI。Unity 2018 之前 ABI 需要在面板设置 // 这里用条件编译兜住不同版本的 API 差异 #if UNITY_2018_1_OR_NEWER PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, ScriptingImplementation.Mono2x); PlayerSettings.Android.targetArchitectures AndroidArchitecture.ARMv7 | AndroidArchitecture.X86; #else PlayerSettings.SetPropertyInt(AndroidTargetArchitectures, 3, BuildTargetGroup.Android); #endif // 4. 版本号从环境变量注入方便 CI 里按 commit 打版本 var version Environment.GetEnvironmentVariable(BUILD_VERSION) ?? 1.0.0; PlayerSettings.bundleVersion version; PlayerSettings.Android.bundleVersionCode int.Parse( Environment.GetEnvironmentVariable(BUILD_CODE) ?? 1); // 5. 签名配置keystore 路径和别名从环境变量读不写死在仓库里 PlayerSettings.Android.useCustomKeystore true; PlayerSettings.Android.keystoreName Environment.GetEnvironmentVariable(KEYSTORE_PATH); PlayerSettings.Android.keystorePass Environment.GetEnvironmentVariable(KEYSTORE_PASS); PlayerSettings.Android.keyaliasName Environment.GetEnvironmentVariable(KEYALIAS_NAME); PlayerSettings.Android.keyaliasPass Environment.GetEnvironmentVariable(KEYALIAS_PASS); // 6. 组装构建参数 var options new BuildPlayerOptions { scenes new[] { Assets/Scenes/Boot.unity, Assets/Scenes/Main.unity }, locationPathName Builds/demo- version .apk, target BuildTarget.Android, options BuildOptions.None }; BuildReport report BuildPipeline.BuildPlayer(options); if (report.summary.result ! BuildResult.Succeeded) { Debug.LogError(BUILD_FAILED: report.summary.result); EditorApplication.Exit(1); // 让 CI 能捕获失败 } Debug.Log(BUILD_OK: report.summary.totalSize bytes); EditorApplication.Exit(0); } }逻辑说明这段脚本把面板上的六类设置全部固化。第 1 步的androidBuildSystem是必须的因为老工程升级上来的ProjectSettings里可能残留 Internal 值而这个值在面板上不一定显眼。第 2 步锁 API 等级是为了让构建和本地 SDK 严格对应避免出现「面板上改了但脚本没改」的双份配置。第 3 步用了条件编译是因为targetArchitectures这个 API 在 Unity 2018 前后签名不同直接写会在老版本上编译不过。第 5 步把密码放在环境变量里是签名的基本纪律keystore 和密码进仓库等于把发布权限公开了。参数说明locationPathName用相对路径相对于-projectPath指定的工程根目录。EditorApplication.Exit一定要显式调用并传退出码否则 Unity 进程可能挂住不退出CI 那边会一直等到超时看起来像是「构建卡死」实际是构建完了没退出。3.4 命令行调用与日志落盘脚本写好之后用一条命令跑完整个流程。export BUILD_VERSION1.4.0 export BUILD_CODE140 export KEYSTORE_PATH/secure/release.keystore export KEYSTORE_PASS****** export KEYALIAS_NAMEdemo export KEYALIAS_PASS****** /opt/Unity/Editor/Unity \ -batchmode \ -quit \ -projectPath $PWD \ -executeMethod BuildScript.BuildAndroid \ -logFile ./build.log命令说明-batchmode关闭所有弹窗和交互这是 CI 环境的前提。-quit让 Unity 在执行完方法后退出但注意如果脚本里已经调用了EditorApplication.Exit-quit只是兜底。-logFile指定日志落盘位置不加的话 batchmode 下日志会走 stdout很容易被 CI 的日志截断加了之后第 4 章所有的排查都靠这个文件。执行完立刻检查两件事echo $?看退出码tail -50 build.log看末尾有没有BUILD_OK。只看到Exiting batchmode而没有BUILD_OK说明构建实际上没走完退出码可能仍然是 0这是 batchmode 的一个经典陷阱。3.5 keystore 与签名配置没有签名的 apk 只能 debug 安装要发布必须有自己的 keystore。用 JDK 自带的 keytool 生成即可不需要额外工具。# 生成一个有效期 100 年的签名文件 keytool -genkeypair -v \ -keystore release.keystore \ -alias demo \ -keyalg RSA \ -keysize 2048 \ -validity 36500 \ -storetype JKS \ -storepass ****** \ -keypass ****** \ -dname CNdemo, OUdev, Odemo, Ldemo, STdemo, CCN参数说明-storetype JKS显式指定格式是有必要的。JDK 8 默认 JKSJDK 9 之后默认变成 PKCS12如果生成和读取用了不同 JDK就会出现「keystore 密码明明对却报Failed to read key from store」的情况。团队里统一用 JKS能省掉大量跨机器问题。-validity 36500是 100 年。这不是随便写的Android 要求上架应用的签名有效期必须超过 2033 年有效期太短的 keystore 在未来会直接失效且无法续期只能换新签名而换签名意味着老用户无法覆盖安装。这是那种「当时觉得无所谓、三年后追悔莫及」的坑。4. 排查Unity3D 打包 apk 最常翻的五个车4.1 报 Failed to find Build Tools revision 26.0.2现象Unity 构建刚开始就中断日志里一行Failed to find Build Tools revision 26.0.2但ls一下build-tools目录明明有东西。原因通常有两个。一是目录里装的是 26.0.3 或者 25.0.2版本号差一位都不行Unity 是精确匹配的。二是source.properties里的Pkg.Revision和目录名不一致这种多半是手动拷贝目录、或者从别的机器整包搬过来造成的。解决先cat build-tools/26.0.2/source.properties看Pkg.Revision是不是26.0.2。不一致就用sdkmanager build-tools;26.0.2重装一遍别手动改。如果工程里ProjectSettings显式指定了 build-tools 版本也要同步检查。4.2 batchmode 下许可证未接受构建静默失败现象命令行跑完退出码 0日志里没有任何 ERROR但Builds/目录下没有 apk。原因licenses目录缺失或者内容不全。交互式构建时 Unity 会弹窗让你点「Accept」batchmode 下没有弹窗这个检查直接被跳过构建流程走到一半就悄悄返回了。解决回到 SDK 目录执行yes | sdkmanager --licenses然后确认licenses/下有一堆哈希命名的文件。改完之后不要马上重跑构建先把Library/删掉因为 Unity 会缓存上一次的 SDK 校验结果不清理的话新许可证状态不会被重新读取。4.3 Windows 上路径过长或含中文Gradle 报错但信息不明现象Windows 机器上构建到 Gradle 阶段突然失败日志里全是 Java 堆栈看不出根因。原因Windows 的路径长度限制是 260 字符Unity 的 Gradle 构建会在Temp/gradleOut/下生成很深的目录结构工程路径稍长就会超限。中文路径则是 Gradle 在写文件时编码处理不当。解决工程路径控制在 60 字符以内全英文。已经建好的工程不要原地改名用-projectPath指向一个短路径的副本更稳。另外 Unity 的安装路径也别太深C:\Program Files\Unity\...这种其实已经吃掉不少配额了。4.4 apk 能装上一启动就闪退现象adb install成功点图标启动黑屏一秒退出。原因按概率排ABI 不匹配模拟器是 x86包里只有 ARMv7 的 so、IL2CPP 的libil2cpp.so没打进去、targetSdk 26 的通知/FileProvider 行为变更导致初始化崩溃。解决先用adb logcat -s Unity抓第一段堆栈比猜快得多。ABI 问题看unzip -l app.apk | grep lib/一个 so 都没有就是 ABI 没勾。targetSdk 相关的问题重点查代码里所有file://的 URI 构造以及 Notification 的构建方式26 之后必须用NotificationChannel。4.5 keystore 读取失败或签名冲突现象构建时报Failed to read key demo from store或者安装时报INSTALL_FAILED_UPDATE_INCOMPATIBLE。原因前者一般是 key alias 拼错、storepass 和 keypass 传反、或者 keystore 格式JKS/PKCS12与读取时指定的不一致。后者是设备上已经装了同包名但签名不同的旧版本。解决先用keytool -list -v -keystore release.keystore确认 alias 存在、格式正确。签名冲突只能卸载旧包或者用adb install -r -d强制降级安装仅限调试。发布渠道上永远不要换签名这是唯一没有后悔药的操作。5. 固化成脚本出包前自检、缓存边界与产物归档5.1 出包前的环境自检脚本前面所有的坑几乎都能在构建开始前 10 秒内发现。所以我习惯把检查项全部前置写成一个脚本构建前必跑。#!/usr/bin/env bash set -euo pipefail SDK${ANDROID_HOME:?ANDROID_HOME not set} FAIL0 check() { if eval $2 /dev/null 21; then echo [OK] $1 else echo [FAIL] $1 FAIL1 fi } # 1. JDK 必须是 1.8 check JDK 8 java -version 21 | grep -q 1\.8 check javac exists test -x \\$JAVA_HOME/bin/javac\ # 2. SDK 四件套齐全 check build-tools 26.0.2 test -d $SDK/build-tools/26.0.2 check platforms android-26 test -f $SDK/platforms/android-26/android.jar check platform-tools adb test -x $SDK/platform-tools/adb check licenses dir test -d $SDK/licenses # 3. 版本号一致性 check Pkg.Revision match \ grep -q Pkg.Revision26.0.2 $SDK/build-tools/26.0.2/source.properties # 4. keystore 可读 check keystore readable test -f \\${KEYSTORE_PATH:?}\ exit $FAIL说明set -euo pipefail让脚本在任何一步失败时立即退出避免在错误的环境上继续往下跑。check函数把「命令成功与否」统一成 OK/FAIL 两行输出最后用退出码返回结果CI 里可以直接当门禁。第 3 项检查Pkg.Revision是为了兜住 4.1 那个「目录名对但内容不对」的情况这个检查救过我至少两次。参数说明ANDROID_HOME和KEYSTORE_PATH都用${VAR:?}形式未设置时直接报错退出。这比等到构建中途才报null path要好得多。5.2 缓存清理的边界Library、Temp、obj 到底删哪个缓存清理是个容易过度操作的地方。全删了每次构建要十几分钟不删又会遇到各种「改了没生效」。我的经验是把清理分三档档位删除目录什么时候用代价轻Temp/每次构建前几秒无痛中Temp/obj/改了签名、SDK 路径、API 等级一两分钟重Temp/obj/Library/换了 SDK 版本、大版本升级、玄学报错十分钟以上Library/里存的是资源导入缓存和脚本程序集是 Unity 工程里最贵的东西。只有在 SDK 环境整体变更时才值得删。而Temp/gradleOut/每次构建都会重新生成留着只会造成「上次的产物混进这次」的假象。# 轻档安全且快构建脚本开头就能加 rm -rf Temp/ obj/ # 重档仅在换 SDK 之后执行 # rm -rf Library/5.3 产物归档与版本号注入出包之后如果只是把 apk 丢在Builds/里过两周就分不清哪个是哪个了。归档时至少要带三样信息版本号、构建时间、commit 短哈希。VERSION${BUILD_VERSION} STAMP$(date %Y%m%d-%H%M%S) COMMIT$(git rev-parse --short HEAD 2/dev/null || echo nogit) mkdir -p dist/ cp Builds/demo-${VERSION}.apk \ dist/demo-${VERSION}-${STAMP}-${COMMIT}.apk # 同步落一份校验和下载后可以比对 sha256sum dist/demo-${VERSION}-${STAMP}-${COMMIT}.apk \ dist/demo-${VERSION}-${STAMP}-${COMMIT}.apk.sha256说明文件名里带时间戳和 commit 哈希是为了解决「同一个版本号反复构建」的问题。版本号是给人看的时间戳和哈希是给排查用的——线上出问题时能精确对应到某一次构建比对着一个1.4.0猜是哪天打的有用得多。6. 用 build-tools 26.0.2 自带的四把刀把 apk 拆开验一遍很多人出完 apk 就结束了实际上 26.0.2 这个目录里躺着四个工具能把 apk 从外到内验一遍而且不需要装任何第三方软件。这套验证我一般放在归档之后、上传之前。第一步看清单信息。aapt dump badging Builds/demo-1.4.0.apk | head -20重点看三行package: name... versionCode... versionName...确认版本号注入生效sdkVersion:19和targetSdkVersion:26确认 API 等级没被面板覆盖native-code: armeabi-v7a x86确认 ABI 和预期一致。如果native-code那行只出现一个 ABI 而工程要求双 ABI说明 PlayerSettings 的改动没进构建回去查脚本。第二步看包内 so 分布。unzip -l Builds/demo-1.4.0.apk | grep lib/这条能看到每个 ABI 目录下打了哪些.so。Mono 后端应该只有libmonobdwgc-2.0.so和libunity.soIL2CPP 后端还会多一个libil2cpp.so。如果选了 IL2CPP 但libil2cpp.so不见了那就是 NDK 没配好链接阶段被跳过但没报错。第三步验签。apksigner verify --verbose --print-certs Builds/demo-1.4.0.apk输出里Verified using v1 scheme: true和v2 scheme: true都要为 true。26 时代 v2 签名是标配只有 v1 的话在部分新系统上装不上。--print-certs打出的证书指纹和我本地 keystore 的指纹对一遍确认不是用了 debug 签名——这个错误在 CI 里特别常见因为环境变量没传进去时 Unity 会静默 fallback 到 debug keystore。第四步验对齐。zipalign -c -v 4 Builds/demo-1.4.0.apk | tail -5注意顺序必须先 zipalign 再 apksigner。反过来的话签名会失效而且 apksigner 不会提醒你。Unity 的 Gradle 构建会自动处理这个顺序但如果是手动二次处理 apk比如替换资源后重打包顺序错了会得到一个签不上名的包。最后一步可以做体积分布核查看看包体到底被什么占着unzip -l Builds/demo-1.4.0.apk \ | awk {print $1, $4} \ | sort -rn \ | head -15前几名通常是classes.dex、libunity.so、大图资源。如果lib/占了七成以上说明双 ABI 的代价很大可以考虑拆成两个 apk 分别出或者只保留 ARMv7。这一步没有什么标准答案纯粹是让体积构成变得可解释而不是看着一个 80MB 的包发愁。提示这四个工具都在build-tools/26.0.2下如果你 PATH 加对了直接敲命令就行没加的话用绝对路径调用效果一样。血泪经验是早年我出包只看到「构建成功」就收了结果有一次发出去的包是 debug 签名的装到测试机上因为签名冲突全部失败排查了两个小时才发现是环境变量没传进 CI。从那以后我每次出包前都强制先跑一遍 preflight.sh归档后再用 aapt 和 apksigner 各验一次——多花两分钟能省掉一晚上。希望帮到你。本文还有配套的精品资源点击获取