Since this is my first blog post here, I owe you an introduction!
One of my goals here is to share my experience on improving the x86 compatibility of various libraries and game engines on Android. This is part of my job at Intel and I hope that sharing this will be useful for others… especially if you’re a developer googling around because of some cryptic toolchain or compiler errors 🙂
Let’s start by explaining what it generally involves to do this and why it matters in the first place. I swear my next blog posts will be more focused on the technical details.
Why doing anything for x86-based Android products?
Android running on x86 platforms isn’t something new. The community-driven android-x86.org project for example exists since 2009. It’s also worth mentioning the first Google TVs (2010) were already running an x86 version of Android and the x86 target has been officially added to the Android NDK since r6 (2011).
Intel contributed a lot to bring more x86 compatibility into the AOSP (Android Open Source Project) itself, and is also behind android-ia.org (2012) that gives a fully open source distribution of Android that can be booted on recent UEFI-based PCs.
You can now also run an x86 version of Android on PCs through the Android SDK emulator (accelerated by our HAXM or kvm) or using other products like Genymotion – that represents around 400’000 users as of now !
There are x86 based Android devices on the market
Since 2012 there is a lot more than these projects: you can find Android devices running on x86 in the hands of customers. Some examples of Android devices running on Intel Architecture are: Samsung Galaxy Tab 3 10.1′, Asus MemoPad FHD 10, Dell Venue 7/8, Motorola Razr I, Lenovo K900 or more recently the Asus Fonepad Note 6.
update 2014/11/1: More and more devices are using Intel SOCs, including the Google Nexus Player, Asus Zenfones, Lenovo Yoga Tablet 2, Asus Transformer Pads (TF103/TF303), Tesco Hudl* 2, and many others.
And they all are compatible with current apps ecosystem
Android apps are ran by the Dalvik Virtual Machine (DVM) or the emerging Android RunTime (ART) that are cross-platform and working well on x86. Some applications (around 40-60% of them) also include CPU-dependent native libraries… and in many cases this means they may be compiled for ARM platforms only.
Luckily the x86 devices on the market all embed something particular called “NDK apps bridging technology” that allows these ARM assemblies to still run through a compatibility layer. That’s why in fact, even as a user of these devices, you would have hard times guessing it is x86 based.
But It’s still really important to give x86 versions of your native libraries
- The bridging technology is a property of Intel so you may not find it in other x86-based products.
- Even if this bridging technology is working really well, there are always performance and battery consumption hits for your app – not negligible for heavy workloads.
- You don’t control it: older devices still carry an older version of this technology and you can’t expect today’s reliability.
- Your application will always be able to run better if the x86 versions of your native libraries is there, and most of the time this isn’t a big deal to create or get and integrate x86 versions of your native libraries.
How to compile or get x86 versions of your native libraries
Check if you are using native libraries
The first thing to do is to check inside your app if you even have native libraries.
If you are using the Android NDK by yourself, you obviously already know your app has native libraries… Maybe even all the code of your app is written in C/C++ (ie. you are using native_app_glue.h).
But these .so files (shared object libraries) can also be generated by game engines or included by other 3rd party libraries.
The standard location for these is in lib/TARGET_ARCH_ABI where TARGET_ARCH_ABI can be as of now: armeabi (ARMv5), armeabi-v7a (ARMv7), mips or x86.
If you are using the Android NDK toolchain:
Start with setting APP_ABI:=all in your Makefile “jni/Application.mk” (not Android.mk! you may need to create it), to change the default value that is only “armeabi”.
Next time you’ll compile your code, the shared object libraries will be generated for all the CPU ABIs the NDK supports, under libs/TARGET_ARCH_ABI/*.so:
Dead simple, isn’t it?
Usually this goes well… but using a new toolchain can sometime shed some light on some bugs or errors that were hidden in your code before. An example is when you have some parts of your code that don’t respect C/C++ standard: result may change depending on compiler’s implementation.
If these .so files come from 3rd party libraries and game engines:
Start by upgrading these to latest version. X86 support may have been added recently.
Having said that, many libs and engines already have x86 support out-of-the-box. Here is a short list of well-known libraries/engines I know they have x86 support: libgdx, cocos2dx, fmod, AppGameKit, Unreal Engine 3, Havok Anarchy SDK, Marmalade, lame, OpenCV, etc.
Troubleshooting
Installation
If you are trying to install your app in an x86 emulator and get this message from logcat:
Failure [INSTALL_FAILED_CPU_ABI_INCOMPATIBLE]
or from Google Play Store:
Your device isn't compatible with this version
This means you have native libraries inside **lib/(armeabi | armeabi-v7a | mips)** folders but not inside lib/x86, hence your package is declared as incompatible with the x86 ABI. |
You need to get an x86 version of your native libraries if you want it to run inside x86 emulators.
Compilation
The most obvious thing that can break x86 compilation is to have ARM assembly code somewhere… you’ll get errors like these:
Error: no such instruction: `ldr r12,[r0]'
Error: no such instruction: `smull %edx,%esi,%eax,%ecx'
Error: number of operands mismatch for `mov'
Error: impossible constraint in 'asm'
Usually this case happen with cross-platform libraries that have their compilation variables configured incorrectly for Android – leading to the inclusion of the ARM code path – and you only need to fix it*
This is the most common case I’ve seen. I’ll cover other more specific ones later in this blog.
*If you really only have the ARM assembly available, you’ll still need to write an x86 or C/C++ equivalent, but this is quite rare.
Runtime
You can use all the classic debugging techniques (gdb…) when using the x86 image inside your Android emulator (+HAXM/kvm) or Genymotion. This is also a good idea to use these regularly since they are a lot faster than the out-of-the-box Android SDK emulator.
Optimization
-O3 and -ffast-math are the classic cflags you can use to enable maximum compiler optimization when targeting any platform but there are some other architecture specific flags you can set when compiling for x86 by adding this in your Android.mk Makefile.
ifeq ($(TARGET_ARCH_ABI),x86)
LOCAL_CFLAGS += -ffast-math -mtune=atom -mssse3 -mfpmath=sse
endif
ifeq ($(TARGET_ARCH_ABI),x86_64)
LOCAL_CFLAGS += -ffast-math -mtune=slm –msse4.2
endif
SSSE3 instruction set is available on every x86-based Android device so you’re safe enabling it. You’ll also be able to enjoy a nice (it can be 20%) performance boost switching fpmath from x87 instruction set to SSE. For x86_64 (64bits) targets, fpmath uses SSE by default, hence no need to enable.
Reducing the size of your app
There is no need to sacrifice any ABI folder if you want to reduce the size of your APK. You can split it and have one version per CPU architecture while keeping one entry on the Google Play Store.
First switch to “advanced mode” inside your developer console, then upload different APKs targeting different CPU architectures with each a different android:versionCode number inside the AndroidManifest.xml.
If you want the right .apk to be served, there is only one rule to follow here:
x86 version number > ARMv7 version number > ARMv5 version number
That’s because a set of these architectures can be compatible with a single device and the Play Store will serve the compatible apk with the highest version number.
You can follow this rule any way you want, but it’s easier to achieve this by following a convention like prefixing your regular version number with a single digit representing the ABI:
Some useful links: