| <html devsite><head> |
| <title>减小 OTA 大小</title> |
| <meta name="project_path" value="/_project.yaml"/> |
| <meta name="book_path" value="/_book.yaml"/> |
| </head> |
| <body> |
| <!-- |
| Copyright 2017 The Android Open Source Project |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| --> |
| |
| <p>本页介绍了为减少多次编译之间不必要的文件变更而对 AOSP 编译系统所做的改动。使用专有编译系统的设备实现人员可根据这项信息采取措施,减小无线下载 (OTA) 更新的大小。 |
| </p> |
| |
| <p>有时,Android OTA 包含的变更文件并非源于代码变更,而是编译系统造成的。在不同时间、不同目录或不同机器上编译相同的代码时可能会发生上述情况,产生大量变更文件。这些多余的文件不仅会增加 OTA 的大小,还会导致难以确定 OTA 中发生变更的代码。</p> |
| |
| <p>为了使 OTA 的内容更加透明,我们对 AOSP 编译系统做了多项改动,目的是消除多次编译之间不必要的文件变更,以此减小 OTA 的大小。这样做是为了减小 OTA 的大小,使其只包含与 OTA 中所含补丁程序相关的文件。AOSP 还包括<a href="#the_build_diff_tool">编译 diff 工具</a>(可过滤出常见的编译相关文件变更,并提供更清晰的编译文件 diff)以及<a href="#block-mapping-tool">块映射工具</a>(可协助您确保块分配的一致性)。</p> |
| |
| <p>编译系统可能会通过多种方式创建不必要的文件 diff。下文讨论了其中一些问题和解决方案,并尽可能提供了 AOSP 中的修复示例。</p> |
| |
| <h2 id="file_order">文件顺序</h2> |
| |
| <p><strong>问题</strong>:文件系统在请求目录中的文件列表时,并不保证文件顺序,尽管对于同一个检出,文件顺序通常是相同的。<code>ls</code> 等工具在默认情况下会对结果进行排序,但 <code>find</code> 和 <code>make</code> 等命令使用的通配符函数却不会对结果进行排序。用户在使用这类工具之前,务必要对输出进行排序。 |
| </p> |
| |
| <p><strong>解决方案</strong>:用户在使用支持通配符的 <code>find</code> 和 <code>make</code> 等工具之前,务必要对这些命令的输出进行排序。要在 <code>Android.mk</code> 文件中使用 <code>$(wildcard)</code> 或 <code>$(shell find)</code>,也应该进行排序。有些工具(如 Java)确实会对输入进行排序,因此有必要先对排序进行验证。</p> |
| |
| <p><strong>示例</strong>:多处问题在核心编译系统中通过内置的 <code>all-*-files-under</code> 宏得到修正,其中包括 <code>all-cpp-files-under</code>(一些定义分散在其他 makefile 中)。有关详情,请参阅以下 CL:</p> |
| |
| <ul> |
| <li><a href="https://android.googlesource.com/platform/build/+/4d66adfd0e6d599d8502007e4ea9aaf82e95569f" class="external">https://android.googlesource.com/platform/build/+/4d66adfd0e6d599d8502007e4ea9aaf82e95569f</a> |
| </li><li><a href="https://android.googlesource.com/platform/build/+/379f9f9cec4fe1c66b6d60a6c19fecb81b9eb410" class="external">https://android.googlesource.com/platform/build/+/379f9f9cec4fe1c66b6d60a6c19fecb81b9eb410</a> |
| </li><li><a href="https://android.googlesource.com/platform/build/+/7c3e3f8314eec2c053012dd97d2ae649ebeb5653" class="external">https://android.googlesource.com/platform/build/+/7c3e3f8314eec2c053012dd97d2ae649ebeb5653</a> |
| </li><li><a href="https://android.googlesource.com/platform/build/+/5c64b4e81c1331cab56d8a8c201f26bb263b630c" class="external">https://android.googlesource.com/platform/build/+/5c64b4e81c1331cab56d8a8c201f26bb263b630c</a> |
| </li></ul> |
| |
| <h2 id="build_directory">编译目录</h2> |
| |
| <p><strong>问题</strong>:变更编译内容所在的目录会导致二进制文件有所不同。Android 编译系统中的大多数路径都是相对路径,因此 C/C++ 中的 <code>__FILE__</code> 不是问题。不过,默认情况下调试符号会对完整的路径名进行编码,而对预剥离二进制文件进行哈希处理会生成 <code>.note.gnu.build-id</code>,因此调试符号发生变更会致使二进制文件发生变化。</p> |
| |
| <p><strong>解决方案</strong>:AOSP 现在会使调试路径变成相对路径。有关详情,请参阅 CL:<a href="https://android.googlesource.com/platform/build/+/6a66a887baadc9eb3d0d60e26f748b8453e27a02" class="external">https://android.googlesource.com/platform/build/+/6a66a887baadc9eb3d0d60e26f748b8453e27a02</a>。 |
| </p> |
| |
| <h2 id="timestamps">时间戳</h2> |
| |
| <p><strong>问题</strong>:编译输出中的时间戳会导致不必要的文件变更。这可能会发生在以下位置:</p> |
| |
| <ul> |
| <li>C 或 C++ 代码中的 <code>__DATE__/__TIME__/__TIMESTAMP__</code> 宏。</li> |
| <li>基于 ZIP 的归档中嵌入的时间戳。</li> |
| </ul> |
| |
| <p><strong>解决方案/示例</strong>:要从编译输出中移除时间戳,请遵循下文中的说明操作。</p> |
| |
| <h3 id="date_time_timestamp_in_c_c">C/C++ 中的 __DATE__/__TIME__/__TIMESTAMP__</h3> |
| |
| <p>这些宏总是为不同的编译生成不同的输出,因此不建议使用。您可以选择通过以下方法来移除这些宏:</p> |
| |
| <ul> |
| <li>直接将其移除(这些宏通常不是必需的)。要查看示例,请参阅:<a href="https://android.googlesource.com/platform/system/core/+/30622bbb209db187f6851e4cf0cdaa147c2fca9f" class="external">https://android.googlesource.com/platform/system/core/+/30622bbb209db187f6851e4cf0cdaa147c2fca9f</a>。</li> |
| <li>要对运行中的二进制文件进行唯一标识,请从 ELF 标头中读取 build-id。</li> |
| <li>要了解操作系统的编译时间,请读取 <code>ro.build.date</code>(应该会对除增量编译之外的所有内容都适用;增量编译可能不会更新此日期)。要查看示例,请参阅:<a href="https://android.googlesource.com/platform/external/libchrome/+/8b7977eccc94f6b3a3896cd13b4aeacbfa1e0f84" class="external">https://android.googlesource.com/platform/external/libchrome/+/8b7977eccc94f6b3a3896cd13b4aeacbfa1e0f84</a>。</li> |
| </ul> |
| |
| <aside class="note"><strong>注意</strong>:Android 7.0 开启了 <code>-Werror=date-time</code>,因此使用时间戳会导致编译错误。</aside> |
| |
| <h3 id="embedded_timestamps_in_zip-based_archives_zip_jar">归档文件(zip、jar)中的嵌入时间戳</h3> |
| |
| <p>Android 7.0 通过将 <code>-X</code> 添加到 <code>zip</code> 命令的所有用例中,解决了 zip 归档文件中嵌入时间戳的问题,因此编译工具的 UID/GID 和扩展的 Unix 时间戳不会嵌入到 ZIP 文件中。</p> |
| |
| <p>新工具 <code>ziptime</code>(位于 <code><a href="https://android.googlesource.com/platform/build/+/master/tools/ziptime/" class="external">/platform/build/+/master/tools/ziptime/</a></code> 下)会重置 zip 标头中的正常时间戳。有关详情,请参阅 <a href="https://android.googlesource.com/platform/build/+/master/tools/ziptime/README.txt" class="external">README 文件</a>。</p> |
| |
| <p><code>signapk</code> 工具为 APK 文件设置的时间戳可能会因服务器所在的时区而异。有关详情,请参阅 CL:<a href="https://android.googlesource.com/platform/build/+/6c41036bcf35fe39162b50d27533f0f3bfab3028" class="external">https://android.googlesource.com/platform/build/+/6c41036bcf35fe39162b50d27533f0f3bfab3028</a>。 |
| </p> |
| |
| <h2 id="version_strings">版本字符串</h2> |
| |
| <p><strong>问题</strong>:APK 版本字符串的硬编码版本通常附加了 <code>BUILD_NUMBER</code>。即使 APK 中并未发生任何其他变更,APK 也仍然会有所不同。</p> |
| |
| <p><strong>解决方案</strong>:从 APK 版本字符串中移除版本号。</p> |
| |
| <p><strong>示例:</strong></p> |
| |
| <ul> |
| <li><a href="https://android.googlesource.com/platform/packages/apps/Camera2/+/5e0f4cf699a4c7c95e2c38ae3babe6f20c258d27" class="external">https://android.googlesource.com/platform/packages/apps/Camera2/+/5e0f4cf699a4c7c95e2c38ae3babe6f20c258d27</a></li> |
| <li><a href="https://android.googlesource.com/platform/build/+/d75d893da8f97a5c7781142aaa7a16cf1dbb669c" class="external">https://android.googlesource.com/platform/build/+/d75d893da8f97a5c7781142aaa7a16cf1dbb669c</a></li> |
| </ul> |
| |
| <h2 id="consistent_build_tools">一致的编译工具</h2> |
| |
| <p><strong>问题</strong>:生成安装文件的工具必须一致(相同的输入应始终生成相同的输出)。</p> |
| |
| <p><strong>解决方案/示例</strong>:以下编译工具需要进行变更:</p> |
| |
| <ul> |
| <li><strong>NOTICE 文件创建工具</strong>。NOTICE 文件创建工具需要变更。请参阅 CL:<a href="https://android.googlesource.com/platform/build/+/8ae4984c2c8009e7a08e2a76b1762c2837ad4f64" class="external">https://android.googlesource.com/platform/build/+/8ae4984c2c8009e7a08e2a76b1762c2837ad4f64</a>。 |
| </li> |
| <li><strong>Java Android 编译器套件 (Jack)</strong>。Jack 工具链需要更新才能处理生成的构造函数排序的偶然性变更。请参阅 CL:<a href="https://android.googlesource.com/toolchain/jack/+/056a5425b3ef57935206c19ecb198a89221ca64b" class="external">https://android.googlesource.com/toolchain/jack/+/056a5425b3ef57935206c19ecb198a89221ca64b</a>。 |
| </li> |
| <li><strong>ART AOT 编译器 (dex2oat)</strong>。ART 编译器二进制文件需要更新才能创建确定性映像。请参阅 CL:<a href="https://android.googlesource.com/platform/art/+/ace0dc1dd5480ad458e622085e51583653853fb9" class="external">https://android.googlesource.com/platform/art/+/ace0dc1dd5480ad458e622085e51583653853fb9</a>。 |
| </li> |
| <li><strong>libpac.so 文件 (V8)</strong>。每项编译会创建不同的 <code>/system/lib/libpac.so</code> 文件,因为 V8 快照会针对每项编译发生变更。解决方案是移除该快照。请参阅 CL:<a href="https://android.googlesource.com/platform/external/v8/+/e537f38c36600fd0f3026adba6b3f4cbcee1fb29" class="external">https://android.googlesource.com/platform/external/v8/+/e537f38c36600fd0f3026adba6b3f4cbcee1fb29</a>。 |
| </li> |
| <li><strong>预先经过 dexopt 处理的 (.odex) 应用文件</strong>。预先经过 dexopt 处理的 (.odex) 文件在 64 位系统上包含未初始化填充。请参阅 CL:<a href="https://android.googlesource.com/platform/art/+/34ed3afc41820c72a3c0ab9770be66b6668aa029" class="external">https://android.googlesource.com/platform/art/+/34ed3afc41820c72a3c0ab9770be66b6668aa029</a>。 |
| </li> |
| </ul> |
| |
| <h2 id="the_build_diff_tool">使用编译 diff 工具</h2> |
| |
| <p>对于无法消除编译相关文件变更的情况,AOSP 纳入了编译 diff 工具 <code><a href="https://android.googlesource.com/platform/build/+/master/tools/releasetools/target_files_diff.py" class="external">target_files_diff.py</a></code>,以用于比较两个文件包。该工具会在两个编译之间执行递归 diff,从而排除常见的编译相关文件变更,例如:</p> |
| |
| <ul> |
| <li>编译输出中的预期变更(例如,由于版本号变更所导致)。</li> |
| <li>由于当前编译系统中的已知问题所导致的变更。</li> |
| </ul> |
| |
| <p>要使用编译 diff 工具,请运行以下命令:</p> |
| |
| <pre class="devsite-terminal devsite-click-to-copy"> |
| target_files_diff.py dir1 dir2 |
| </pre> |
| |
| <p><code>dir1</code> 和 <code>dir2</code> 是包含每个编译的提取目标文件的基础目录。</p> |
| |
| <h2 id="block-mapping-tool">使块分配保持一致</h2> |
| <p>在非 A/B OTA 中,影响时间的因素之一是块移动。对于给定的文件,尽管其内容在两个编译之间会保持不变,但实际持有数据的块可能已发生变化。因此,更新程序会在 OTA 期间执行不必要的 I/O 来四处移动块。</p> |
| |
| <p>为了解决这个问题,我们在 Android 7.0 中扩展了 <code>make_ext4fs</code> 工具,该工具会尝试使块分配在各编译之间保持一致。<code>make_ext4fs</code> 会接受可选的 <code>-d base_fs</code> 标记,该标记会在生成 <code>ext4</code> 映像时尝试将文件分配给相同的块。您可以从上一个编译的目标文件 zip 文件(<code>IMAGES/system.map</code> 和 <code>IMAGES/vendor.map</code>)中提取块映射文件(即 <code>base_fs</code> 映射文件)。接下来,<code>base_fs</code> 文件便可以通过 <code>PRODUCT_SYSTEM_BASE_FS_PATH</code> 和 <code>PRODUCT_VENDOR_BASE_FS_PATH</code> 进行记录并指定。例如,</p> |
| |
| <pre class="devsite-click-to-copy"> |
| PRODUCT_SYSTEM_BASE_FS_PATH := path/to/base_fs_files/base_system.map |
| PRODUCT_VENDOR_BASE_FS_PATH := path/to/base_fs_files/base_vendor.map |
| </pre> |
| |
| <p>虽然这对减小整体的 OTA 更新包大小来说并无帮助,但它确实可以通过减少 I/O 量来改善 OTA 性能。</p> |
| |
| </body></html> |