关注

IL2CPP 构建大小优化

问题

  • 应用大小超过 iOS 移动网络下载的限制会强制 iOS 用户通过 WiFi 下载。
  • 应用的通用安装包或者单独包均大于苹果提交指引的要求。

原因

我的应用大小超过了 iOS 移动网络下载的限制并且超过了 tvOS 主应用的大小。

otool的报告反馈单独的应用包(32bit, 64bit)或者是通用包都超过了苹果提交审核的要求。

解决方案

IL2CPP将C#的预编代码转换C++代码,这些代码将会被Xcode和LLVM转换成最终的执行代码,这会导致更大的通用包或者应用分片(32bit,64bit)尺寸。您可以通过几个方面来优化代码。阅读下面的信息将更有助您了解可以做哪些工作来优化构建包的大小:

为什么 IL2CPP 编译的包要比 mono 的大呢?
 
有两方面的原因:
  • 如果您使用的是通用构建,那将会有32bit分片和64bit分片两个部分,而这两个部分为两种不同的架构包含了两个完全一样的可执行文件,导致尺寸增大一倍。
  • 如果您对比过ARMv7 Mono 和ARMv7 IL2CPP的话,IL2CPP的版本会更大些;造成这样是因为要处理类型的元数据。这在Unity 4.6.4p2中已被修复。在4.7.0f1和5.4.0f1中都做了进一步的改进。

相比于只针对ARMv7或者ARM64架构,针对通用架构进行构建总会生成更大尺寸的包。移除托管组件集已经可以使构建更小(Unity 4.6.3f1), 但是真正的改进来自于我们在运行时中可以至少创建通用的typeinfo (generic typeinfo),不过这个功能目前在Unity 4.6.4p1中已经可以部分实现了。 相比于Mono 32bit,包含了32-bit分片和64-bit 分片的通用构建尺寸要大上一倍 。

 
在 Unity 4.6.4p1,我们改进了通用类型和数组类型。除此之外,这里有减小 iOS 包体大小的信息。额外的信息可以读下面的与Stripping相关的章节。
 

请在mono (ARMv7), IL2CPP (ARMv7), IL2CPP (ARM64), IL2CPP Universal之间做个对比表格,包括使用了哪些Stripping设置的信息!

 
关注当前二进制包的大小是很有必要的,因为如果这是一个通用构建,由于包含了ARMv7和ARM64的包,导致相比于单个ARMv7 mono或者IL2CPP的包要大上一倍。如果您想拿输出做直接对比,那么请拿 ARMv7 mono 和 ARMv7 IL2CPP做对比。
 

苹果会检查哪些大小的限制?


点击这里查阅查苹果应用商店的提交信息。
  • 对于MinimumOSVersion 小于0 的应用, 二进制包__TEXT 部分最大是80MB。
  • 对于MinimumOSVersion 是x 到8.x 的应用, 二进制包中每种架构__TEXT 部分最大是60MB。
  • 对于MinimumOSVersion 是x 或者更高的应用, Mach-O 二进制包中最大是400 MB。

如果您支持的iOS系统小于iOS 7.0, 苹果对于32bit+64bit代码部分一共有 80MB 的大小限制。对于iOS系统版本最小是7.0或者更高的,针对每个架构的分片有60MB(60 000 000 字节)的大小限制。100MB(移动数据)应用限制(用户需要通过 WiFi 下载)和最大 4GB 的应用限制。 80MB 和 60MB/60MB是针对包含在fat二进制里的32bit和64bit分片的大小限制。您需要用otool以获得准确的数字。.ipa 文件的大小限制在100MB,这个大小决定了用户是否需要通过 WiFi去下载您的应用。通过以下这些步骤以获得准确的数字:

  1. 在发布模式下构建您的应用。不要用测试模式,因为它无法代表您的最终安装包。确认您使用了正确的优化方法,可以点击此处了解更多信息:http://docs.unity3d.com/Manual/iphone-playerSizeOptimization.html

  2. 在 XCode 下打包您的应用,并用您的部署证书导出.ipa文件。

  3. 使用估算按钮来估算应用的大小,以确认应用小于100MB或者整体包的大小小于4GB。

    注意:从 Xcode 6.3起就没有估算按钮 了。您可以通过以下公式计算大小,但是要注意压缩系数可能会有所变化:
    app_store_size = sizeof(.ipa/(zip with exec_binary removed)) + sizeof(codesegment(exec_binary)) + 0.2 * sizeof(datasegment(exec_binary))

    该公式是基于以下的认知:只有代码部分(code segment)会被加密,数据部分(data segment)的压缩率可以很容易被确认 。

    使用dd命令从可执行包里面解压出数据 (你可以指定字节偏移和长度),然后再压缩这些数据。代码部分(code segment)被加密成完美的噪声一样。在执行之前你需要一个密钥才能反编译它。iTunes/App Store管理着这些密钥。这就是为什么我们被代码部分(code segment)看做一个整体,而没有将它包含在压缩率的计算里面。

  4. 当您创建好 .ipa包以后(包体的大小几乎等同于Xcode估计按钮返回的结果),打开终端,找到.ipa的 目录,然后运行otool。

  5. 使用otool将输出进行解压缩,然后查看是否已经接近80MB的大小限制 。

    otool -l <your_app_name>.app/<your_app_name> or size <your_app_name>.app/<your_app_name>

  6. 现在您可以根据不同的架构得到相应的输出(如果是通用架构,则是armv7 + arm64 两部分 ),为armv7 (LC_SEGMENT)收集信息,如果arm64 (LC_SEGMENT_64) 可用的话,你可以做同样的事情。


    1. 用segname __TEXT定位到LC_SEGMENT

      code segment size = 30474240 ~= 30MB
    2. 用segname __DATA定位到 LC_SEGMENT
      data segment size (mostly metadata) = 10420224 ~= 10 MB
    otool返回的结果如下:

    armv7架构:
    code segment size = 30474240 ~= 30MB
    data segment size (大部分是元数据) = 10420224 ~= 10 MB
    segment + data segment = 30 + 10 = 40MB

    arm64架构:
    code segment size = 25559040 ~= 26MB
    data segment size (大部分是元数据) = 17727488 ~= 18 MB
    segment + data segment = 26 + 18 = 44MB

    苹果使用这个例子中的otool报告(30MB + 26MB = 56MB)来做检测。上述报告在 <7.0的时候,小于80MB;在>=7.0的时候分别是30MB和26MB,两者都是小于60MB。

在这些检查的基础之上,就很容易在发布模式下打包一个测试应用,然后提交给iTunes Connect。在提交过程中,如果分片大小超过限制的话,静态项目检查器应该会提醒您。一旦您把应用提交到iTunes Connect,您就可以看到针对所有单个平台和通用平台苹果所期望的压缩后文件大小,下载文件大小和安装文件大小。可以看下面的例子:

 

使用 iOS 9.0的设备会根据需要只下载相应的部分(32或者64)。苹果会切割这些二进制包并会创建一个独立的包,所以像iPhone 6s这样的设备会得到一个更小的下载包,这个过程中你不需要做任何事情。这跟 ODR 或者 Bitcode 完全没有关系。但是使用 iOS 9.0以下的设备仍然需要下载通用包。

剥离
 
不管在Unity的Stripping Level 配置如何设置,IL2CPP的脚本后台还是会做字节代码的拆解。对于用户来说最好的选择就是把Stripping Level 选项设置为禁用,因为IL2CPP总是会做拆解,所以 拆解层级对于执行包的大小影响其实很有限。如果你打开了Stripping Level的设置而不是选择禁用,您可能会遇到一些问题,因为IL2CPP编译工具链会尝试去了解您的脚本集合使用的是哪些原生引擎代码,导致在应用启动时只注册相关的原生代码。如果您在使用拆解设置的时候遇到问题,并且觉得他们是有问题的,请提交bug报告给我们。

5.3.x由于 Bitcode 的原因构建的大小会变大

当您用 Unity 5.3.x 构建iOS应用的时候,得到的包体会变大。这是因为 5.3.x的 Bitcode 支持已经打开了。为什么这是件好事以及怎么处理可以看这里

通过 MapFileParser 分析脚本

您可以更深入地了解和查看脚本对包体大小的影响。您可以使用 MapFileParser 这个来检查可执行文件的尺寸以及脚本对其大小所做的贡献。这个工具会在Xcode打包应用的时候,处理Xcode生成的map文件(在使用Archive打包的时候不会生成map文件) 。在 5.2.4p1 及以下版本里,-stats 这个标识符是不存在的。您可以在 Unity.app 的安装目录下找到 MapFileParser 或者Unity生成的 Xcode 工程根目录中也可以找到。在 Xcode 链接输出的Derive Data文件夹中可以找到 your_map_file.map。

完整的路径类似于:Library/Developer/Xcode/DerivedData/Unity-iPhone-glmxdxebssyebsfcbtobeuasetge/Build/Intermediates/Unity-iPhone.build/<mode>-iphoneos/Unity-iPhone.build/alpin-LinkMap-normal-<architecture>.txt 

您可以在终端运行下面的命令来分析这些map文件。

<Contents/Tools/MapFileParser/>MapFileParser -format=Clang <your_map_file.map> -stats

分析这些函数的名字是区分不同代码最好的方法(用户脚本,静态库)。从托管集里面生成的代码会以 _m的后缀结尾。例如:

_TestScript_Start_m4164746442: 10 bytes
_TestScript_Update_m263972995: 10 bytes
_TestScript
_ctor_m922641354: 24 bytes

这会帮您更好的理解您的代码,插件或者引擎代码。

更多信息

本文适用于 Unity 5.2.0p1 及以上,Xcode7及以上,iOS9.0及以上版本

这篇文章有帮助吗?
1 人中有 1 人觉得有帮助
还有其它问题?提交请求

0 评论

登录写评论。