UE5安卓包体优化实战
UE5安卓包体优化实战
看了好几个大佬写的包体优化的文章了,一直没实际操作一下,趁现在手里头没啥活,实操一下(摸鱼一下),巩固一下知识。
打包设置
包体优化,首先需要知道包体里面有哪些东西,以第三人模板为例,引擎版本5.3.2(源码版本引擎),修改一下打包设置,然后打个包。

把游戏资源都打包进入apk

因为是打最小包,所以直接打shipping包

这个就是我们需要的文件了,其他文件暂时先放一边
包体大小分布
查看APK可知,包体主要是包含以下几项:
assets文件下的main.obb.png(游戏内资源 Pak)- lib文件下的可执行代码(so -
lib/arm64-v8a) - 其他第三方组件拷贝进 APK 内的文件

根据上面这三种资产开始分别优化
压缩 NativeLibs
对于原生 Android 而言,是否在安装时解压 NativeLibs 是由 AndroidManifest.xml 中的 extractNativeLibs 控制的:
1 | <application android:allowBackup="true" android:appComponentFactory="android.support.v4.app.CoreComponentFactory" android:debuggable="true" android:extractNativeLibs="false" android:hardwareAccelerated="true" android:hasCode="true" android:icon="@drawable/icon" android:label="@string/app_name" android:name="com.epicgames.ue4.GameApplication" android:networkSecurityConfig="@xml/network_security_config" android:supportsRtl="true"> |
在新版引擎中,在 AndroidRuntimeSettings 配置中直接提供了 bExtractNativeLibs 的选项:
1 | bool bExtractNativeLibs = true; |
这个我根据大佬的文章来看,在UE5.3中是默认开启,于是我关闭后打包测试对比一下
1 | //DefaultEngine.ini |

前面是修改后,后面是默认开启
| arm64-v8a Shipping | 修改后 | 修改前 | 变化 |
|---|---|---|---|
| libEOSSDK.so | 21.12MB | 7.91MB | 13.21MB |
| libpsoservice.so0.2 | 0.23MB | 0.08MB | 0.15MB |
| libUnreal.so | 100.4MB | 39.39MB | 60.55MB |
代码体积优化
对于 UE 项目而言,优化 so 的大小有以下几种思路:
- 禁用不必要模块
- 控制代码优化(控制 inline/O3/0z)
- 禁用 Module 不必要异常处理
- 启用 LTO
- 剔除不需要的导出符号
禁用模块
可以把引擎中内置的明确不需要使用的模块在 target.cs 中关闭,具体有哪些可以修改的配置可以在”\Engine\Source\Programs\UnrealBuildTool\Configuration\TargetRules.cs”中查看:
1 | // disable modules |
按需求关闭,因为第三人称模版中不需要物理效果和导航,所以可以关闭。
关闭异常处理
有些模块中打开了 C++ 异常处理,但是没有 try/catch 的使用:
1 | bForceEnableExceptions = false;//默认关闭 |
修改编译器优化模式
在 target.cs 中控制 bCompileForSize 的值,可以选择使用 O3 或 Oz 编译代码:
1 | public enum OptimizationMode |
1 | //这里选Size,// Favor minimal code size |
启用 LTO
LTO 是 Link Time Optimization 的简称,可以在链接时剔除死代码、优化跨模块的函数调用、内联等。
受 bAllowLTCG 参数控制,选择是否添加 -flto=thin 的编译参数,thin 是缩减大小与优化耗时的综合版本。
1 | bAllowLTCG = true; // LTO |
剔除导出符号
在编译 so 时,除非特殊设置,所有的函数和变量都会被导出,用于被其他的 so 访问。
但在 UE 引擎内,只有极少数的接口,是明确被外部访问的(JNI 相关的接口),所以 libUnreal.so 的符号导出绝大部分是浪费的,剔除掉符号导出可以大幅降低 so 的大小和内存占用!
现代编译器提供了 version-script 的链接时控制机制,可以通过传入一个 ldscript 文件来控制链接时的符号行为。
需要在编译过程中先构造出一个 ldscript 文件,填入符号导出控制代码,然后在 target.cs 中,传递给 Linker:
1 | string VersionScriptFile = GetVersionScriptFilename(); |
对于 UE 而言,需要允许导出的只有 Java_*/ANativeActivity_onCreate/JNI_OnLoad 这三类匹配符号,,其余的均可剔除。

优化后(对比上一步)
| arm64-v8a Shipping | 修改后 | 修改前 | 变化 |
|---|---|---|---|
| libEOSSDK.so | 7.91MB | 0 | 7.91MB |
| libpsoservice.so0.2 | 0.23MB | 0.08MB | 0MB |
| libUnreal.so | 39.39MB | 33.04MB | 9.35MB |
内存收益
安卓可以通过 dumpsys meminfo 来查看整个包的 so 占用内存情况,包含了所有已加载的 so,但可以通过优化前后的差值得到实际的内存收益。
优化前:
1 | ** MEMINFO in pid 28879 [com.YourCompany.MinAPKTest] ** |
优化后:
1 | Uptime: 663111257 Realtime: 1598890414 |
| arm64-v8a Shipping | 优化前 | 优化后 | 减少 |
|---|---|---|---|
| so 总内存 | 110017 | 70732 | 39285 |
优化策略补充
重定位表压缩
SDK 28
在 Android 的 MinSDKVersion 大于等于 28 时(Android9),可以在编译和链接时开启 RELR 重定位表压缩。利用相对地址重定位的特点,对重定位信息进行高效编码,从而减少存储空间占用。
开启方法,需要在编译阶段给 Compiler 和 Linker 传递参数:
1 | AdditionalCompilerArguments += " -fPIC"; |
-Wl,--pack-dyn-relocs=android+relr,--use-android-relr-tags是 Android 特有的链接器选项,它们是对标准-Wl,-z,relro和-Wl,-z,now的补充和优化,特别是针对 Android 系统中动态链接和重定位的处理。 它们主要用于进一步减小二进制文件大小和改善加载时间。
优化前后:
可以使用安卓SDK的工具llvm-readelf.exe查看libUnreal.so
1 | \..\AppData\Local\Android\Sdk\ndk\25.1.8937393\toolchains\llvm\prebuilt\windows-x86_64\bin\llvm-readelf.exe -d "G:\YourProject\Binaries\Android\libUnreal.so" |

1 | 2.8 MB → 155 KB |
资源优化:
主流方法是把资源合理规划拆分,默认启动界面作为一个资源加载关卡,其他所有资源拆分成pak包,后续通过下载的方式动态挂载,后面有时间实现一下。
总结
这是我最后在.Target.cs的完整代码
1 | // Copyright Epic Games, Inc. All Rights Reserved. |