アプリサイズ最適化とApp Thinning
ダウンロードサイズが数MB増えるだけでインストール完了率は落ちる。App ThinningとApp Bundle、R8による縮小の原理を押さえれば、端末に届く実サイズを的確に削れる。
- 配布物のサイズと端末に届くサイズは別物で、iOSのApp Thinningも AndroidのApp Bundleも「全部入りバイナリを配り、端末ごとに必要な断片だけ転送する」点が本質。
- AndroidはR8が到達不能コードの削除(tree shaking)・難読化・最適化を一括で行い、リソース縮小はコード縮小後に未参照リソースを削って初めて効く順序依存がある。
- 動的機能配信(Dynamic Feature/On-Demand Resources)は初回インストールに含めない部分をオンデマンドに切り出す仕組みで、初期ダウンロードサイズの上限規制を回避する主要手段になる。
配布サイズと配信サイズを分けて考える
アプリサイズ最適化でまず揃えるべきは、「開発者がストアに上げるバイナリのサイズ」と「利用者の端末に実際にダウンロードされインストールされるサイズ」がまったくの別物だ、という前提です。開発者は全機種・全解像度・全ABI(CPUアーキテクチャ)・全言語に対応する一枚岩の配布物をアップロードしますが、任意の一台の端末が必要とするのはそのごく一部です。iOSのApp ThinningもAndroidのApp Bundleも、この差分を埋めるために「全部入りを受け取り、端末ごとに必要な断片だけを組み立てて転送する」という同じ発想で作られています。この分離を理解しないと、配布物サイズを削る努力が配信サイズに反映されず徒労に終わります。
ここで注意すべき指標が2つあります。ダウンロードサイズ(通信で転送される圧縮済みの量)とインストールサイズ(展開後にストレージを占有する量)です。両ストアともダウンロードサイズに上限規制を設けており、これを超えるとモバイル回線での自動ダウンロードが制限されるなど配布上の実害が出ます。最適化の第一目標は多くの場合このダウンロードサイズです。
iOS: App Thinningの3つの仕組み
App Thinningは単一機能ではなく、3つの技術の総称です。
横にスクロール
| 技術 | 削る対象 | 実行タイミング |
|---|---|---|
| App Slicing | 対象デバイスに不要な解像度画像・アーキテクチャ・アセット | App StoreがデバイスのGPU・画面・OSに合わせバリアントを生成・配信 |
| On-Demand Resources (ODR) | 初回起動に不要なリソース(後半ステージ、チュートリアル等) | アプリ本体とは別にAppleのCDNへ置き、必要になった時点で取得 |
| Bitcode(歴史的機能) | 中間表現からの再最適化余地 | かつてApple側で再コンパイル。現在は非推奨・廃止方向 |
中核はApp Slicingです。開発者は.xcarchiveを1つアップロードしますが、App Storeはそこから各デバイスクラスに対応したApp Variantを自動生成します。Retina解像度の異なる端末には該当する@2x/@3x画像だけを、アーキテクチャの異なる端末には該当スライスだけを含む縮小版を配信します。これによりある一台がダウンロードするのは、全解像度・全アーキテクチャを含む元アーカイブより大幅に小さいバリアントになります。開発者側の要件は、アセットをAsset Catalog(.xcassets)に入れておくことです。ファイルシステムに直接置いた画像はスライシングの対象にならず、全端末に一律配信されてしまうためです。
On-Demand Resourcesは、初回起動に不要なリソースを本体から切り離す仕組みです。リソースにタグを付けておくと、それらは本体バイナリではなくAppleのサーバーに格納され、アプリがNSBundleResourceRequestで明示的に要求した時点でダウンロードされます。ゲームの後半ステージや初回チュートリアル動画のように「いつか必要だが最初は不要」なアセットを初期ダウンロードサイズから外せます。
Bitcodeは、アプリをLLVMの中間表現としてアップロードし、Apple側で将来のCPU向けに再最適化できるようにする仕組みでしたが、現在は非推奨となり新規提出では実質的に利用できません。App Slicingはビットコードの有無と独立して機能します。「App Thinning=Bitcode」という古い理解は誤りで、いま効くのはSlicingとODRだと切り分けてください。
Android: App Bundle(AAB)とsplit APK
Androidの配信サイズ削減の土台はAAB(Android App Bundle)です。開発者はAABという中間形式をアップロードし、Google Playが端末ごとに最適化したsplit APK群を動的に生成・署名して配信します。この最終署名をGoogleが担う点は/mobile-development/mobile-app-signing-provisioning/で扱ったPlay App Signingと同じ基盤の上に成り立っています。
split APKは役割ごとに分割されます。
| 分割APK | 含むもの | 端末ごとの選択基準 |
|---|---|---|
| base APK | 全端末共通のコード・リソース | 常に配信される土台 |
| density split | 画面密度別のリソース(drawable-hdpi/xxxhdpi等) | 端末の画面密度に一致する1種のみ |
| ABI split | ネイティブ.soライブラリ(arm64-v8a等) | 端末CPUに一致するABIのみ |
| language split | 言語別の文字列リソース | 端末の設定言語に一致する分のみ |
たとえば全ABI・全密度・全言語を含んだ一枚岩APKが仮に60MBでも、arm64-v8a・xxhdpi・日本語だけの端末が受け取るのはbase+該当split群に絞られ、実配信は大幅に縮みます。旧来のuniversal APK(全部入りを固定配布する方式)と比べた優位はここにあり、開発者は分割ロジックを自前で書く必要がありません。
R8による縮小・難読化・最適化
配信の分割とは別に、コード自体を小さくするのがR8です。R8は旧来のProGuard(縮小・難読化)とD8(DEX変換)を統合した現行のツールチェーンで、リリースビルド時に次の3つを一括で行います。
| 処理 | 内容 | サイズへの効き方 |
|---|---|---|
| Shrinking(縮小) | エントリポイントから到達不能なクラス・メソッド・フィールドを削除(tree shaking) | 未使用コードの物理的削除で直接減量 |
| Obfuscation(難読化) | クラス名・メソッド名を短い記号(a, b, c…)へリネーム | 識別子文字列の短縮でDEXが縮む・解析耐性も向上 |
| Optimization(最適化) | インライン化・未到達分岐の除去・定数畳み込みなど | 実行コードの削減と高速化 |
縮小の原理は到達可能性解析です。R8はマニフェストのエントリポイントやkeep指定を根(ルート)として、そこから参照が辿れるコードだけを生存とみなし、辿れないものを削除します。ここで問題になるのがリフレクションです。Class.forNameや文字列からのメソッド呼び出しは静的解析で参照を辿れないため、R8はそのクラスを未使用と誤判定して削り、実行時にClassNotFoundExceptionを招きます。これを防ぐのがproguard-rules.proの-keep規則で、削ってはならないクラスを明示的にルートへ加えます。
難読化を有効にすると本番クラッシュのスタックトレースがa.b.cのような記号だらけになり、そのままでは原因箇所を特定できません。R8はビルド時に元の名前との対応をmapping.txtとして出力するので、これを保管しておき、難読化済みトレースを元のシンボルへ復元(deobfuscate/retrace)できるようにしておくことが必須です。mapping.txtを失うと本番クラッシュの解析手段を失います。
リソース縮小の順序依存
コードだけでなく、未参照のリソース(未使用のレイアウト・画像・文字列)も削れます。Android Gradle PluginのshrinkResourcesがこれを担いますが、重要なのはコード縮小の後に走るという順序です。理由は明快で、あるコードが削除されればそのコードだけが参照していた画像も未参照になるため、コード側のtree shakingを先に確定させないと、どのリソースが本当に不要かを判定できないからです。したがってリソース縮小はコード縮小(minifyEnabled)とセットで有効にして初めて意味を持ちます。ただしリソースもResources.getIdentifierで名前から動的参照されると静的に辿れず、keep.xmlでの保護が要る点はコード側と同型の注意です。
動的機能配信(Dynamic Feature Delivery)
初期ダウンロードサイズをさらに下げる上位の手段が、機能そのものを初回インストールから外す動的配信です。
| 方式 | プラットフォーム | 初回に含めるか |
|---|---|---|
| Dynamic Feature Module | Android | 含めず、Play Feature Deliveryで必要時にダウンロード(on-demand)またはインストール時条件配信 |
| On-Demand Resources | iOS | 初回リソースのみ含め、残りはタグ付けしてApple CDNから随時取得 |
| App Clip / Instant App | 両OS | 本体とは別の超軽量バイナリだけを先に届ける |
AndroidのDynamic Feature Moduleは、特定機能のコードとリソースを別モジュールに切り出し、Play Core(Feature Delivery)API経由でユーザーがその機能に到達した時点でダウンロード・インストールします。稀にしか使わない大型機能(動画エディタ、AR機能など)を初期配信から外せるため、ダウンロードサイズ上限に対する主要な逃げ道になります。この「必要な断片を後から取り寄せる」発想を配布形態ごと極端に推し進めたのが、本体を待たずに起動する/mobile-development/mobile-app-clip-instant-apps/です。
配布サイズと配信サイズの分離が大前提です。iOSはApp Slicing(デバイス別バリアント)が中核でBitcodeは過去の技術、AndroidはAABからのsplit APK(密度・ABI・言語)で配信を絞る、と対比で覚えます。R8はshrink(到達不能コード削除)・obfuscate(リネーム)・optimizeの3役で、リフレクションは-keepで保護、mapping.txtでretraceという流れは頻出。リソース縮小はコード縮小の後段に走る順序依存が要点で、動的機能配信は初回インストールから機能を外す上限回避策だと説明できるようにします。
まとめ
アプリサイズ最適化の骨格は「一枚岩を配って端末ごとに削る」という配信分離にあります。iOSはApp Slicingがデバイスに合ったバリアントを自動生成し、ODRが初回不要リソースを切り離します。AndroidはAABを土台にsplit APKで密度・ABI・言語を端末ごとに絞り込み、R8が到達不能コードの削除と難読化・最適化でコード自体を縮めます。リソース縮小がコード縮小の後段でしか正しく効かない順序依存、リフレクションが静的解析を破るため-keepとmapping.txtの管理が要る点は、原理から導かれる実務の勘どころです。これらは起動処理量を減らす副次効果も持ちますが、体感速度そのものの改善は/mobile-development/mobile-app-startup-optimization/が扱う別軸の最適化であり、バイナリサイズ削減とは目的も測定指標も分けて考えるのが正しい整理です。
モバイル開発の記事ガイド
アプリサイズ最適化とApp Thinningを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
モバイル開発
比較で見る軸
難易度: advanced / カテゴリ: モバイル開発 / タグ数: 6
導入後に効く点
AndroidはR8が到達不能コードの削除(tree shaking)・難読化・最適化を一括で行い、リソース縮小はコード縮小後に未参照リソースを削って初めて効く順序依存がある。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- モバイル開発
- タグ数
- 6
判断チェックリスト
- 自社の用途が「モバイル開発 / iOS」に近いか確認する。
- 強みである「配布物のサイズと端末に届くサイズは別物で、iOSのApp Thinningも AndroidのApp Bundleも「全部入りバイナリを配り、端末ごとに必要な断片だけ転送する」点が本質。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。