OTA サイズを縮小する

このページでは、ビルド間の不要なファイルの変更を減らすために AOSP に追加された変更について説明します。独自のビルドシステムを管理しているデバイスの実装者は、OTA アップデートのサイズを減らすためのガイドとしてこの情報を使用できます。

Android OTA アップデートには、コードの変更とは関係なく変更されたファイルが含まれることがあります。実は、これらはビルドシステムのアーティファクトで、同じコードが異なる時間に異なるディレクトリやマシン上でビルドされ、変更ファイルが多数生成された場合に発生します。このような余計なファイルがあると OTA パッチのサイズが増大し、変更されたコードを特定するのが困難になります。

OTA の内容の透明性を高めるため、AOSP には、OTA パッチのサイズを減らすように設計されたビルドシステムの変更が含まれています。ビルド間の不要なファイルの変更が排除され、パッチに関連したファイルのみが OTA アップデートに含まれます。AOSP には、共通のビルド関連ファイルの変更を抽出し、ビルドファイルの差分を作成するビルド差分ツールとブロックの割り当ての一貫性を維持するブロック マッピング ツールもあります。

ビルドシステムでは、方法によっては不必要に大きなパッチが作成されることがあります。これを軽減するため、Android 8.0 以降では、各ファイルの差分のパッチサイズを減らす新機能を実装しました。OTA アップデート パッケージのサイズを縮小した改善点は次のとおりです。

  • Brotli(汎用の可逆圧縮アルゴリズム)の使用。非 A/B デバイスのアップデートでフルイメージに使用しています。圧縮を最適化するために Brotli をカスタマイズできます。system.img などのファイル システム内の複数のブロックで構成される大規模なアップデートでは、デバイス メーカーやパートナーは独自の圧縮アルゴリズムを追加し、同じアップデートの異なるブロックにそれぞれ異なる圧縮アルゴリズムを使用できます。
  • Puffin による再圧縮の使用。これはデフレート ストリームのパッチツールで、決定性アルゴリズムを使用し、A/B OTA アップデートの生成において圧縮と差分関数を処理します。
  • デルタ生成ツールの使用方法(パッチの圧縮に bsdiff ライブラリを使用する方法など)の変更。Android 9 以降では、bsdiff ツールは、パッチに最適な圧縮結果をもたらす圧縮アルゴリズムを選択します。
  • update_engine の改善により、A/B デバイスのアップデートにパッチを適用したときのメモリ使用量が減少しました。
  • ブロックベースの OTA アップデートで、大きな zip ファイルの分割を改善。imgdiff のモードの 1 つは、サイズが大きすぎる APK ファイルをエントリ名に基づいて分割します。この分割方法のほうが、ファイルを線形分割し、bsdiff ツールを使用して圧縮する方法よりパッチが小さくなります。

以降のセクションでは、OTA アップデートのサイズに影響するさまざまな問題、ソリューション、AOSP での実装例について説明します。

ファイルの順序

問題: ディレクトリ内のファイルの一覧表示を求めたときに、ファイルシステムはファイルの表示順序を保証しません(ただし、同じチェックアウトを利用すれば通常は同じです)。ls などのツールはデフォルトで結果を並べ替えますが、findmake などのコマンドで使用するワイルドカード機能は並べ替えをしません。これらのツールを使用する前に、出力を並べ替える必要があります。

解決策: findmake などのツールでワイルドカード機能を使用する場合は、ツールを使用する前に出力を並べ替えます。Android.mk ファイル内で $(wildcard) または $(shell find) を使用する場合も同じように並べ替えてください。入力を並べ替えてくれるツールもあるので(Java など)、並べ替えが行われていないかどうかを先に確認してから並べ替えを行いましょう。

例: コア ビルドシステムで、組み込みマクロ all-*-files-under を使用して多数のインスタンスが修正されました。これには all-cpp-files-under も含まれていました(他の makefile に定義が分散していたため)。詳細については、以下を参照してください。

ビルド ディレクトリ

問題: ビルドが行われるディレクトリを変更すると、バイナリが異なってしまうことがあります。Android ビルドのパスのほとんどは相対パスのため、C / C++ の __FILE__ では問題ありません。しかし、デバッグ シンボルはデフォルトでフルパス名をエンコードしています。.note.gnu.build-id はバイナリをストリップして(デバッグ情報を削除して)ハッシュ化してから生成されるため、デバッグ シンボルが変更されると違うものになります。

解決策: AOSP は相対パスでデバッグを行うようになりました。詳しくは、CL の https://android.googlesource.com/platform/build/+/6a66a887baadc9eb3d0d60e26f748b8453e27a02 をご覧ください。

タイムスタンプ

問題: ビルド出力にタイムスタンプを使用すると、不要なファイル変更が生じます。これは、次の場所で発生する可能性があります。

  • C または C++ コードの __DATE__/__TIME__/__TIMESTAMP__ マクロ。
  • zip ベースのアーカイブに埋め込まれたタイムスタンプ。

解決策 / 例: ビルド出力からタイムスタンプを削除するには、以下の C / C++ の __DATE__ / __TIME__ / __TIMESTAMP__ およびアーカイブ内の埋め込みタイムスタンプの手順を使用してください。

C / C++ の __DATE__ / __TIME__ / __TIMESTAMP__

これらのマクロは、ビルドごとに異なる出力を生成するため、使用しないでください。マクロを排除する方法は次のとおりです。

アーカイブ内の埋め込みタイムスタンプ(zip、jar)

Android 7.0 では、zip アーカイブ内の埋め込みタイムスタンプの問題を解決するため、zip コマンドが使われるすべての場所に -X を追加しました。この方法で zip ファイルからビルダーの UID / GID と拡張 Unix タイムスタンプが削除されました。

新しいツール ziptime/platform/build/+/main/tools/ziptime/ にあり、zip ヘッダー内の通常のタイムスタンプをリセットします。詳しくは、README ファイルをご覧ください。

signapk ツールは APK ファイルのタイムスタンプを設定しますが、サーバーのタイムゾーンによってはタイムスタンプが異なる可能性があります。詳細については、CL の https://android.googlesource.com/platform/build/+/6c41036bcf35fe39162b50d27533f0f3bfab3028 をご覧ください。

バージョン文字列

問題: APK バージョンの文字列には、ハードコーディングされたバージョンに BUILD_NUMBER が追加されていることがあります。そのため、APK 内で他に何も変更されていない場合でも違う APK になってしまいます。

解決策: APK バージョン文字列からビルド番号を削除します。

例:

デバイスでの信頼性計算を有効にする

デバイスで dm-verity が有効になっている場合、OTA ツールは信頼性構成を自動的に選択し、デバイスでの信頼性計算を有効にします。それにより、信頼性ブロックを OTA パッケージに未加工のバイト列として保存せずに、Android デバイスで計算できます。信頼性ブロックは、2 GB のパーティションに対して約 16 MB 使用することがあります。

ただし、デバイスでの信頼性の計算には時間がかかる場合があります。特に、前方誤り訂正コードに時間がかかることがあります。Pixel デバイスでは 10 分程度かかることが多くなります。ローエンドのデバイスではさらに時間がかかることがあります。dm-verity を有効にしたままデバイスでの信頼性計算を無効にするには、OTA アップデートの生成時に --disable_fec_computationota_from_target_files ツールに渡します。このフラグにより、OTA アップデート中にデバイスでの信頼性計算が無効になります。それによって OTA のインストール時間が短縮されますが、OTA パッケージのサイズは増えます。デバイスで dm-verity が有効になっていない場合は、このフラグを渡しても効果はありません。

一貫したビルドツール

問題: インストールするファイルの生成ツールには一貫性がなくてはなりません。所定の入力に対しては常に同じ出力を生成する必要があります。

解決策 / 例: 次のビルドツールの変更が必要でした。

ビルド差分ツールを使用する

ビルドに関連したファイルの変更を排除できないケースに対応するために、AOSP には 2 つのファイル パッケージを比較するビルド差分ツール target_files_diff.py が含まれています。このツールは、2 つのビルド間の再帰的な差分を実行しますが、次のようなビルド関連ファイルによくある変更は差分の対象から除外されています。

  • ビルド出力に当然生じる変更(ビルド番号の変更など)
  • 現行ビルドシステムでの既知の問題による変更。

ビルド差分ツールを使用するには、次のコマンドを実行します。

target_files_diff.py dir1 dir2

dir1dir2 はビルドごとに抽出されたターゲット ファイルを格納するベース ディレクトリです。

ブロック割り当ての一貫性を維持する

あるファイルの内容が 2 つのビルド間で同じであっても、そのデータを格納する実際のブロックが変更されている可能性があります。そのため、アップデータは不要な I/O を実行して OTA アップデートのためにブロックを移動しなければなりません。

仮想 A/B OTA アップデートでは、書き込み時のスナップショットのスナップショットを保存するのに必要なストレージ スペースを大幅に増やすことができます。非 A/B OTA アップデートでは、OTA アップデートのためにブロックを移動すると、ブロックの移動による I/O が増えているため、更新時間が長くなります。

この問題に対処するため、Android 7.0 では make_ext4fs ツールを拡張し、ブロック割り当てをビルド間で一貫するようにしました。make_ext4fs ツールにオプションの -d base_fs フラグを指定すると、ext4 イメージの生成時にこのツールは同じブロックへのファイル割り当てを試みます。以前のビルドのターゲット ファイルの zip ファイルからブロック マッピング ファイル(base_fs マップファイルなど)を抽出できます。各 ext4 パーティションには、IMAGES ディレクトリに .map ファイルがあります(たとえば、IMAGES/system.mapsystem パーティションに対応します)。それらの base_fs ファイルは、次の例のように、PRODUCT_<partition>_BASE_FS_PATH でチェックインおよび指定できます。

  PRODUCT_SYSTEM_BASE_FS_PATH := path/to/base_fs_files/base_system.map
  PRODUCT_SYSTEM_EXT_BASE_FS_PATH := path/to/base_fs_files/base_system_ext.map
  PRODUCT_VENDOR_BASE_FS_PATH := path/to/base_fs_files/base_vendor.map
  PRODUCT_PRODUCT_BASE_FS_PATH := path/to/base_fs_files/base_product.map
  PRODUCT_ODM_BASE_FS_PATH := path/to/base_fs_files/base_odm.map

これは、OTA パッケージ全体のサイズを縮小するのには役立ちませんが、I/O の量を減らすことで OTA アップデートのパフォーマンスを向上させます。仮想 A/B アップデートでは、OTA を適用するのに必要な保存容量が大幅に削減されます。

アプリの更新の回避

ビルド差分を最小限に抑えるほか、アプリストアで更新されるアプリのアップデートを OTA アップデートから除外することで、OTA アップデートのサイズを縮小できます。APK にデバイスの各種パーティションのかなりの部分が含まれることがよくあります。アプリストアで更新されるアプリの最新バージョンを OTA アップデートに含めると、OTA パッケージのサイズに大きな影響を与え、ユーザーの利点も制限される可能性があります。ユーザーが OTA パッケージを受け取るころには、アップデート済みのアプリか、より新しいバージョンをアプリストアから直接受け取れる可能性もあります。