Android apps that incorporate native Swift need to embed all their native dependencies in the app in the form of shared object files for each supported architecture. This includes both app-specific Swift code, as well as all the core dependencies like the Foundation libraries. These are all weighty files, but the one that stands out in the list is the 40M lib_FoundationICU.so
:
ls -laSrh ~/Library/org.swift.swiftpm/swift-sdks/swift-6.0.3-RELEASE-android-24-0.1.artifactbundle/swift-6.0.3-release-android-24-sdk/android-27c-sysroot/usr/lib/aarch64-linux-android/
441K libFoundationXML.so
1.8M libFoundationNetworking.so
3.8M libFoundationInternationalization.so
8.7M libFoundation.so
8.8M libFoundationEssentials.so
9.6M libswiftCore.so
40M lib_FoundationICU.so
With Skip's native Swift support (see Building apps with Skip's native Swift Android toolchain integration (tech preview)), we have two very basic sample apps, one called "Hello Skip" which uses pure transpilation from Swift to Kotlin (and no native Swift), and another called "Hiya Skip" which uses native Swift (for 2 architectures: aarch64 and x86_64). As can be seen from those two release pages, HelloSkip-release.apk
is 12.9 MB, and HiyaSkip-release.apk
is 200MB! The compressed distribution bundles, which is approximately the download size that a user would experience, are a bit better (HelloSkip-release.aab
is 8.44 MB and HiyaSkip-release.aab
is 70.4 MB), but 70MB is still too much for a "Hello World" app, especially as contrasted with the iOS HiyaSkip-release.ipa
: 789KB.
I would like to start an open discussion about what might be done about this. Here are the options we have considered thus far:
- Exclude i18n altogether: if code just imports
FoundationEssentials
and eschewsFoundationInternationalization
, then the APK size would be reduced drastically. This is the most obvious solution, but is also the most obvious one to dismiss out of hand: apps need support for localization. - Implement a
lib_FoundationICUAndroidNDK.so
replacement library that uses the existinglibicu.so
: aside from only being present in Android 12 (API 31) and higher (as per ICU4C on Android) and thereby only being available on ~45% of devices (as per Android Studio's API market share estimator), the Android ICU4C NDK Reference shows that the API subset they expose is insufficient for the needs ofFoundationInternationalization
: it has none of theudat_*
,unum_*
, orucurr_*
needed for date/time formatting and parsing, nor anyucal_*
calendar functions. - Implement a
lib_FoundationICUAndroidJNI.so
that bridges localization functions to Java: unlike the Android NDK's ICU4C support, the Java SDK's ICU4J support (e.g., via theandroid.icu.text
,android.icu.number
andandroid.icu.lang
packages) are considerably more complete. However, this would require implementing support via JNI, and incurring a bridging trip for each any every localization call. I fear that the performance implications could be significant. - Reduce the size of
lib_FoundationICU.so
: most of the size of the library comes from the in theicu_packaged_main_data.*.inc.h
header tables in swift-foundation-icu/icuSources/common at main · swiftlang/swift-foundation-icu · GitHub. Could these be somehow compressed further? Or could a significant size reduction be realized by excluding some rare-but-large locales from that file? I haven't explored this option yet. - Support for dynamic thinning of ICU data: if an app only contains localization strings for 2 languages, then it might make sense to reduce the locale data to only support those languages. This would be a significant re-architecting of swift-foundation-icu to use external and removable resources rather than embedded
[uint8_t]
data.
I would be interested in hearing other suggestions for how we might reduce the Android app size. 200MB is too large, especially when contrasted with Flutter (~10 MB min APK size) which bundles its own i18n support.