Beyond Java
Android applications development is mostly based on Java programming. The SDK is based on Java, the runtime system is fully compliant with Java6, partially with Java7, and Google is already experimenting with Java8. Most developers will easily approach the platform if they already know Java programming language. However, Android offers a lot more to those developers that are dealing with heavy-duty, performance-oriented scenarios: Android Native API.
Native API
Native API gives the developers the opportunity to call native C, and partially C++, code from an Android Java application. Native code is compiled as standard ELF .so
files and stored in the app APK file. Being native code, it has to be compiled for every architecture we are going to support, because, contrary to the bytecode, it can't be built once and run on every architecture.
As integrators, we must embrace one or more Android Application Binary Interfaces (ABIs) and aim for having full compatibility with the Android NDK. Of course, Google provides guidelines and constraints to easily reach this goal. These are the basic rules for proper compatibility:
- Our implementation must include support for code running in the managed environment, that is Java code, to call into native code, using the standard Java Native Interface (JNI) semantics
- If our implementation supports the 64-bit ABI, we must support its relative 32-bit version, too, because we must provide compatibility to non-64 bit potential devices
- Google suggests that we build our implementation using the source code and header files available in the Android Open Source Project—just don't reinvent the wheel
From a libraries point of view, our implementation must be source-compatible (that is, header compatible) and binary-compatible (for the ABI) with all the following libraries:
- libc (C library)
- libm (math library)
- liblog (Android logging)
- libz (Zlib compression)
- libdl (dynamic linker)
- libGLESv1_CM.so (OpenGL ES 1.x)
- libGLESv2.so (OpenGL ES 2.0)
- libGLESv3.so (OpenGL ES 3.x)
- libEGL.so (native OpenGL surface management)
- libjnigraphics.so, libOpenSLES.so (OpenSL ES 1.0.1 audio support)
- libOpenMAXAL.so (OpenMAX AL 1.0.1 support)
- libandroid.so (native Android activity support)
- libmediandk.so (native media APIs support)
These libraries also provide minimal support for the C++ JNI interface as well as support for OpenGL.
An implementation of each one of these libraries must be present in our system to be compatible with Android NDK. This is a dynamic list and we cannot treat it as a definitive set of libraries: future versions of Android could add new libraries and increase development possibilities and scenarios. That's why native code compatibility is challenging. For this reason, Google strongly suggests to use the implementations of the libraries listed earlier from the Android Open Source Project, taking advantage of the Open Source philosophy of Android and to enjoy well-supported and well-tested source code.
Maintaining 32-bit support
Nowadays, all major manufactures are switching to 64-bit architecture and new ARMv8 architecture deprecates lots of old CPU operations. Unfortunately, the market is still full of 32-bit compatible software and even on 64-bit architecture we must still support these deprecated operations, to avoid scaring developers and losing precious market share. Fortunately, we can choose to make them available via real hardware support or software emulation, at the expense of performance.
Supporting 32-bit architecture can be very tricky. We can just think about one simple scenario, for example, accessing the /proc/cpuinfo
file. Legacy versions of the Android NDK used /proc/cpuinfo
to discover CPU features. For compatibility with applications built using 32-bit NDK, we must specifically include the following things in /proc/cpuinfo
when it is read by 32-bit ARM applications:
- Features: This is followed by a list of any optional ARMv7 CPU features supported by the device
- CPU architecture: This is followed by an integer describing the device's highest supported ARM architecture (for example, 8 for ARMv8 devices)
The tricky part is that these requirements only apply when /proc/cpuinfo
is read by 32-bit ARM applications. The file must be not altered when read by 64-bit ARM or non-ARM applications.