たくさんの自由帳

Hello Android 15。16KB ページサイズ編

投稿日 : | 0 日前

文字数(だいたい) : 5924

どうもこんばんわ。
前回貼り付け忘れた画像です。かわいい

Imgur

Imgur

本題

前回の続きっちゃそうですが、単品で読めます。
https://takusan.negitoro.dev/posts/android_rust/

前回NDKを使うアプリを作りました。そうです、各 CPU アーキテクチャ向けにビルドするあれです。
NDKを使う(使った)アプリを開発する際、この16KB ページサイズに対応する必要があります。

こうしき

対応する必要があるか確認

リリース APKを作った後に、Analyze APK ...を押すことで、APKの中を見れます。

Imgur

で、こんな感じに、libフォルダがあれば対応が必要です。
が、今のアプリ開発だとlibandroidx.graphics.path.soが入ってしまう?

Imgur

はじめに

この記事で言うネイティブコードは、クロスプラットフォームの人たちが言う 各プラットフォームの開発言語 (Swift / Kotlin) の事では無く、
C++Rustのような Android NDK が必要なコードのことを指します。

ネイティブライブラリに関しても同じ。

AGP をバージョンアップ

https://developer.android.com/build/agp-upgrade-assistant

AGPバージョン8.5.1以上にすると共有ライブラリのパッケージを更新するのセクションは完了しそう。
ネイティブコードを使ってる場合は、自分で対応するにしろ、対応してもらうにしろ何にしろAGPのアップデートが必要そう?(よくわからない)

私の環境のAndroid Gradle Plugin8.8.2
Android StudioにはAGP Upgrade Assistantがついているので古い場合は上げちゃえば良さそう。

Imgur

対応パターン

多分このよっつ?

  • ライブラリがネイティブコードを使ってる場合
  • AndroidXlibandroidx.graphics.path.solibdatastore_shared_counter.soだけ入ってる場合
    • androidx.から始まる(Android Jetpack)のライブラリが.soファイルに依存している
  • Android StudioCMakeLists.txtとかを書いてC++をコンパイルしている場合
  • 共有ライブラリを他のプロジェクトでビルドして、Androidへ持ち込んでいる場合

使ってるライブラリがネイティブコードを使ってる場合

ライブラリ作者にお願いしてきてください
よろしくお願いします

AndroidX の .so ファイルのみが表示されている場合

libandroidx.graphics.path.solibdatastore_shared_counter.soとかが表示されている場合。androidx.のライブラリを入れたら付いてきた場合。
この場合は、すでに 16KB ページサイズでライブラリを配布しているので問題ない(はず)です。観測してる範囲では。

証拠は

後述するのですが、C++のビルド時に参照するCMakeLists.txtを見ると、16KB ページサイズでビルドするように追記されてました

俺がライブラリ作者だ / ネイティブコードをビルドしている場合

がんばろう

すでに 16KB ページサイズに対応しているか確認

https://developer.android.com/guide/practices/page-sizes#elf-alignment

macOS / Linuxの場合はシェルスクリプトで確認できます。適当にテキストファイルを.shで作り、シェルスクリプトを貼り付けてください。
vimだとペーストモードが便利。

説明通りここからシェルスクリプトをコピーしてきます。
https://cs.android.com/android/platform/superproject/main/+/main:system/extras/tools/check_elf_alignment.sh

Linuxしか確認できていませんが、chmodで実行権限をつけた後、./check_elf_alignment.sh {APKのパス}コマンドで動くはず。

chmod +x check_elf_alignment.sh
./check_elf_alignment.sh app-release.apk

Windowsの場合はWSL2か、あとはコマンドを愚直に叩く方法もある→ https://developer.android.com/guide/practices/page-sizes#windows-powershell
ちなみにGitBashじゃ動かなかった、unzip dir lib/*の箇所でエラーになるのと、GitBashでもobjdumpwindowsに無い。

以下が実行結果です、
UNALIGNED16KB ページサイズ未対応で、ALIGNEDが対応済みです。もっぱら自分が用意した.soが未対応ですね、、

/tmp/app-release_out_6q4W9/lib/arm64-v8a/libandroid_jni.so: UNALIGNED (2**12)
/tmp/app-release_out_6q4W9/lib/arm64-v8a/libandroid_jni_without_simd.so: UNALIGNED (2**12)
/tmp/app-release_out_6q4W9/lib/arm64-v8a/libandroidx.graphics.path.so: ALIGNED (2**14)
/tmp/app-release_out_6q4W9/lib/arm64-v8a/libjnidispatch.so: ALIGNED (2**16)
/tmp/app-release_out_6q4W9/lib/arm64-v8a/libandroid_rust_uniffi.so: UNALIGNED (2**12)

対応方法

対応する必要があるCPU アーキテクチャARM 64 ビット(arm64-v8a)x86_64です。
32bitはいいらしい。

この3つ?

  • Android StudioCMakeLists.txtを使ってC++をビルドしている場合
  • 他のC++プロジェクトをAndroid NDKでビルドして.so.hAndroid Studioへ持ち込んでいる場合
  • Android NDKRustとかからクロスコンパイルしている場合

環境

Android NDK r27を使います。
後述しますがr28を使えれば一番早いです。

Imgur

最短ルート

Android NDK r28以上を使って、あとはいつも通りビルドする。
バージョンが上げられる場合は多分これが一番早い。

Android Studio以外でビルドする場合はNDKのパスをr28に置き換えれば良さそう。
Android StudioC++ビルドする場合は、app/build.gradle (.kts)ndkVersion = ""28のものにする必要があります。以下例。

android {
    // 以下省略...
 
    defaultConfig {
        // 以下省略...
 
        ndkVersion = "28.0.13004108"
    }
 
    // 以下省略...
}

Android Studio で有効にする

一番多そう雰囲気(なぜか変換できる)。

https://developer.android.com/guide/practices/page-sizes#compile-r27

C++コードをAndroid Studioで書いている場合。
この場合はappフォルダ内のbuild.gradle (.kts)arguments += listOf("-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON")を書き足せばいいはず。

多分こう。

android {
    namespace = "io.github.takusan23.cppnativesample"
    compileSdk = 35
 
    defaultConfig {
        applicationId = "io.github.takusan23.cppnativesample"
        minSdk = 21
        targetSdk = 35
        versionCode = 1
        versionName = "1.0"
 
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
 
        // これを足す
        // This block is different from the one you use to link Gradle
        // to your CMake or ndk-build script.
        externalNativeBuild {
            // For ndk-build, instead use the ndkBuild block.
            cmake {
                // Passes optional arguments to CMake.
                arguments += listOf("-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON")
            }
        }
    }
 
    // 以下省略...

これでAPKを作成しもう一度チェックしてみると、ちゃんとarm64-v8ax86_64ALIGNEDになってました。

takusan23@DESKTOP-ULEKIDB:~$ ./check_elf_alignment.sh app-release-cpp.apk
 
Recursively analyzing app-release-cpp.apk
 
NOTICE: Zip alignment check requires build-tools version 35.0.0-rc3 or higher.
  You can install the latest build-tools by running the below command
  and updating your $PATH:
 
    sdkmanager "build-tools;35.0.0-rc3"
 
=== ELF alignment ===
/tmp/app-release-cpp_out_BGTqn/lib/x86_64/libcppnativesample.so: ALIGNED (2**14)
/tmp/app-release-cpp_out_BGTqn/lib/arm64-v8a/libcppnativesample.so: ALIGNED (2**14)
/tmp/app-release-cpp_out_BGTqn/lib/x86/libcppnativesample.so: UNALIGNED (2**12)
/tmp/app-release-cpp_out_BGTqn/lib/armeabi-v7a/libcppnativesample.so: UNALIGNED (2**12)
Found 2 unaligned libs (only arm64-v8a/x86_64 libs need to be aligned).

Android Studio を使わない他の C++ プロジェクトで CMakeLists.txt に指定する

次はこのパターン
Android Studio以外でCMakeLists.txtを使って共有ライブラリをビルドしている場合。

https://developer.android.com/guide/practices/page-sizes#compile-r27

CMakeLists.txtを使ってリンカーに引数をつければいいらしい。
というわけでAndroid向けにビルドできる適当なプロジェクトで試します。今回はUltraHDR画像を作るlibultrahdrをビルドしてみる。Android向けが公式にサポートされてるので。
Androidの機能だからそれはそう)

building.mdに従えば出来るはず。
cmakeninjaaptからinstallして、Android NDKをダウンロードして、指示通りコマンドを叩くだけ感。

しかし、これでビルドして.soAPKに入れても16K ページサイズに対応してないのでUNALIGNEDになります。

/tmp/app-release-other-project_out_7tCyK/lib/arm64-v8a/libuhdr.so: UNALIGNED (2**12)

というわけでAndroidビルドで使ってる?CMakeLists.txtを探します。
libultrahdrの場合はandroid.cmakeがそれだったので、一番下にこれを書き足しました。target_link_optionsはなんかエラーになってしまった。

# Android 15 16K page-size support.
set(CMAKE_SHARED_LINKER_FLAGS "-Wl,-z,max-page-size=16384")

あとはこれでビルドすればいいはず。出来た.somain/jniLibsCPU アーキテクチャのところに格納して終わりのはず。
以下は面倒くさがってarm64-v8aしかビルドしてませんが、これでALIGNEDになりました。libuhdr.soです。

takusan23@DESKTOP-ULEKIDB:~$ ./check_elf_alignment.sh app-release-other-project-16k.apk
 
Recursively analyzing app-release-other-project-16k.apk
 
NOTICE: Zip alignment check requires build-tools version 35.0.0-rc3 or higher.
  You can install the latest build-tools by running the below command
  and updating your $PATH:
 
    sdkmanager "build-tools;35.0.0-rc3"
 
=== ELF alignment ===
/tmp/app-release-other-project-16k_out_EFo2u/lib/x86_64/libcppnativesample.so: ALIGNED (2**14)
/tmp/app-release-other-project-16k_out_EFo2u/lib/arm64-v8a/libcppnativesample.so: ALIGNED (2**14)
/tmp/app-release-other-project-16k_out_EFo2u/lib/arm64-v8a/libuhdr.so: ALIGNED (2**14)
/tmp/app-release-other-project-16k_out_EFo2u/lib/x86/libcppnativesample.so: UNALIGNED (2**12)
/tmp/app-release-other-project-16k_out_EFo2u/lib/armeabi-v7a/libcppnativesample.so: UNALIGNED (2**12)
Found 2 unaligned libs (only arm64-v8a/x86_64 libs need to be aligned).
=====================

Imgur

Rust などのクロスコンパイルで Android 向けビルドを 16KB ページサイズに対応させる

https://developer.android.com/guide/practices/page-sizes#compile-r27

Rustcargoその他のビルドシステムに当たるので、-Wl,-z,max-page-size=16384をどうにかして渡す必要があります。

しらべた、
https://stackoverflow.com/questions/39310905/

cargo buildの際に、RUSTFLAGS-C link-arg=に突っ込めば良さそう感。なのでこれでいいはず。

RUSTFLAGS='-C link-arg=-Wl,-z,max-page-size=16384' cargo build --release --target aarch64-linux-android
RUSTFLAGS='-C link-arg=-Wl,-z,max-page-size=16384' cargo build --release --target x86_64-linux-android

Imgur

できた.soを同様にjniLibsに配置すればいいはず。
APK作ってチェックしてみたけど大丈夫そう。libandroid_rust_jni.sox86_64arm64-v8aALIGNEDになってます!!

takusan23@DESKTOP-ULEKIDB:~$ ./check_elf_alignment.sh app-release-rust-jni.apk
 
Recursively analyzing app-release-rust-jni.apk
 
NOTICE: Zip alignment check requires build-tools version 35.0.0-rc3 or higher.
  You can install the latest build-tools by running the below command
  and updating your $PATH:
 
    sdkmanager "build-tools;35.0.0-rc3"
 
=== ELF alignment ===
/tmp/app-release-rust-jni_out_bm5NF/lib/x86_64/libandroid_rust_jni.so: ALIGNED (2**14)
/tmp/app-release-rust-jni_out_bm5NF/lib/x86_64/libandroidx.graphics.path.so: ALIGNED (2**14)
/tmp/app-release-rust-jni_out_bm5NF/lib/arm64-v8a/libandroid_rust_jni.so: ALIGNED (2**14)
/tmp/app-release-rust-jni_out_bm5NF/lib/arm64-v8a/libandroidx.graphics.path.so: ALIGNED (2**14)
/tmp/app-release-rust-jni_out_bm5NF/lib/x86/libandroid_rust_jni.so: UNALIGNED (2**12)
/tmp/app-release-rust-jni_out_bm5NF/lib/x86/libandroidx.graphics.path.so: ALIGNED (2**14)
/tmp/app-release-rust-jni_out_bm5NF/lib/armeabi-v7a/libandroid_rust_jni.so: UNALIGNED (2**12)
/tmp/app-release-rust-jni_out_bm5NF/lib/armeabi-v7a/libandroidx.graphics.path.so: ALIGNED (2**14)
Found 2 unaligned libs (only arm64-v8a/x86_64 libs need to be aligned).

Imgur

16KB ページサイズの動作確認

https://developer.android.com/guide/practices/page-sizes#16kb-emulator

エミュレーターで試せるらしい。
起動できた。

Imgur

逆に16KB ページサイズに対応してないと以下のような例外で落ちる。

FATAL EXCEPTION: main
Process: io.github.takusan23.androidrustjni, PID: 16320
java.lang.UnsatisfiedLinkError: dlopen failed: empty/missing DT_HASH/DT_GNU_HASH in "/data/app/~~S5iS_B60dhn6WlqTEzvegg==/io.github.takusan23.androidrustjni-HG0y1lLFYvVg6MvQNC3k2g==/lib/x86_64/libandroid_rust_jni.so" (new hash type from the future?)
    at java.lang.Runtime.loadLibrary0(Runtime.java:1081)
    at java.lang.Runtime.loadLibrary0(Runtime.java:1003)
    at java.lang.System.loadLibrary(System.java:1765)
    at io.github.takusan23.androidrustjni.MainActivity.<clinit>(MainActivity.kt:42)
    at java.lang.Class.newInstance(Native Method)
    at android.app.AppComponentFactory.instantiateActivity(AppComponentFactory.java:95)
    at androidx.core.app.CoreComponentFactory.instantiateActivity(CoreComponentFactory.java:44)
    at android.app.Instrumentation.newActivity(Instrumentation.java:1448)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3941)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4235)
    at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:112)
    at android.app.servertransaction.TransactionExecutor.executeNonLifecycleItem(TransactionExecutor.java:174)
    at android.app.servertransaction.TransactionExecutor.executeTransactionItems(TransactionExecutor.java:109)
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:81)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2636)
    at android.os.Handler.dispatchMessage(Handler.java:107)
    at android.os.Looper.loopOnce(Looper.java:232)
    at android.os.Looper.loop(Looper.java:317)
    at android.app.ActivityThread.main(ActivityThread.java:8705)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:886)

16KB ページサイズでビルドしたら古い端末で動くの?

https://android-developers.googleblog.com/2024/08/adding-16-kb-page-size-to-android.html

で書かれてる通り、16KB ページサイズでビルドした共有ライブラリは4KB デバイスでも動くそうな。

一応古い端末を起動して試してみたけど、Galaxy S7 EdgeSnapdragon 820 (64bit) / Android 7)で起動できた。
16KB ページサイズ.soに置き換えるで問題なさそう。

Imgur

おわりです。お疲れ様でした。888