İçeriğe geç
Tüm yazılar

Android 16 KB sayfa boyutu geçişi: gerçekte ne bozulur, APK'nı nasıl kontrol edersin

Google artık Android uygulamalarının 16 KB bellek sayfalarını desteklemesini istiyor. Pratikte bunun anlamı, neden native kütüphanelerin kırıldığı ve uygulamanın güvenli olduğunu nasıl doğrulayacağın.

MFKAPPS 4 dk okuma

Uygulaman herhangi bir native kod taşıyorsa — kendi JNI kodun ya da içine bir .so dosyası gömülü bir üçüncü taraf SDK’sı — büyük ihtimalle yalnızca yeni donanımlarda patlayan bir mayın taşıyorsun demektir. Android artık geleneksel 4 KB sayfaların yanında 16 KB bellek sayfalarını da destekliyor ve Google Play, yakın Android sürümlerini hedefleyen uygulamaların her ikisinde de doğru çalışmasını şart koşuyor. Sadece Kotlin kullanan uygulamaların çoğu bundan hiç etkilenmeden geçiyor. Native bağımlılığı olan uygulamalar çoğu zaman geçemiyor ve hata biçimi çirkin: bir lint uyarısı değil, kurulumda ya da ilk açılışta çökme — üstelik sadece gerçekten 16 KB sayfa kullanan cihazlarda.

Bu geçişi tüm uygulama ailesinde yaşadım — büyük kısmı hiçbir şey değişmeden geçti, bir bağımlılık dışında. İşte kontrolün gerçekte ne içerdiği.

Sayfa boyutu neden önemli

Bellek sayfası, işletim sisteminin belleği yönettiği en küçük birimdir. Android baştan beri 4 KB sayfalar kullandı; bazı yeni çipsetler bunun yerine varsayılan olarak 16 KB sayfalar kullanıyor, çünkü daha büyük sayfalar RAM ağırlıklı iş yükleri için bellek yönetim maliyetini azaltıyor — daha az sayfa tablosu girdisi, daha az TLB kaçırma, ortalamada daha hızlı uygulama açılışı. İşletim sistemi ve Android Runtime bunu sıradan Kotlin ve Java kodu için şeffaf biçimde hallediyor. ViewModel, Room, Compose için tamamen görünmez — hiçbiri altta yatan sayfa boyutunu umursamaz.

Native kod umursar, çünkü native kütüphaneler ELF ikili dosyalarıdır ve ELF ikili dosyalarının yüklenebilir segmentleri, derleme zamanında sabit bir sınıra hizalanır. 4 KB hizalı segmentlerle derlenmiş bir .so, 4 KB sayfa kullanan bir cihazda sorunsuz çalışır. Aynı dosyayı 16 KB sayfa çalıştıran bir cihazda yükle, dinamik bağlayıcı onu doğru eşleyemeyebilir — uygulama başlamaz ya da o kütüphaneye dokunduğu anda çöker.

Gerçekte ne bozulur

Kotlin kodun değil. Gerçek risk listesi kısa ve nettir:

  • Kendi NDK/JNI modüllerin, eğer varsa.
  • Önceden derlenmiş native kütüphane paketleyen üçüncü taraf AAR’lar — görüntü işleme, çökme raporlama, veritabanı motorları, ML çalışma zamanları. AAR’ının içinde bir .so varsa aday demektir.
  • Eski araç zinciri çıktısı. Araç zinciri varsayılan olarak 16 KB hizalı segment üretmeden önce, eski bir NDK ile derlenmiş bir kütüphane — bu, aksi halde aktif bakımı yapılan bir SDK’da bile yaygın hata durumudur; ikili dosya sadece hizalama varsayılanı değiştiğinden beri yeniden derlenmemiştir.

Bozulmayan şeyler: saf Kotlin bağımlılıkları, native kod içermeden yalnızca .jar veya .aar olarak dağıtılan her şey ve Google’ın kendi birinci taraf kütüphanelerinin, doğru hizalamayla zaten yeniden derlenmiş yakın sürümleri.

APK’nda gerçekte ne olduğunu kontrol etmek

Derleme yapılandırmasına dokunmadan önce, gerçekten risk altında olup olmadığını öğren:

unzip -l app-release.apk | grep '\.so$'

Bu hiçbir şey döndürmüyorsa işin bitti — hizalanacak native kod yok. Sonuç dönüyorsa, her kütüphanenin segment hizalamasını doğrudan kontrol et:

unzip -p app-release.apk lib/arm64-v8a/libsomething.so | \
  readelf -l - | grep LOAD

Her LOAD segmentindeki Align sütununa bak. 0x4000 (16 KB) o kütüphanenin zaten doğru hizalandığı anlamına gelir. 0x1000 (4 KB) hizalı olmadığı anlamına gelir ve peşine düşmen gereken kütüphane budur — ya geldiği SDK’nın güncel bir sürümü, ya da kendi kodunsa yeniden derleme.

Android Studio’nun APK Analyzer’ı aynı bilgiyi komut satırı olmadan gösterir: APK’yı aç, lib/ altındaki bir .so dosyasına in, sadece 4 KB hizalamayı doğrudan işaretler.

Çözüm, elindeki kontrol miktarına göre sıralı

  1. Kendi native kodun: güncel bir NDK ile yeniden derle. Yakın araç zinciri sürümleri varsayılan olarak 16 KB hizalı çıktı üretir, bu genellikle build.gradle.kts içinde bir sürüm yükseltmesi artı temiz bir yeniden derlemedir — kaynak kod değişikliği yoktur.
  2. Eski bir .so içeren üçüncü taraf SDK: bağımlılığı güncelle. Aktif bakımı yapılan neredeyse her SDK 16 KB hizalı bir derleme yayınladı; çözüm bir geçici çözüm değil, bir sürüm yükseltmesidir.
  3. Güncellenmemiş bir bağımlılık: rahatsız edici durum budur. Gerçek seçeneklerin SDK’ya açılan bir issue, açık kaynaksa fork edip yeniden derlemek ya da bağımlılığı bırakmaktır. Kendi derlemediğin bir .so’yu yeniden hizalayan bir APK seviyesi hilesi yoktur.

Beni yakalayan kütüphane tam olarak ikinci durumdu — Granyn’in işlem geçmişinin arkasındaki bekleme durumu şifrelemesi için önceden derlenmiş native ikili dosyalar taşıyan bir veritabanı uzantısı. Küçük bir sürüm yükseltmesi hizalı derlemeyi getirdi; entegrasyon kodunda hiçbir şey değişmedi.

Gerçek bir 16 KB cihazda test etmek

Hizalama kontrolleri ne olması gerektiğini söyler; gerçek 16 KB sayfalarda test etmek ne olduğunu söyler. Android Emulator, tam olarak bunun için derlenmiş sistem imajları sunuyor — SDK Manager’da yakın bir API seviyesinin “16 KB Page Size” varyantını bul, ondan bir AVD oluştur ve APK’nı tam olarak yayın için yapacağın gibi kur. Hizalanmamış bir kütüphane kullanan bir şey çökecekse, orada, hemen ve tekrarlanabilir biçimde çöker — bu, sahibi olmadığın bir cihazdan gelen bir destek talebinden çok daha iyi bir yer.

Sonuç

Çoğu Kotlin öncelikli uygulama için bu geçiş görünmez — native kod yok, risk yok, yapacak bir şey yok. Native bağımlılık çeken her şey için çözüm neredeyse her zaman yeniden yazmak değil, bağımlılık sürümünü yükseltmektir: readelf ile hizalanmamış .so’yu bul, onu getireni güncelle, yeniden derle ve yayınlamadan önce 16 KB sistem imajında doğrula. Kontrol on dakika sürer. Atlamak, bunu bir çökme raporundan öğrenmek anlamına gelir.