blob: 41c22c20830f46606ee8bf1a3b223e5cabdcd92f [file] [log] [blame]
<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>