Androidの16KBページサイズ移行:実際に何が壊れ、APKをどう確認するか
Googleは今、Androidアプリに16KBメモリページのサポートを求めている。実務上何を意味するのか、なぜネイティブライブラリが壊れるのか、自分のアプリが安全かどう確認するかをまとめた。
アプリが何らかのネイティブコードを積んでいるなら——自前のJNIでも、.soファイルを同梱したサードパーティSDKでも——新しいハードウェアでしか爆発しない地雷を抱えている可能性が高い。Androidは従来の4KBページに加えて16KBメモリページをサポートするようになり、Google Playは最近のAndroidバージョンをターゲットにするアプリに対して、両方で正しく動くことを要求している。Kotlinオンリーのアプリのほとんどは、これを何も変えずに通過する。ネイティブ依存のあるアプリはそうはいかないことが多く、その壊れ方は厄介だ——lintの警告ではなく、インストール時や初回起動時のクラッシュで、しかも実際に16KBページを使う端末だけで起きる。
このアプリ群全体でこの移行を経験した——ほとんどは何事もなかったが、ひとつの依存関係だけはそうではなかった。ここでは、その確認作業が実際に何を含むのかを書く。
なぜページサイズが重要なのか
メモリページとは、OSがメモリを管理する最小単位だ。Androidは最初から4KBページを使ってきたが、一部の新しいチップセットではデフォルトで16KBページが使われる。大きいページはRAMを多く使うワークロードのメモリ管理オーバーヘッドを減らすからだ——ページテーブルエントリが減り、TLBミスが減り、平均してアプリの起動が速くなる。OSとAndroid Runtimeは、通常のKotlinやJavaのコードに対してはこれを透過的に処理する。ViewModel、Room、Composeには見えない——どれも基盤となるページサイズを気にしない。
ネイティブコードは気にする。ネイティブライブラリはELFバイナリであり、ELFバイナリのロード可能セグメントはビルド時に固定境界へアライメントされるからだ。4KBアライメントのセグメントでビルドされた.soは、4KBページの端末では問題なく動く。同じファイルを16KBページで動く端末にロードすると、動的リンカが正しくマップできないことがある——アプリが起動しないか、そのライブラリに触れた瞬間にクラッシュする。
実際に何が壊れるか
Kotlinのコードは壊れない。実際のリスクリストは短く具体的だ。
- 自前のNDK/JNIモジュール——もし持っていれば。
- 事前ビルド済みネイティブライブラリを同梱するサードパーティAAR——画像処理、クラッシュレポート、データベースエンジン、ML実行系。AARの中に
.soがあるものはすべて候補になる。 - 古いツールチェーンの出力。 ツールチェーンがデフォルトで16KBアライメントのセグメントを生成するようになる前の、古いNDKでビルドされたライブラリ——これが最もよくある障害パターンで、それ以外は活発にメンテされているSDKでも起きる。バイナリが、アライメントのデフォルトが変わって以来単に再ビルドされていないだけだ。
壊れないもの:純粋なKotlin依存関係、ネイティブコードを含まず.jarや.aarだけで配布されるもの、そして最近のリリースにあるGoogle自身のファーストパーティライブラリ——これらはすでに正しいアライメントで再ビルドされている。
APKに実際に何が入っているか確認する
ビルド設定に手を付ける前に、そもそもリスクがあるかどうかを確認する。
unzip -l app-release.apk | grep '\.so$'
これが何も返さなければ完了だ——アライメントすべきネイティブコードはない。何か返ってきたら、各ライブラリのセグメントアライメントを直接確認する。
unzip -p app-release.apk lib/arm64-v8a/libsomething.so | \
readelf -l - | grep LOAD
各LOADセグメントのAlign列を見る。0x4000(16KB)なら、そのライブラリはすでに正しくアライメントされている。0x1000(4KB)ならされておらず、それが追いかけるべき相手だ——出所のSDKの更新版を探すか、自分のコードなら再ビルドする。
Android StudioのAPK Analyzerは、コマンドラインなしで同じ情報を表示してくれる。APKを開き、lib/配下の.soファイルまで掘り下げると、4KBのみのアライメントを直接フラグしてくれる。
修正方法(コントロールできる度合いの順)
- 自前のネイティブコード:最新のNDKで再ビルドする。最近のツールチェーンはデフォルトで16KBアライメントの出力を生成するので、多くの場合
build.gradle.ktsのバージョン更新とクリーンな再ビルドだけで済む——ソースコードの変更は不要。 - 古い
.soを持つサードパーティSDK:依存関係を更新する。積極的にメンテされているSDKのほぼすべてが16KBアライメントのビルドを既に出している。修正は回避策ではなくバージョン更新だ。 - 更新されていない依存関係:これが厄介なケースだ。現実的な選択肢は、SDKにissueを立てる、オープンソースならフォークして再ビルドする、あるいはその依存関係を捨てることだ。自分がビルドしていない
.soを再アライメントするAPKレベルの裏技は存在しない。
私を捕まえたライブラリはまさにケース2だった——Granynの取引履歴の背後にある保存時暗号化のために、事前ビルド済みネイティブバイナリを同梱していたデータベース拡張だ。マイナーバージョンの更新でアライメント済みのビルドが手に入り、統合コード側は何も変わらなかった。
実機の16KB端末でテストする
アライメントのチェックは何が起こるべきかを教えてくれる。実際の16KBページでのテストは、実際に何が起こるかを教えてくれる。Android Emulatorには、まさにこのために作られたシステムイメージが用意されている——SDK Managerで最近のAPIレベルの「16 KB Page Size」バリアントを探し、そこからAVDを作成し、リリース版と同じ手順でAPKをインストールする。アライメントされていないライブラリを使う何かがクラッシュするなら、そこで、即座に、再現可能な形でクラッシュする。それは、自分の手元にない端末からのサポートチケットよりもずっと良い発見場所だ。
まとめ
Kotlin中心のほとんどのアプリにとって、この移行は見えないものだ——ネイティブコードなし、リスクなし、やることなし。ネイティブ依存を引き込んでいる何かに対しては、修正はほぼ常に書き直しではなく依存関係のバージョン更新になる。readelfでアライメントされていない.soを見つけ、それを配布している側を更新し、再ビルドし、公開前に16KBのシステムイメージで検証する。この確認作業は10分で済む。それを飛ばすということは、代わりにクラッシュレポートから知ることになるということだ。
// 関連記事
ジャーナルの他の記事
Android の In-App Review API:うっとうしくならずに評価を頼む方法
Google の In-App Review API の実践ガイド。実際の仕組み、表示すべきタイミング、そしてありがちな『評価してください』ポップアップが Play ストアの評価を静かに損なっている理由。
Androidのアプリショートカットとクイック設定タイル:アプリを開かずに水分補給を記録する
AndroidのダイナミックShortcutManager APIとTileServiceの実践ガイド — ワンタップの操作でアプリ起動を完全にスキップする方法と、多くの実装が陥りがちな落とし穴について。
2026年のAndroidにおけるフォアグラウンドサービスタイプ: 自分の機能に本当に当てはまるものを選ぶ
Androidのフォアグラウンドサービスタイプの制限に関する実践ガイド — dataSync、mediaPlayback、specialUse、shortService — 強制終了や審査却下を避けて正しいタイプを選ぶ方法。