RORK LABJP
DEADLINE — From August 31, every new app and app update on Google Play must target Android 16 (API level 36). Eight days to goEXTENSION — Eligible developers can request an extension through November 1 via a form in Play Console, but the request itself has to be filed before the deadlineEXISTING — Even apps you leave alone need at least API level 35 to stay discoverable to new users on recent Android devices. Standing still quietly cuts off new installsEXPO — Expo SDK 57 moves React Native from 0.85 to 0.86 as a small, one-command upgrade with no breaking changes. React stays at 19.2, same as SDK 56MEMORY — expo@57.0.9 bumps React Native to 0.86.2 and clears the Hermes V1 memory regression that inflated usage in apps importing reanimated or workletsPREBUILD — expo prebuild now clears and regenerates the native directories by default, which collides easily with hand-edited config from your API 36 migrationDEADLINE — From August 31, every new app and app update on Google Play must target Android 16 (API level 36). Eight days to goEXTENSION — Eligible developers can request an extension through November 1 via a form in Play Console, but the request itself has to be filed before the deadlineEXISTING — Even apps you leave alone need at least API level 35 to stay discoverable to new users on recent Android devices. Standing still quietly cuts off new installsEXPO — Expo SDK 57 moves React Native from 0.85 to 0.86 as a small, one-command upgrade with no breaking changes. React stays at 19.2, same as SDK 56MEMORY — expo@57.0.9 bumps React Native to 0.86.2 and clears the Hermes V1 memory regression that inflated usage in apps importing reanimated or workletsPREBUILD — expo prebuild now clears and regenerates the native directories by default, which collides easily with hand-edited config from your API 36 migration
Articles/App Dev
App Dev/2026-08-23Intermediate

Bumping targetSdk to 36 surfaced a 16 KB warning. These are two different deadlines

The August 31 target API 36 requirement and the February 1, 2027 16 KB page size requirement are separate conditions. Here is how to inspect your AAB without installing the NDK, and why a LOAD misalignment and a zip boundary miss need completely different fixes.

Google Play2916 KB page sizetarget API 36Expo180Rork543

I raised one of my wallpaper apps to target API level 36, pushed it to internal testing, and a warning I had not seen before appeared in Play Console: this app must support 16 KB memory page sizes.

With the August 31 deadline a week away, my first reaction was to assume this had just been added to the same pile. It has not. These are two separate requirements with two different dates.

Treating them as one problem pulls work that is not due until next year into a week that is already full. Treating them as interchangeable is worse — you fix one, see the warning clear, and assume the other is handled too.

The two conditions in Play Console differ in scope and in date

The target API level requirement and the 16 KB page size requirement are triggered by different things.

ItemTarget API level 3616 KB page size
DateAugust 31, 2026February 1, 2027
Who it applies toEvery app update and every new appApps targeting API 35+ that ship native code
Consequence of missing itYou cannot submit updatesYou cannot submit updates (from that date)
Where the fix livesBuild config (targetSdkVersion) and behavior changesLink-time alignment of `.so` files and their position in the zip
Kotlin / Java-only appsAppliesDoes not apply — already compliant

So the moment you move targetSdk from below 35 up to 36, the 16 KB requirement newly applies to you. The warning did not appear because the deadlines collided. It appeared because you just stepped inside the condition.

I have written up the target API 36 side separately, in an audit of what the effective targetSdkVersion actually resolves to and in why August 31 means different things depending on whether you ship updates. This post stays on the 16 KB side.

The primary source is Support 16 KB page sizes on Android Developers. You will find plenty of posts citing November 1, 2025, or an extension to May 31, 2026. The current official wording is that starting February 1, 2027, updates that do not support 16 KB page sizes cannot be released. There is no reason to panic over the older dates — and no reason to assume the runway is unlimited either.

Checking your AAB without installing the NDK

The documented procedure uses llvm-objdump and zipalign. Both require the Android SDK and NDK. If you build through EAS and have no local toolchain, that means several gigabytes of install for a single yes-or-no answer.

In practice only two pieces of information matter: the ELF program headers inside each .so, and where that .so starts inside the zip. Python's standard library reads both.

#!/usr/bin/env python3
"""Inspect .so files in an APK / AAB for 16 KB alignment. No dependencies."""
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):
    """Read the local header to find where the data actually starts."""
    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("No .so under lib/. This app ships no native code.")
            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("no RELRO")
            if stored and zip_ok is False: flags.append("zip boundary"); 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("\nNeeds attention: %d" % ng)
    return 1 if ng else 0
 
if __name__ == "__main__":
    sys.exit(main(sys.argv[1]))

p_align sits 48 bytes into each program header entry as a 64-bit value. 16 KB is 16384, so if every PT_LOAD segment reports 16384 or more, the ELF side is fine. This is the same number llvm-objdump prints as align 2**14, just written in a different base.

data_offset() reads the zip local header to find where the payload actually begins. The header_offset that zipfile hands you points at the header, not at the data — and zipalign aligns the data. I missed that distinction on my first pass and flagged a perfectly good build as broken.

Reading the output: LOAD=4096 and a zip boundary miss need different fixes

To verify the checker, I built one .so linked at 4 KB and one at 16 KB, packed them into a zip laid out like an APK, and ran it. This is the actual output.

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

Needs attention: 3

The arch column reads x86_64 because of the verification environment; a real APK shows arm64-v8a there. The column reflects the machine field in the ELF header itself, so a file sitting under lib/arm64-v8a/ that is secretly a different architecture will also stand out.

I then rebuilt the zip with padding so each stored .so lands on a multiple of 16384, and ran the same check.

$ 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]

Needs attention: 1

The zip boundary flag on libapp.so cleared. The LOAD=4096 on libthirdparty.so did not. That gap is the distinction that matters most in practice.

OutputCauseWho can fix it
`zip boundary` onlyPackaging — where the file lands inside the AAB / APKYou, by updating build tooling
`LOAD=4096`The `.so` itself was linked assuming 4 KB pagesThe library author, or you if you rebuild it
`no RELRO`Relocation sections are not made read-only after loadThe library author. This can crash on 16 KB devices

A zip boundary flag on its own clears by moving to Android Gradle Plugin 8.5.1 or higher. LOAD=4096 will not budge no matter what you change in your own build config — the binary you were handed was linked that way. Your options are to wait for an updated release or to replace the dependency.

Collapsing both into "16 KB support" means burning hours on build settings for a problem your build cannot reach. Working solo, those hours come straight out of the deadline.

What actually helps in an Expo or Rork project

React Native added 16 KB page size support in 0.77 (see the React Native 0.77 release notes), which corresponds to Expo SDK 53 and later. If you are on the current Rork-generated setup, core libraries are very unlikely to report LOAD=4096.

The risk sits in third-party native modules added afterwards. Maps, video, payments, and on-device ML libraries all tend to lag, and a repository can carry the fix while the version published to npm still ships the old .so.

The fastest order to work through:

  1. Download the .aab from EAS Build and run the check. If there are zero .so files, the requirement does not apply to you at all
  2. Note the library names that report LOAD=4096 and read their changelogs. Most have a release with a one-line "16 KB support" entry
  3. If updating does not clear it, look at whether expo-build-properties lets you pin ndkVersion and rebuild the module yourself
  4. If it still persists, decide between replacing the library and accepting that it needs resolving before the date

Before you touch anything native, confirm your changes will survive the current expo prebuild defaults. I covered that in auditing which native changes prebuild will erase.

16 KB backcompat mode, and what it costs

The documentation describes a compatibility mode that lets 16 KB-kernel devices run apps built for 4 KB pages. When a .so has 4 KB LOAD segments and uncompressed .so files sit on 4 KB boundaries, the package manager turns it on.

The catch is a dialog on first launch telling the user the app is running in compatibility mode. From the user's side, that warning is the first thing the app does. Setting android:pageSizeCompat in the manifest suppresses the dialog, but not the underlying state.

I treat this strictly as a bridge to a date, not a resolution. An app running is not the same as an app running as intended. Lean on backcompat and forget about it, and the next time this topic surfaces will be a support message about a strange screen at startup.

Where to draw the line for August 31

Framed as this week's task list:

What August 31 requires is the target API 36 bump and keeping up with the behavior changes that come with it. The 16 KB warning, red as it looks in Play Console, is not on that list. It is due February 1, 2027.

Run the check today anyway. The reason is narrow: if LOAD=4096 shows up, you cannot fix it yourself, and waiting on a dependency takes calendar time. Knowing how many unfixable items you have is what determines how many options you still have. If all you see is zip boundary, it is a tooling update and there is no rush.

If you do one thing today, download a single recent .aab and run the check on it. It takes under a minute, and a result of zero means you can put this out of your head until next year.

For the behavior changes that tend to surface alongside a target API 36 bump, deciding when to fix predictive back on Android 16 walks through the same kind of split between what the deadline forces and what can wait.

When deadlines stack up, the instinct is to work down the list of red warnings in the order they appear. Writing out the condition and the date next to each one often halves what actually belongs in this week.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

App Dev2026-08-18
The three places I had to fix before a Rork project actually targeted API level 36
My app.json said targetSdkVersion 36. The value my build actually read was 35. Here is the script that reports the effective value, and how I split my apps between raising, leaving alone, and requesting an extension.
App Dev2026-08-21
Apple's automated pass reads what your purpose strings are for, not whether they exist
Submit a Rork or Expo iOS build and you may get it back under Guideline 5.1.1 with a note about placeholder or otherwise insufficient purpose strings. Here is how to read a rejection that names no key, and how to rewrite the strings from app.json.
App Dev2026-08-16
My chart broke on day one, not at scale
A line chart that vanished for anyone with only a few days of data. The cause was a zero-height Y axis turning coordinates into NaN. Here is the measured behavior and the small normalization layer that fixed it.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →