APP_PLATFORM,android:minSdkVersion和android:targetSdkVersion之间有什么关系?

Nic*_*ick 14 android android-ndk

我正在开发一个使用NDK功能的Android应用.我的应用程序定义android:minSdkVersion,并android:targetSdkVersionAndroidManifest.xmlAPP_PLATFORM在JNI/Application.mk.

我目前的理解是android:minSdkVersiondecalres支持的最小操作系统版本,android:targetSdkVersion声明要链接的Java库版本,并APP_PLATFORM声明要链接的C++库.

两个问题:

  1. 我的理解是否正确?

  2. 是否APP_PLATFORM更好android:minSdkVersion呢?或者他们必须彼此平等?

我的问题的原因:我希望我的应用程序可用于API> = 10的设备,但我需要使用在NDK AMotionEvent_getAxisValue中的platforms\android-13文件夹中定义的NDK函数(如).所以我用android:minSdkVersion=10APP_PLATFORM=13.项目编译成功,但是它可以在API 10-12设备上运行吗?

Lou*_*ini 25

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版本(例如r10e,r13b):这是您从Google下载的Android NDK版本(tar/zip文件)的版本. 以下是所有NDK版本

  • minSdkVersion(例如15,19,21)是您在AndroidManifest.xml中在<uses-sdk>元素下的<manifest>元素中创建的设置.此设置会影响NDK(本机)和Java开发.

  • targetSdkVersion(例如15,19,21)是您在AndroidManifest.xml中在<uses-sdk>元素下的<manifest>元素中创建的不同设置.此设置仅影响Java开发.

  • 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

正如您所看到的,这些级别大致对应于Android版本,但是甚至没有远程匹配Android版本的数量(这太简单了).例如,API级别10对应于Android OS 2.3.3和2.3.4.

像"Lollipop"和"Nougat"这样可爱的代码名称比API版本更粗糙.例如,几个API版本(21和22)是"Lollipop"

这些相同的API版本号由Java Build.VERSION_CODES类型编码:

单击此处查看Build.VERSION_CODES

例如,22级是 LOLLIPOP_MR1

并非每个概念都存在每个数字.例如,您可能要支持API级别10,但没有APP_PLATFORM10那么你会回到最后一个可用APP_PLATFORM,APP_PLATFORM9.

单击此处以查看已安装的基础中的API版本的分布

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?

此设置仅在Android Java世界中有效(它对您的C NDK代码没有影响).它实现了Android Java世界APP_PLATFORM中与Android C NDK世界类似的目的 .

有时您希望您的应用程序支持旧设备,但也使用仅在较新的Java API版本上提供的新功能.

例如,Android添加了一个漂亮的Java VoiceInteractorAPI,仅在API 23(Android 6.0)或更高版本中受支持.VoiceInteractor如果您的客户拥有新设备,但您的​​应用仍在旧设备上运行,则可能需要支持.

通过设置targetSdkVersion为23,您与Android签订了简单的合同:

  • Android同意让您的应用代码访问仅限API-23的Java功能 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
Run Code Online (Sandbox Code Playgroud)

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 here and more below).

But there also have been changes in previous APP_PLATFORMs (e.g. signal() in API 19).

And there are even some breaking changes in APP_PLATFORMs after 21 such as APP_PLATFORM 24 (e.g. std::vector::resize as Karu mentions in a comment of this question).

So this is clearly a bad Google habit that is here to stay.

Why do these changes make my app crash on old devices?

To see why these naughty changes are a problem, remember that the C library on Android is a shared library, meaning that the implementation of non-inline, non-macro calls like sprintf() is not compiled into your program but rather present in the C library on your test devices and on each customer device.

So it doesn't just matter what API version you have in your development environment. It also matters what API version of C library is on each device where your app might run.

Suppose your app calls atof() and you build your app with APP_PLATFORM 21 and test it on your modern test devices that run Android 5 or later (API version 21 or later). Everything looks ok.

Then you release your app and suddenly find tons of customers with Android OS versions 4.4 and earlier (API versions less than 21) report your app crashing on their devices.

What's going on?

In APP_PLATFORM 21 (Android 5), atof() is a regular (not inline, not macro) function. So the native part of your app (the myapp.so file that ndk-build will create, and that you load from your Java code using System.loadLibrary("myapp")) will be marked as having a dependency on an external function called atof() in the C library.

When you run your app on a given device, Android will open your myapp.so, see the dependency on atof(), and find atof() in the C library on that device.

But the shock surprise is that in APP_PLATFORMs earlier than 21, atof() was an inline function in the platform header files, meaning that:

  • its implementation (its definition, its code, its body) was in the header file and got compiled into your app when you built your app
  • there is no atof() implementation in the C library on any customer device with API < 21 (any customer device running Android < 5). There never needed to be, since atof() was inline back in those days.

So when you run your app on devices running API version < 21 (Android OS < 5), the Java call System.loadLibrary("myapp") fails because the run-time loader cannot find all the symbols needed by your myapp.so. Android knows your myapp.so needs atof() but cannot find atof() in the C library on the device. Crash.

This atof() example is only one of many, undocumented or barely-documented breaking changes that Google brazenly refers to as WorkingAsIntended. Besides atof(), you can find huge numbers of other stackoverflow items with the same cause (e.g. with mkfifo() and, unbelievably, even rand())

How can I fix it?

In the atof() example above, you might say to yourself "okay, if there is no atof() on older devices, I'll provide one in my app and ship a new app version."

And in fact that would work.

But you'll have a sinking feeling in your stomach when you realize there is no answer to a much more important question:

How can I know what changed, and what old devices will be affected?

Here's the real kicker. You can't.

Unlike the Android Java API, where Google carefully maintains backwards compatibility with old APIs, clearly documenting any behavior changes that are keyed to the targetSdkVersion parameters, there is no such documentation for Android NDK APP_PLATFORM levels.

Like the Java API, you can look up an NDK call and find out what is the earliest API version (the earliest customer Android OS) where that call is supported.

But unlike the Java API with targetSdkVersion, when you change your NDK APP_PLATFORM level, you will not be able to find any Google documentation that tells you:

  • what API changes (possibly even C library API changes) exist that might break your app on older devices. For example, a list of functions like atof(), mkfifo() and rand() for which you would need to provide your own implementation for older devices
  • what effect not providing those re-implemented routines will have on the lowest Android OS that you can now support with your app

Simply put, Google won't tell you the earliest Android version that each APP_PLATFORM supports.

If you happen to have a lot of old devices lying around and a lot of time, you could try your app on every possible old Android version and see what crashes with missing C library symbols, providing custom implementations for functions that are not found. Of course that's only the first level of testing: in reality Google could have made breaking changes where the symbol is still there (so no crash), but the call behaves differently. This would never be accepted at the Java level but for some reason Google feels entitled to do it with the NDK.

Of course, nobody has time to do this, nor should developers have to.

So effectively what that means is that this is the official Google NDK policy:

Every time you increase the APP_PLATFORM of your project, you get access to new APIs, but you also get some breaking changes that will cause your app to crash on some older devices. Oh, and we're not going to give you a specific list of those changes. Nor are we going to tell you the earliest Android OS version on which your app is still guaranteed to work.

And effectively what that means is:

Every time you increase the APP_PLATFORM of your project, you have to set minSdkVersion equal to APP_PLATFORM, preventing your app from running on older devices. Otherwise your app may crash on some older devices.

It is hard to overstate how tragic this is.

Google is effectively telling you "in order to use new NDK features, you must abandon all your customers with old devices and abandon future sales to customers with older devices."

To make this tragedy concrete with a real-world example, notice that Google added support for OpenGL ES 3.1 in API Level 21 (Android OS 5.0). Suppose you wanted to support new OpenGL ES 3.1 features on new devices, but still support OpenGL ES 3.0 (API level 18 (Android OS 4.3)) and OpenGL ES 2.0 (API level 5 (Android OS 2.0)) on older devices. This is a very likely scenario since (unlike the transition from OpenGL ES 1 to 2) the changes in OpenGL ES 2 to 3 are quite minor and cumulative.

In order to support ES 3.1 from your app with Google's absurd NDK policy, you would have to drop support for all devices with less than Android 5.

Are there workarounds?

Kind of, but it's unlikely any developer has the time for them.

The first workaround was mentioned above: carefully test your app on every possible old Android version, not only for symbol-not-found crashes but behavior changes too.

The second workaround is that you can, in theory, "ship" different versions of your NDK code to customers with different API versions.

The easiest way is probably do that at the NDK level. For example, you could build multiple myapp.sos in your NDK build, each with a different APP_PLATFORM value in Application.mk, and bundle all of them into your app .apk. Then from your Java code you could System.LoadLibrary() a different .so depending on the API version of the customer's device.

This would be similar in structure to how NDK developers currently bundle multiple NDK versions for each architecture (e.g. armeabi, armeabi-v7a, mips, x86).

However, there is a massive practical difference: unlike the multiple ABIs which ndk-build more or less provides for free without wasting developer time, the developer would have to spend a lot of time hacking both the NDK and Java build scripts to c


Xar*_*rgs 23

  1. android:minSdkVersion 是您的应用所期望的最低操作系统版本.

  2. android:targetSdkVersion本质上是您为应用程序设计的最大操作系统版本.这是一个如何工作的例子.想象一下,您使用API​​ 19测试了您的应用程序,并且您使用android:targetSdkVersion= 19 发布了应用程序.然后,Google决定通过更改某些API的行为来发布API 20,但他们不希望更改旧应用的行为(以防止破坏它们).因此,当您的应用启动时,Android会看到您的应用已经targetSdkVersion= 19,因此它会为您提供旧的API行为,但如果某个其他应用显示targetSdkVersion= 20,则Android会为其提供新的API行为.

  3. APP_PLATFORM是NDK将使用本机代码编译的本机头文件和库的版本.如果您设置APP_PLATFORM为特定值并且您使用仅在该平台版本中可用的API,那么您的应用将无法在较旧的平台上正常运行.所以APP_PLATFORM是最小值.解决方案是使用较低的值而不使用那些较新的API,或者编写在运行时决定是否调用新API的代码(并且可能使用dlopen/ dlsym).

看起来通常使用APP_PLATFORM比新值更新的值是没有意义的android:minSdkVersion,除非你做了一些特殊的事情(比如小心不要通过在运行时检查版本来调用新的API,另外确保不要链接到新的API)而是使用dlopen/ dlsym).

因此,如果您使用APP_PLATFORM=13并且您调用AMotionEvent_getAxisValue(这不是在早期平台标头中,暗示它在早期平台上不可用),您的应用程序将无法在API <13的设备上运行.一个警告是,如果AMotionEvent_getAxisValue实际上是在旧版本上可用,但它不在头文件/库文件中,或者它没有记录.但是我不知道这个特定的API是否就是这种情况(基本上,这需要更多的研究和风险分析,你是否想要依赖不支持的东西).

  • 请注意,对于本机代码,您不必使用新API使代码与较旧的Android版本不兼容.仅设置APP_PLATFORM太高会导致编译器决定使用不存在的原语(例如,如果APP_PLATFORM> = 24,则调用std :: vector :: resize maxe编译器会生成不适用于Lollipop的代码).简而言之,让APP_PLATFORM高于minSdkVersion是危险的(因为它默认为SDK版本,你正在构建非本机代码,你应该总是明确地设置它). (2认同)