RORK LABEN
DEADLINE — 8月31日から、Google Play の新規アプリとアプリ更新はすべて Android 16(API レベル36)を対象にする必要があります。残り8日ですEXTENSION — 条件を満たす開発者は Play Console のフォームから11月1日までの延長を申請できます。申請そのものは締切前に済ませておく必要がありますEXISTING — 更新を出さない既存アプリも、新しい Android を載せた端末で新規ユーザーに見つけてもらうには最低 API レベル35 が必要です。静かに新規流入だけが止まる構造ですEXPO — Expo SDK 57 は React Native 0.85 から 0.86 への更新で、破壊的変更なしの1コマンド移行を狙った小さなリリースです。React は SDK 56 と同じ 19.2 のままですMEMORY — expo@57.0.9 が React Native 0.86.2 へ更新し、reanimated や worklets を読み込むアプリでメモリが大きく増える Hermes V1 の回帰を解消しましたPREBUILD — expo prebuild が既定でネイティブディレクトリを破棄して再生成するようになりました。API 36 対応で手を入れた設定と衝突しやすい部分ですDEADLINE — 8月31日から、Google Play の新規アプリとアプリ更新はすべて Android 16(API レベル36)を対象にする必要があります。残り8日ですEXTENSION — 条件を満たす開発者は Play Console のフォームから11月1日までの延長を申請できます。申請そのものは締切前に済ませておく必要がありますEXISTING — 更新を出さない既存アプリも、新しい Android を載せた端末で新規ユーザーに見つけてもらうには最低 API レベル35 が必要です。静かに新規流入だけが止まる構造ですEXPO — Expo SDK 57 は React Native 0.85 から 0.86 への更新で、破壊的変更なしの1コマンド移行を狙った小さなリリースです。React は SDK 56 と同じ 19.2 のままですMEMORY — expo@57.0.9 が React Native 0.86.2 へ更新し、reanimated や worklets を読み込むアプリでメモリが大きく増える Hermes V1 の回帰を解消しましたPREBUILD — expo prebuild が既定でネイティブディレクトリを破棄して再生成するようになりました。API 36 対応で手を入れた設定と衝突しやすい部分です
記事一覧/アプリ開発
アプリ開発/2026-08-23中級

targetSdk を 36 に上げたら 16 KB の警告が出ました。この2つは別の期限です

対象 API レベル36 の8月31日と、16 KB メモリページサイズの2027年2月1日は別の条件です。NDK を入れずに手元の AAB を検査する方法と、LOAD のずれと zip 境界のずれで直し方が変わることを、実際の出力とあわせて整理しました。

Google Play2916 KB ページサイズtarget API 36Expo180Rork543

運用している壁紙アプリの一本を対象 API レベル36 に引き上げ、内部テストへ上げたところで、Play Console に見慣れない警告が並びました。「アプリは 16 KB のメモリページサイズをサポートする必要があります」という一文です。

8月31日の期限に追われている最中でしたので、一瞬「これも今週中に直さないといけないのか」と身構えました。結論から書きますと、この2つは別の条件で、期日も違います。

混ぜて考えると、本来なら来年まで時間のある作業を、残り8日の作業リストに引き込んでしまいます。逆に「同じ話だろう」と片方だけ直して安心してしまう危険もあります。ここを最初に切り分けておきます。

Play Console に並ぶ2つの条件は、対象も期日も違います

対象 API レベルの要件と、16 KB ページサイズの要件は、発動する条件がそもそも異なります。

項目対象 API レベル3616 KB ページサイズ
期日2026年8月31日2027年2月1日
対象になる条件すべてのアプリ更新と新規アプリAPI レベル35 以上を対象にし、かつネイティブコードを含むアプリ
満たさないと起きること更新が提出できない更新が提出できない(期日以降)
直す場所ビルド設定(targetSdkVersion)と挙動の追随`.so` のリンク時アライメントと zip の格納位置
Kotlin / Java だけのアプリ対象対象外(すでに満たしている)

つまり、targetSdk を 35 未満から 36 へ引き上げた瞬間に、16 KB の要件が「新たに自分に適用された」状態になります。警告が今このタイミングで出てきたのは、期限が重なったからではなく、自分が条件の内側に入ったからでした。

対象 API レベル36 側の作業内容については、実効 targetSdkVersion を洗い出した記録と、更新を出す人と出さない人で8月31日の意味が変わる話にまとめてあります。本記事は 16 KB 側だけを扱います。

期日の一次情報は Android Developers の「16 KB ページサイズをサポートする」にあります。過去に「2025年11月1日」「延長すると2026年5月31日」という日付が広く共有されましたが、現在の公式表記は「2027年2月1日以降、16 KB をサポートしない更新はリリースできない」です。古い記事を読んで焦る必要はありませんが、猶予が無限にあるわけでもありません。

NDK を入れずに、手元の AAB がどちら側か調べる

公式の検査手順は llvm-objdumpzipalign を使います。どちらも Android SDK と NDK が要りますので、CI コンテナや、普段 EAS Build に任せていて手元にツールチェーンがない環境では、この確認のためだけに数 GB を入れることになります。

実際には、判定に必要な情報は .so の ELF プログラムヘッダと、zip の格納位置の2つだけです。Python の標準ライブラリだけで両方読めます。

#!/usr/bin/env python3
"""APK / AAB の .so を 16 KB アライメント観点で検査する(外部依存なし)"""
import sys, zipfile, struct
 
PT_LOAD, PT_GNU_RELRO = 1, 0x6474e552
 
def read_program_headers(data):
    if data[:4] != b"\x7fELF":
        return None
    is64, little = data[4] == 2, data[5] == 1
    end = "<" if little else ">"
    if not is64:
        return {"arch": "32bit", "loads": [], "relro": False}
    e_machine, = struct.unpack_from(end + "H", data, 18)
    e_phoff, = struct.unpack_from(end + "Q", data, 32)
    e_phentsize, e_phnum = struct.unpack_from(end + "HH", data, 54)
    loads, relro = [], False
    for i in range(e_phnum):
        off = e_phoff + i * e_phentsize
        p_type, = struct.unpack_from(end + "I", data, off)
        p_align, = struct.unpack_from(end + "Q", data, off + 48)
        if p_type == PT_LOAD:
            loads.append(p_align)
        elif p_type == PT_GNU_RELRO:
            relro = True
    return {"arch": {0xB7: "arm64-v8a", 0x3E: "x86_64"}.get(e_machine, hex(e_machine)),
            "loads": loads, "relro": relro}
 
def data_offset(z, info):
    """ローカルヘッダを読み、実データの開始位置を返す(zipalign が揃えるのはここ)"""
    f = z.fp
    f.seek(info.header_offset + 26)
    n, m = struct.unpack("<HH", f.read(4))
    return info.header_offset + 30 + n + m
 
def main(path):
    ng = 0
    with zipfile.ZipFile(path) as z:
        infos = [i for i in z.infolist() if i.filename.endswith(".so")]
        if not infos:
            print("lib/ に .so がありません。ネイティブコードなしと判断できます")
            return 0
        for info in sorted(infos, key=lambda i: i.filename):
            hdr = read_program_headers(z.read(info))
            if hdr is None:
                continue
            worst = min(hdr["loads"]) if hdr["loads"] else 0
            aligned = worst >= 16384
            stored = info.compress_type == zipfile.ZIP_STORED
            zip_ok = (data_offset(z, info) % 16384 == 0) if stored else None
            flags = []
            if not aligned: flags.append("LOAD=%d" % worst); ng += 1
            if not hdr["relro"]: flags.append("RELRO なし")
            if stored and zip_ok is False: flags.append("zip 境界ずれ"); ng += 1
            state = "UNALIGNED" if flags else "ALIGNED"
            print("%-10s %-12s %-9s %s" % (state, hdr["arch"],
                  "stored" if stored else "deflated", info.filename),
                  ("[" + " / ".join(flags) + "]") if flags else "")
    print("\n要対応: %d 件" % ng)
    return 1 if ng else 0
 
if __name__ == "__main__":
    sys.exit(main(sys.argv[1]))

p_align はプログラムヘッダの先頭から 48 バイト目にある 64 ビット値です。16 KB は 16384 ですので、PT_LOAD セグメントの p_align がすべて 16384 以上であれば ELF 側は満たしています。llvm-objdumpalign 2**14 と表示するのと同じ値を、桁の表現だけ変えて読んでいます。

data_offset() のほうは、zip のローカルヘッダを読んで実データの開始位置を求めています。zipfile が返す header_offset はヘッダの位置であって、zipalign が揃えるのはその先のデータ開始位置です。ここを取り違えると、揃っている AAB を「ずれている」と誤判定します。私は最初この違いに気づかず、正しいはずのビルドを不合格にしてしまいました。

出力の読み分け: LOAD=4096 と zip 境界ずれは、直し方が違う

検証のため、4 KB リンクの .so と 16 KB リンクの .so を用意し、APK と同じ構造の zip に詰めて実行しました。以下は実際の出力です。

$ python3 check_16kb.py sample-app.apk
UNALIGNED  x86_64       stored    lib/arm64-v8a/libapp.so [zip 境界ずれ]
ALIGNED    x86_64       deflated  lib/arm64-v8a/libhermes.so
UNALIGNED  x86_64       stored    lib/arm64-v8a/libthirdparty.so [LOAD=4096 / zip 境界ずれ]

要対応: 3 件

検証環境の都合で arch 列が x86_64 になっていますが、実際の APK では arm64-v8a が表示されます。この列は ELF ヘッダの機種フィールドを読んでいますので、lib/arm64-v8a/ の下にあっても中身が別アーキテクチャなら、それはそれで気づける作りにしてあります。

次に、.so の実データが 16384 の倍数に載るよう詰め物を入れた zip を作り直して、同じ検査を通しました。

$ python3 check_16kb.py aligned-app.apk
ALIGNED    x86_64       stored    lib/arm64-v8a/libapp.so
ALIGNED    x86_64       deflated  lib/arm64-v8a/libhermes.so
UNALIGNED  x86_64       stored    lib/arm64-v8a/libthirdparty.so [LOAD=4096]

要対応: 1 件

libapp.sozip 境界ずれ は消えましたが、libthirdparty.soLOAD=4096 は残りました。この差が、実務でいちばん効いてくる区別です。

出力原因直す人
`zip 境界ずれ` のみパッケージング側。AAB / APK に詰めるときの位置自分(ビルド設定の更新で解消)
`LOAD=4096``.so` そのものがリンク時に 4 KB 前提で作られているそのライブラリの提供者(または自分で再ビルド)
`RELRO なし`再配置セクションが読み取り専用化されていない提供者。16 KB 環境ではクラッシュ要因になり得ます

zip 境界ずれ だけであれば、Android Gradle Plugin 8.5.1 以上へ上げるだけで解消します。一方 LOAD=4096 は、自分のビルド設定をいくらいじっても直りません。配布されているバイナリの中身の問題ですので、依存の更新を待つか、置き換えるかの判断になります。

この2つを分けずに「16 KB 対応」とひとくくりにしてしまうと、直せない相手にビルド設定を延々と試すことになります。個人開発では、その数時間がそのまま期限との差になります。

Expo / Rork のプロジェクトで実際に効く手当て

React Native 本体は 0.77 で 16 KB ページサイズに対応しました(React Native 0.77 のリリースノート)。Expo SDK でいえば 53 以降が該当しますので、Rork が生成する現行構成をそのまま使っている限り、コア側で LOAD=4096 が出ることはまずありません。

問題になるのは、あとから足したサードパーティのネイティブモジュールです。地図、動画、決済、機械学習まわりのライブラリは更新が滞りやすく、リポジトリでは新しいバージョンが出ていても、npm 上の最新版に古い .so が残っていることがあります。

手順としては、次の順で確認するのが早いです。

  1. EAS Build から .aab をダウンロードし、上の検査を通します。ここで .so が 0 件なら、そもそもこの要件の対象外です
  2. LOAD=4096 が出たライブラリ名を控え、そのパッケージの更新履歴を見ます。多くは「16 KB support」の一行が入ったバージョンが出ています
  3. 更新で消えない場合は、expo-build-propertiesndkVersionandroid.minSdkVersion などのビルド条件を揃え、自前でプリビルドし直せるか検討します
  4. それでも残るなら、そのライブラリを置き換えるか、期日までに解決するかを判断します

なお、ネイティブ側に手を入れる前に、expo prebuild の既定変更で自分の変更が消えないかを確認しておいてください。この点はプリビルドで消えるネイティブ変更を洗い出す記事に手順を書いています。

16 KB backcompat モードという逃げ道と、その代償

公式ドキュメントには、16 KB カーネルの端末が 4 KB 前提のアプリを動かすための互換モードが記載されています。.soLOAD が 4 KB で、かつ非圧縮の .so が 4 KB 境界に載っている場合、パッケージマネージャがこのモードを有効にします。

ただし、初回起動時に「互換モードで動作しています」という警告ダイアログが表示されます。ユーザーから見れば、アプリを開いた最初の体験がその警告です。マニフェストの android:pageSizeCompat を設定すればダイアログは出なくなりますが、互換モードで動いている事実は変わりません。

私はこれを「期日までの一時しのぎ」として位置づけています。動くことと、意図した状態であることは別です。互換モードに寄りかかったまま忘れると、次にこの話題が浮上するのは、ユーザーからの「起動時に変な表示が出る」という問い合わせになります。

8月31日に持ち込む作業の線引き

ここまでを、今週の作業リストという観点でまとめます。

8月31日までに必要なのは、対象 API レベル36 への引き上げと、それに伴う挙動の追随だけです。16 KB の警告は、Play Console に赤く出ていても今週の必須項目ではありません。2027年2月1日までに片付ければよい作業です。

ただし、検査自体は今日のうちに一度通しておくことをおすすめします。理由は単純で、LOAD=4096 が出た場合は自分では直せず、依存の更新を待つ時間が要るからです。「直せない項目がいくつあるか」を早く知っておくほど、選べる手が増えます。逆に zip 境界ずれ だけなら、ビルド設定の更新で片付く話ですので、急ぐ必要はありません。

今日できることを1つ挙げるなら、直近の .aab を1本だけダウンロードして、上の検査を通してみることです。所要は数十秒で、結果が「要対応: 0 件」なら、この話は来年まで頭から外して構いません。

対象 API レベル36 に上げた際に一緒に浮上しやすい挙動の変化については、Android 16 の予測型「戻る」をいつ直すかの判断でも扱っています。期限に必要な作業と、切り離せる作業の線引きという意味では、本記事と同じ考え方に立っています。

期限が重なる時期は、どうしても目の前の赤い表示から順に手をつけたくなります。条件と期日を並べて書き出すだけで、その日にやるべきことが半分になることもあります。お読みいただきありがとうございました。

シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥2,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

アプリ開発2026-08-18
Rork のプロジェクトで targetSdkVersion 36 を通すまでに直した3か所
app.json に targetSdkVersion 36 と書いてあるのに、ビルドが読んでいた値は 35 でした。実効値を報告するスクリプトと、引き上げ・更新停止・延長申請の三択の分け方を残します。
アプリ開発2026-08-21
Apple の自動解析は、purpose string が書いてあるかではなく何に使うかを読んでいます
Rork や Expo で生成した iOS アプリを提出すると、Guideline 5.1.1 で「placeholder or otherwise insufficient purpose strings」と返されることがあります。キー名が示されないこの指摘の読み方と、app.json から文言を書き直すまでの手順をまとめました。
アプリ開発2026-08-16
グラフが壊れて見えるのは、データが多いときではなく最初の数日でした
記録アプリの折れ線グラフが、データの少ない最初の数日だけ線が消える。原因はY軸の幅が0になり座標がNaNになることでした。実測した挙動と、描画前に置く小さな正規化層を紹介します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →