Controlling APK Size When Using Native Libraries

If you ever worked on reducing your apk size you must have come across splitting the apk based on Density and Application Binary Interface (ABI).

In this post we will understand what is ABI, problems with the ABI split, and alternative way to avoid those problems. If splitting APK is a new topic for you I would recommend going through the Wojtek Kaliciński’s post here.

The ABI defines, with great precision, how an application’s machine code is supposed to interact with the system at runtime. You must specify an ABI for each CPU architecture you want your app to work with.

For instance, Android supports the following ABIs :

mips, mips64, X86, X86–64, arm64-v8a, armeabi, armeabi-v7a

In simple words all android devices have different chipset, processors, and CPU architectures. Applications uses the native library based on the CPU architecture of the phone for example Google Pixel will use arm64-v8a version of the native lib and Google Nexus 5 will use armeabi-v7a version of the native lib.

Whenever you add any native library in your application it will have a .so file for all the supported CPU architectures. This increases the apk size by a huge margin and the target device only needs one version of the library.

I have created a sample app with one activity and launcher icons.

Analysis of the APK file

Now let’s add a native library with multiple ABIs, for this demo I will be using Realm.

Note: This issue is not specific to realm, this is how all native library will behave on Android.

APK details after adding Realm

APK size is now 5MB from 74KB and we all are like …

Let’s take a look what happened there.

Realm distribution for different CPU architecture

So basically realm adds .so file for 5 CPU architectures mips, X86, X86–64, armeabi-v7a, and arm64-v8a this increases the apk size by 4.8 MB; remaining ~130KB is increased in dex size.

If you release this application Google Pixel device will install this 5MB apk where it needs only one version of the Realm lib (arm64-v8a) that is just 947KB, that is a huge overhead. (It will even work fine with v7 version of realm which is just 771KB, we will understand this soon.)

Let’s see how ABI split can help us in avoiding this problem

ABI split is basically telling gradle build system to create multiple apks for the different CPU architecture and include only relevant native libraries. The app module’s build.gradle looks like below with ABI split.

Android application level build.gradle file.

If you add this configuration in the application it will create a different version of apk for all different architectures as shown below.

Multiple APK files generated after enabling split

Amazing right! We went from 74KB to 5MB and came back to 1.3 MB.

But But But….

Though we now have a tailored version of apk only with necessary things, these are 5 apk for one application and we haven’t even gone through the apk splitting based on the density (This option generates individual apk for xhdpi, xxhdpi, and xxxhdpi depending on the configuration) which is more helpful than optimising for native library.

Normally we would want to split apk for at least hdpi, xhdpi, and xxhdpi version of the application so now we have 15 APKs to release. Let’s say you release an update of the application every month then you have to maintain 15 apks and imagine your client/senior comes and tells you need to do sanity for every apk before release just to be sure.

There is even a bigger problem than this cumbersome maintenance.

If you are building the application for the mass audience, users in emerging market like India where the mobile data and WiFi is not as cheap as other developed countries, users share the application using Bluetooth or Hot-Spot via SHAREit or Xender and side-load it.

Discussion about the APK side loading

If you split the apk using ABI split and a user with ARM architecture phone shares the application with someone with X86 architecture phone, the application will crash.

None of us wants to be in that situation where apps are crashing left and right.

It’s time to find a solution

There is a way we can avoid the above problem by using NDK abiFilters, remove the ABI split configuration and add below code in your app module’s build.gradle.

build.gradle file with abiFilters configuration

What this options does is, it only keeps the native library of architectures mentioned in abiFilters. Now our 5MB apk is 2MB (Not bad right), we can avoid creating 5 APKs of 1MB or one APK of 5MB.

APK with “armeabi-v7a” and “x86” libs

Wait a minute I just explained that changing anything in ABI can result in an unexpected result at the user end and now I am suggesting to blindly remove the ABIs, To understand that we need to go into the detail about each architecture.

TL;DR

Detail about CPU Architecture

If you are still not sure about removing these libraries, Let me show you one example which will give you courage to do this. Let’s dig into Google Maps v9.55.1 universal apk — MinSDK: Android 4.3 (Jelly Bean MR2, API 18).

Google Maps uses splits by ABI and density (Google Maps has 17 apks for one release) still their universal apk only supports arm64-v8a, armeabi-v7a, X86, and X86–64.

So you have to take a call on supporting 64-bit libraries based on size vs performance criteria but mips, mips-64, and armeabi should be removed without any hesitation.

Detail explanation about each architecture

  • armeabi is very old ARM based architecture. Since Android 4.4, the CDD (compatibility definition) strictly requires ARMv7 Read CDD Document here.

CDD is specification provided by Google to device manufacturers with every android release, it contains the minimum requirement to release android devices with Google certification.

Most of the device manufactures shifted to armV7 before Android 4.4 so we can safely ignore this armeabi architecture now.

  • This is the most common architecture currently available. This is a must support ABI for all the applications.
  • armeabi-v7a — 32bit — ARM Cortex-A5, ARM Cortex-A7, ARM Cortex-A8, ARM Cortex-A9, ARM Cortex-A12, ARM Cortex-A15, ARM Cortex-A17
  • This is the next generation 64 bit ARM architecture. All the flagship phones uses this architecture. Starting from Nexus 5x, Nexus 6P, Google Pixel all uses 64 bit ARM chipsets.
  • arm64-v8a — 64bit — ARM Cortex-A35, ARM Cortex-A53, ARM Cortex-A57, ARM Cortex-A72, ARM Cortex-A73
  • Support of this ABI is optional as the devices can use armeabi-v7a version of the native library. There will be a slight performance hit using a 32 bit version of the library on 64 bit processor but this should be avoidable considering that fact that we can save ~1MB of APK size in above example.
  • If you are using a native library which is the core part of your project and you want to give the best experience to the user on Flagship phones then maybe you can include this ABI but for most of the apps this can be removed until we see the majority of devices adopting 64bit processors.
  • Intel Processor-based devices, ASUS ZenFone2 being the most well-known device of the lot along with many Lenovo phones, application has to support this.
  • There are no android devices with 64-bit Intel processors available in the market and even if there are few devices it can very well use the 32 bit version of the native library so X86–64 libs can be avoided in bundling.
  • There are no android devices with MIPS architecture in market.

There was one device which was supposed to be launched in 2016 but I haven’t found any information other than this article First MIPS device and there is a thread about MIPS on Reddit.

If you decide to go down this route and filter the libraries, while releasing the application Google Play Console will show a warning like below but don’t worry it is just informing you about changes.

Google Play Console warning when uploading application with ABI filters

So what are you waiting, go ahead change your gradle file right away.

I’d love to hear your thoughts in the comments below, or talk to me on Twitter.

Edit :- This article is now referenced on Official Realm Documentation — here

References

  1. ABI Management — https://developer.android.com/ndk/guides/abis.html
  2. Multiple APK Support — https://developer.android.com/google/play/publishing/multiple-apks.html
  3. Thanks to Mert Şimşek for post related to abiFilters — https://medium.com/mobiwise-blog/unsatisfiedlinkerror-problem-on-some-android-devices-b77f2f83837d

Android Developer