This answer brings together hard-to-find but important information from many excellent websites and stackoverflow answers/comments on this under-documented NDK subject:
Avoiding Crashes: NDK Version, minSdKVersion
, targetSdkVersion
, and APP_PLATFORM
(in project.properties)
The short version
To prevent your native Android app from mysteriously crashing on older customer devices, the short answer is to make your NDK APP_PLATFORM
(in project.properties
or Application.mk
) the same as your minSdkVersion
(in AndroidManifest.xml
).
But depending on what NDK features you use, doing so may severely limit the set of customers who can download your app.
To find out why that is and whether you have other options, read on...
Four different concepts
NDK Version (e.g. r10e, r13b): this is the version of the Android NDK release (the tar/zip file) you download from Google. Here are all the NDK versions
minSdkVersion
(e.g. 15,19,21) is a setting you make in your AndroidManifest.xml in a <uses-sdk>
element under the <manifest>
element. This setting affects both NDK (native) and Java development.
targetSdkVersion
(e.g. 15,19,21) is a different setting you make in your AndroidManifest.xml in a <uses-sdk>
element under the <manifest>
element. This setting affects only Java development.
APP_PLATFORM
(e.g. 15,19,21) is a setting you make in your Native NDK project. Typically this setting is found in the project.properties
file at the root of your project as the last number at the end of the target=
line, e.g. target=Google Inc.:Google APIs:21
for level 21 (and usually the way that "21" got there is by being the -t/--target
option to a command-line invocation of the android create project
or android update project
command). You can also make this setting by putting APP_PLATFORM := android-21
into your Application.mk
file.
Three of these concepts use API Level Numbers
minSdkVersion
, targetSdkVersion
and APP_PLATFORM
use Android's consistent numbering scheme for API levels:
Click here to see Google's API Level Chart
As you can see, the levels roughly correspond to Android releases, but do not even remotely match the number of the Android release (that would be too easy). For example, API level 10 corresponds to Android OS 2.3.3 and 2.3.4.
The cute code-names like "Lollipop" and "Nougat" are at a coarser granularity than API versions. For example, several API versions (21 and 22) are "Lollipop"
These same API version numbers are encoded by the Java Build.VERSION_CODES
type:
Click here to see Build.VERSION_CODES
For example, level 22 is LOLLIPOP_MR1
Not every number exists for each concept. For example, you might want to support API level 10, but there is no APP_PLATFORM
10 so you would go back to the last available APP_PLATFORM
, APP_PLATFORM
9.
Click here to see distribution of API versions in the installed base in the wild
What is minSdkVersion
?
This setting is by far the easiest to understand. When you set minSdkVersion
to, say, 21 (corresponding to Android OS 5.0), that means that the Google Play Store will only advertise your app as supporting Android OS 5.0 and beyond, and Android will prevent users from installing your app if their Android device has lower than Android OS 5.0 installed.
So minSdkVersion
is the lowest OS you support. Nice and simple.
This setting obviously has implications both for Java and C code: you want to set it to the lowest OS version that both parts of your code can support.
Both targetSdkVersion
and APP_PLATFORM
must be greater than or equal to minSdkVersion
.
What is targetSdkVersion
?
This setting only has effect in the Android Java world (it has no effect on your C NDK code). Its fulfills a purpose in the Android Java world that is similar to APP_PLATFORM
in the Android C NDK world.
Sometimes you want your app to support older devices, but also use new features only available on newer Java API versions.
For example, Android added a nifty Java VoiceInteractor
API that's only supported in API 23 (Android 6.0) or later. You might want to support VoiceInteractor
if your customers have a new device, but still have your app run on older devices.
By setting targetSdkVersion
to 23, you are making a simple contract with Android:
- Android agrees to give your app code access to API-23-only Java features like
VoiceInteractor
- In return, you agree to check, at runtime in your Java code, whether that feature is available on the customer's device before calling it (otherwise your app will crash).
This contract works because in Java, it is "ok" if your code has references to classes/methods that might not exist on the customer's device, as long as you don't call them.
The contract applies to all Android Java features added after your minSdkVersion
up to and including your targetSdkVersion
.
In addition to giving you access to certain new Java APIs, the targetSdkVersion
setting also enables or disables certain well-documented compatibility behaviors:
Click here to see which behavior changes come with each targetSdkVersion
These well-documented changes also form a kind of contract. For example, around Android 4, Google migrated their Android device designs away from having a dedicated menu button and towards having an on-screen Action bar. If your app has a targetSdkVersion
lower than 14 (Android 4.0.1), Google would put a software menu button up on the screen to make sure your app kept working even if the device didn't have a dedicated menu button. But by choosing a targetSdkVersion
greater than or equal to 14 at build time, you are promising Google that you either don't have a menu or you use an Action bar, so Google no longer puts up the software menu button.
What is APP_PLATFORM
?
APP_PLATFORM
performs a similar function in the C NDK world that targetSdkVersion
performs in the Java world.
But, sadly, due to a combination of limitations in the C language and bad behavior by Google, APP_PLATFORM
is significantly more dangerous and, frankly, nearly unusable.
Let's start from the beginning...
APP_PLATFORM
is an NDK-only setting that tells the build-ndk
tool which subdirectory of your NDK to look in for certain key include files and libraries which collectively are referred to as an NDK "platform." Each NDK distribution (each NDK tar/zip that we developers download from Google) contains multiple platforms.
For example, if you set APP_PLATFORM
to android-21
, build-ndk
will look in:
$(ndk_directory)/platforms/android-21/arch-$(architecture)/usr/include
$(ndk_directory)/platforms/android-21/arch-$(architecture)/usr/lib
for include files and libraries.
If you installed your NDK by simply downloading a zip/tar from Google's NDK Downloads Website, then $(ndk_directory)
is simply the directory where you extracted the file.
If you installed your NDK by first downloading the Android (Java) SDK and then running the Android SDK Manager to install the "NDK" item, then $(ndk_directory) is $(sdk_directory)/ndk-bundle
, where $(sdk_directory)
is wherever your SDK is installed.
$(architecture)
is arm
, arm64
, x86
, etc.
What is in a "platform"?
The $(ndk_directory)/platforms/android-XX
directory contains two super-important things:
- all your C library calls like
fopen()
, atof()
, sprintf()
etc. The C library on Android is called "bionic."
- Android-specific NDK calls/types/defines like
AInputQueue
and EGLContext
What changes at different APP_PLATFORM
levels?
In each android-XX
version, Google adds more calls to the NDK. For example,
APP_PLATFORM
API level 9 added the very useful NativeActivity
APP_PLATFORM
API level 18 added OpenGL ES 3.0
Some APP_PLATFORM
versions also add calls to the C library, and/or "fix" things that are missing (for example, the token PTHREAD_KEYS_MAX
got added in APP_PLATFORM
21).
Click here to read Google's incomplete documentation on what changed in each APP_PLATFORM
level
So far this is similar to the Java world. Nobody expects Google or any other OS vendor to make every new feature available on old devices, especially when those features rely on hardware only found on newer devices (e.g. faster processors, new camera features, new audio features).
But Google's NDK team did a naughty thing that the Java team did not.
In some APP_PLATFORM
versions, Google made gratuitous, breaking API changes that cannot possibly be excused by any legitimate argument such as those above.
These are the types of breaking API changes that Android Java developers would never accept. For example, Google has
- renamed C library functions and
- changed C library functions from being inlined to being not inlined
The most serious case of this was APP_PLATFORM
21, where Google made many breaking changes that generated an extremely high number of stackoverflow issues (many examples <a href="https://stackoverflow.com/questions/31194260/android-nd