Reducing your Android APK size When Using Native Libraries

Every now and then, we hear from users concerned about the size Realm’s native libraries add to their app. With today’s release of Realm 0.79, we’ve made major improvements on that front.

A default empty Android app compiled for ARM with no external library added will have a base weight of about 907KB (in some cases, you can go as low as ~50KB, see note). With Realm 0.78 added, the same app measured 3.9MB. Now with Realm 0.79, that weight can go down to just 1.6MB, a decrease of over 70%.

Read on to see why this matters, and how we (+ you) can achieve this.

Why You Should Care About APK Size

A couple of megabytes saved might not look much to owners of modern devices, but there is a vast number of older or low-end devices out there in Androidland that don’t have the luxury of free storage space. In fact, as Android is especially popular in emerging economies, most devices running Android apps do not look like your iPhones or Nexuses — some only have a few dozen megabytes of space available for all apps, meaning that installing an app of a few megs will usually necessitate deleting another app. Often, a high megabyte size will be the difference between getting an install or not, for those space-strapped users.

The Deal with Native Libraries

If you downloaded our 0.78 release jar and dug in, you would have seen something like this:

Get more development news like this

$ du -h -d1
 28K    ./com
468K    ./io
9.1M    ./lib <--- Native libraries
 16K    ./META-INF
9.6M    .

Yikes! Native libraries total 9MB, over 94% of our jar size. This in turn, would add over 3MB to your APK (after compression).

If you read our FAQ, you know that the size of the Realm jar file you download is different than the size Realm will add to your app. That’s because the jar must contain one copy of the same native library for each architecture you target. Nowadays you would commonly need to target ARM, ARMv7-A, ARM64, MIPS & x86, meaning on average every byte of native code you add is multiplied by 5x!

Now the Android installation process is smart enough to discard the native libraries for non relevant CPUs, e.g. it will only keep the MIPS copy of the library if you run on a MIPS device, and delete the others. So once your app is installed on the device, Realm will actually take a lot less space than what your APK may look like. Your app size will still show high on the app store, and your app may still be a bit heavy to download though!

Optimizing Native Libraries

Forgetting for a moment the fact there are multiple copies of your native code embedded in the jar, it’s still worth trying to see what can be done to optimize the size of each compiled version of the native library.

Looking deeper at the files in the lib/ folder of our jar, you could see the size of each individual architecture we supported. (NB: ARM64 was not supported yet in 0.78)

$ ls -lh lib/
lib/armeabi:
total 3776
-rwxr-xr-x@ 1 emanuelez  staff   1.8M Jan 22 14:30 libtightdb-jni.so

lib/armeabi-v7a:
total 3672
-rwxr-xr-x@ 1 emanuelez  staff   1.8M Jan 22 14:31 libtightdb-jni.so

lib/mips:
total 6376
-rwxr-xr-x@ 1 emanuelez  staff   3.1M Jan 22 14:32 libtightdb-jni.so

lib/x86:
total 4888
-rwxr-xr-x@ 1 emanuelez  staff   2.4M Jan 22 14:31 libtightdb-jni.so

(libtightdb is the current name of our C++ core library, deriving from the previous name of our company “TightDB”; that project is now being renamed to realm-core.)

You can see that ARM libraries are much smaller. That’s because they can take advantage of the Thumb instruction set, which is great since most Android devices run on ARM processors.

At that point both our core (libtightdb) and the JNI code was compiled with GCC with the -Os flag, which tries to optimize the library for size while maintaining decent performance. We also enabled -visibility=hidden to hide most of the unneeded ELF symbols.

Toolchain Tricks

So what can we do to improve this? We experimented with several settings and we achieved great results. We now use two extra features of the GCC toolchain:

This lead us to the following results:

realm/build/intermediates/bundles/release/jni/armeabi:
total 1624
-rwxr-xr-x  1 emanuelez  staff   809K Feb 11 09:30 libtightdb-jni.so

realm/build/intermediates/bundles/release/jni/armeabi-v7a:
total 1592
-rwxr-xr-x  1 emanuelez  staff   793K Feb 11 09:30 libtightdb-jni.so

realm/build/intermediates/bundles/release/jni/mips:
total 3448
-rwxr-xr-x  1 emanuelez  staff   1.7M Feb 11 09:30 libtightdb-jni.so

realm/build/intermediates/bundles/release/jni/x86:
total 2640
-rwxr-xr-x  1 emanuelez  staff   1.3M Feb 11 09:30 libtightdb-jni.so

We practically halved the size of our libraries! This brought the total size of the jar file from 3.1 MB to 2.1 MB. Quite an improvement!

APK Splits

But there’s one more thing that app developers can do to make this even better: APK splits. It’s a relatively new feature added to the Android Gradle plugin that promises to deliver one APK per CPU, only containing the relevant native libraries.

Unfortunately this feature doesn’t work when the native libraries are bundled in a jar file, but there’s a quick workaround that can be used.

Our distribution package (available on our website under Download->Java) contains a folder called ‘eclipse’. This folder contains a split version of the Realm library. All you need to do is to copy the small jar file into the libs folder of your app and copy the four folders in the src/main/jniLibs directory. Now you can enable ABI splitting in the build.gradle file like this:

android {
    // Some other configuration here...

    splits {
        abi {
            enable true
            reset()
            include 'x86', 'armeabi', 'armeabi-v7a', 'mips'
            universalApk false
        }
    }
}

Now Gradle will generate one APK per CPU reducing the size even further!

Recap

Putting this all together, smaller compiled size and APK splits mean that Realm now adds as little as ~730KB to your APKs for most (ARM-based) devices. That’s 76% less than the previous 3MB Realm would add to your app in the past! We will keep trying to bring this number down, and we look forward to helping you keep your apps lean & clean, wherever you’re deployed.


Feb. 17 UPDATE: a few helpful redditors point out that in some cases, your Android app size can go below 907KB, and down to ~50KB if you can you can remove the Android Support Library & appcompat.

About the content

This content has been published here with the express permission of the author.


Realm Java Team

The Realm Java Team is working around the clock (literally, we span 17 timezones) in order to create the best possible persistence solution on mobile. Easy-to-use, powerful features and first class performance is possible, and we are committed to building that.

4 design patterns for a RESTless mobile integration »

close