Every now and then the question about the size of a NativeScript application package pops up, especially for the Android platform. The purpose of this post is to provide an in-depth and thorough answer to this question and why, by default, the Android application package (*.APK) adds ~12MBs on top of a blank, purely native Android application.
Let me first start with a high-level overview of the NativeScript architecture behind the Android Runtime.
NativeScript Android Runtime
The NativeScript Android Runtime consists of several major parts that provide the core functionality. These include:
- A C++ layer that tells V8 what to do with all the Android APIs.
How Android Works With Native Libraries
The tricky part of Android is how it resolves native binary libraries that are ABI specific. In other words, how does it know which binaries are for 32 bit devices, and which are for 64 bit architectures? The following explanation is taken directly from this excellent article in the Android docs:
The Android system knows at runtime which ABI(s) it supports, because build-specific system properties indicate:
- The primary ABI for the device, corresponding to the machine code used in the system image itself.
- An optional, secondary ABI, corresponding to another ABI that the system image also supports.
This mechanism ensures that the system extracts the best machine code from the package at installation time. When installing an application, the package manager service scans the APK, and looks for any shared libraries of the form:
If none is found, and you have defined a secondary ABI, the service scans for shared libraries of the form:
When it finds the libraries that it's looking for, the package manager copies them to /lib/lib<name>.so, under the application's data directory (data/data/<package_name>/lib/)."
ABI Support in NativeScript
In order for a NativeScript Android application to be available on devices with different binary interfaces, the NativeScript Android Runtime package has to provide the correct ABI versions for its native libraries.
The most common ABIs nowadays are:
If you create a NativeScript project and add the Android platform (“tns platform add android”), take a closer look in this folder:
There are three folders in there and their names match the ABIs listed above. These folders ensure that your application will run on almost all of the modern Android devices out there. However, when archived, they take-up a total of ~10MBs.
Said another way, blank NativeScript projects for Android are ~12MB by default because they include three copies of the NativeScript runtime built for different Android CPU architectures. (Since iOS has only one CPU architecture, this is not a problem for NativeScript on iOS.)
End of story? Not quite.
A single, self-contained APK is not the only possible solution for supporting correct ABI resolution. The Google Play Store also supports a feature called “Multiple APKs”.
This is a feature on Google Play:
"that allows you to publish different APKs for your application that are each targeted to different device configurations. Each APK is a complete and independent version of your application, but they share the same application listing on Google Play."
Google encourages developers to avoid using multiple APKs when possible and recommend a single, self-contained application package instead. Still, this sometimes is either impossible or not desired. For example, if an application exceeds the APK size limit of Google Play - that is 100MB, then it obviously needs to be split in multiple APKs. Or, if an application target audience is expected to have low bandwidth for some reason, the developer would prefer to reduce the APK size by all means.
Multiple APKs in NativeScript
Using the multiple APKs feature in a NativeScript application may reduce the ~12MBs default APK size three times -- up to ~4.5MB per ABI (x86, arm64-v8a, armeabi-v7a). This provides a workaround that can dramatically reduce the size of NativeScript APKs when app package size is important.
Unfortunately, there is no built-in functionality that automates the process in the NativeScript CLI yet. We do have this functionality on our radar and will start with its implementation soon. Until then, if the 12MBs APK size is a blocker for your application, you can manually “thin” the APK by removing the unneeded ABI builds of the NativeScript Runtime from the <project-name>/platforms/android/libs/jni/ folder.
One word of caution: if your application is started on a device that supports “ABI not available in your APK” then the app will simply crash.
Optional generation of multiple application packages per ABI is coming in the NativeScript CLI. Google does recommend single APKs whenever possible and we support this recommendation, so support for multiple APKs is intended only for apps where app package size is critical. While the per-ABI approach can save you some 8MBs, it comes at the price of a more sophisticated publish process where the chance for an error increases with each separate package produced. But if you’re willing to make that trade-off, and saving 8MBs helps you adopt NativeScript, multiple APKs is the answer for now.