简体   繁体   English

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

[英]What is the relation between APP_PLATFORM, android:minSdkVersion and android:targetSdkVersion?

I'm developing an Android app that uses NDK features. 我正在开发使用NDK功能的Android应用程序。 My app defines android:minSdkVersion and android:targetSdkVersion in AndroidManifest.xml and APP_PLATFORM in jni/Application.mk. 我的应用在AndroidManifest.xml定义了android:minSdkVersionandroid:targetSdkVersion ,在jni / Application.mk中定义了APP_PLATFORM

My current understanding is that android:minSdkVersion decalres minimal supported OS version, android:targetSdkVersion declares Java library version to be linked against, and APP_PLATFORM declares C++ library to be linked against. 我目前的理解是android:minSdkVersion降低了支持的最低操作系统版本, android:targetSdkVersion声明了要链接的Java库版本,而APP_PLATFORM声明了要链接的C ++库。

Two questions: 两个问题:

  1. Is my understanding correct? 我的理解正确吗?

  2. Is it Ok for APP_PLATFORM to be greater that android:minSdkVersion ? APP_PLATFORM可以大于android:minSdkVersion吗? Or they must be equal each other? 还是必须彼此平等?

The reason for my question: I want my app to be available for devices with API >= 10 , but I need to use NDK functions (like AMotionEvent_getAxisValue ) that are defined in platforms\\android-13 folder in NDK. 我提出这个问题的原因:我希望我的应用程序可用于API> = 10的设备 ,但是我需要使用NDK中的platforms\\android-13文件夹中定义的NDK函数(例如AMotionEvent_getAxisValue )。 So I use android:minSdkVersion=10 and APP_PLATFORM=13 . 所以我用android:minSdkVersion=10APP_PLATFORM=13 Project compiles successfully, but would it be runnable on API 10-12 devices? 项目可以成功编译,但是可以在API 10-12设备上运行吗?

This answer brings together hard-to-find but important information from many excellent websites and stackoverflow answers/comments on this under-documented NDK subject: 这个答案汇集了来自许多优秀网站的难以找到但重要的信息,以及关于这个文档不足的NDK主题的stackoverflow答案/评论:

Avoiding Crashes: NDK Version, minSdKVersion , targetSdkVersion , and APP_PLATFORM (in project.properties) 避免崩溃:NDK版本, minSdKVersiontargetSdkVersionAPP_PLATFORM (在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 ). 为了防止您的本机Android应用神秘地崩溃在旧的客户设备上,简单的答案是使NDK APP_PLATFORM (位于project.propertiesApplication.mk )与minSdkVersion (位于AndroidManifest.xml )相同。

But depending on what NDK features you use, doing so may severely limit the set of customers who can download your app. 但是根据您使用的NDK功能,这样做可能会严重限制可以下载您的应用程序的客户群。

To find out why that is and whether you have other options, read on... 要找出原因,以及是否还有其他选择,请继续阅读...

Four different concepts 四个不同的概念

  • NDK Version (eg r10e, r13b): this is the version of the Android NDK release (the tar/zip file) you download from Google. NDK版本 (例如r10e,r13b):这是您从Google下载的Android NDK版本(tar / zip文件)的版本。 Here are all the NDK versions 这是所有NDK版本

  • minSdkVersion (eg 15,19,21) is a setting you make in your AndroidManifest.xml in a <uses-sdk> element under the <manifest> element. minSdkVersion (例如15,19,21)是您在AndroidManifest.xml中的<manifest>元素下的<manifest> <uses-sdk>元素中进行的设置。 This setting affects both NDK (native) and Java development. 此设置会影响NDK(本机)和Java开发。

  • targetSdkVersion (eg 15,19,21) is a different setting you make in your AndroidManifest.xml in a <uses-sdk> element under the <manifest> element. targetSdkVersion (例如15,19,21)是您在AndroidManifest.xml中的<manifest>元素下的<manifest> <uses-sdk>元素中进行的另一种设置。 This setting affects only Java development. 此设置仅影响Java开发。

  • APP_PLATFORM (eg 15,19,21) is a setting you make in your Native NDK project. APP_PLATFORM (例如15,19,21)是您在本机NDK项目中进行的设置。 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, eg 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). 通常,此设置位于project.properties根目录下的project.properties文件中,作为target=行末尾的最后一个数字,例如,对于21级, target=Google Inc.:Google APIs:21 21(通常采用以下方式“ 21”可以通过-t/--target选项到达android create projectandroid update project命令的命令行来实现。 You can also make this setting by putting APP_PLATFORM := android-21 into your Application.mk file. 您还可以通过将APP_PLATFORM := android-21放入Application.mk文件来进行此设置。

Three of these concepts use API Level Numbers 其中三个概念使用API​​级别编号

minSdkVersion , targetSdkVersion and APP_PLATFORM use Android's consistent numbering scheme for API levels: minSdkVersiontargetSdkVersionAPP_PLATFORM对API级别使用Android的一致编号方案:

Click here to see Google's API Level Chart 点击此处查看Google的API级别图表

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). 如您所见,级别大致与Android版本相对应,但与Android版本的数量甚至没有远程匹配(这太容易了)。 For example, API level 10 corresponds to Android OS 2.3.3 and 2.3.4. 例如,API级别10对应于Android OS 2.3.3和2.3.4。

The cute code-names like "Lollipop" and "Nougat" are at a coarser granularity than API versions. 可爱的代号,例如“ Lollipop”和“ Nougat”,比API版本的粒度更粗。 For example, several API versions (21 and 22) are "Lollipop" 例如,几个API版本(21和22)是“棒棒糖”

These same API version numbers are encoded by the Java Build.VERSION_CODES type: 这些相同的API版本号由Java Build.VERSION_CODES类型编码:

Click here to see Build.VERSION_CODES 点击此处查看Build.VERSION_CODES

For example, level 22 is LOLLIPOP_MR1 例如,级别22为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. 例如,您可能希望支持API级别10,但是没有APP_PLATFORM 10,因此您将返回到最后一个可用的APP_PLATFORMAPP_PLATFORM 9。

Click here to see distribution of API versions in the installed base in the wild 单击此处以在野外安装的基础中查看API版本的分发

What is minSdkVersion ? 什么是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. 当您将minSdkVersion设置为21(对应于Android OS 5.0)时,这意味着Google Play商店只会宣传您的应用程序支持Android OS 5.0及更高版本,并且Android将阻止用户安装其应用程序(如果他们的Android设备)低于安装的Android OS 5.0。

So minSdkVersion is the lowest OS you support. 因此, minSdkVersion是您支持的最低操作系统。 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. 显然,此设置对Java和C代码都有影响:您希望将其设置为代码的两个部分都可以支持的最低OS版本。

Both targetSdkVersion and APP_PLATFORM must be greater than or equal to minSdkVersion . targetSdkVersionAPP_PLATFORM必须大于或等于minSdkVersion

What is targetSdkVersion ? 什么是targetSdkVersion

This setting only has effect in the Android Java world (it has no effect on your C NDK code). 此设置仅在Android Java世界中有效(对您的C NDK代码无效)。 Its fulfills a purpose in the Android Java world that is similar to APP_PLATFORM in the Android C NDK world. 它在Android Java世界中APP_PLATFORM了与Android C NDK世界中的APP_PLATFORM相似的目的。

Sometimes you want your app to support older devices, but also use new features only available on newer Java API versions. 有时,您希望您的应用程序支持较旧的设备,但也使用仅在较新的Java API版本上可用的新功能。

For example, Android added a nifty Java VoiceInteractor API that's only supported in API 23 (Android 6.0) or later. 例如,Android添加了一个漂亮的Java VoiceInteractor API,该API仅在API 23(Android 6.0)或更高版本中受支持。 You might want to support VoiceInteractor if your customers have a new device, but still have your app run on older devices. 如果您的客户有新设备,但您的​​应用仍在旧设备上运行,则可能需要支持VoiceInteractor

By setting targetSdkVersion to 23, you are making a simple contract with Android: 通过将targetSdkVersion设置为23,您可以与Android签订一个简单的合同:

  • Android agrees to give your app code access to API-23-only Java features like VoiceInteractor 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). 作为回报,您同意在运行时在Java代码中检查该功能是否在客户的设备上可用,然后再调用它(否则您的应用程序将崩溃)。

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. 该合同之所以有效,是因为在Java中,只要您的代码中引用了客户设备上可能不存在的类/方法(只要您不调用它们),就可以。

The contract applies to all Android Java features added after your minSdkVersion up to and including your targetSdkVersion . 该合同适用于minSdkVersion直至targetSdkVersion包括您)添加的所有Android Java功能。

In addition to giving you access to certain new Java APIs, the targetSdkVersion setting also enables or disables certain well-documented compatibility behaviors: 除了允许您访问某些新的Java API外, targetSdkVersion设置还启用或禁用某些有据可查的兼容性行为:

Click here to see which behavior changes come with each targetSdkVersion 单击此处以查看每个目标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. 例如,围绕Android 4,Google将其Android设备设计从原来的专用菜单按钮转移到了屏幕上的操作栏。 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. 如果您的应用程序的targetSdkVersion低于14(Android 4.0.1),则Google会在屏幕上放置一个软件菜单按钮,以确保即使设备没有专用的菜单按钮,您的应用程序仍可以正常工作。 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. 但是,通过在构建时选择大于或等于14的targetSdkVersion ,可以向Google保证您没有菜单或使用操作栏,因此Google不再设置软件菜单按钮。

What is APP_PLATFORM ? 什么是APP_PLATFORM

APP_PLATFORM performs a similar function in the C NDK world that targetSdkVersion performs in the Java world. APP_PLATFORM在C NDK世界中执行与targetSdkVersion在Java世界中执行的功能相似的功能。

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. 但是,可悲的是,由于C语言的局限性和Google的不良行为的共同作用, APP_PLATFORM显然更加危险,并且坦率地说几乎无法使用。

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." APP_PLATFORM是仅NDK的设置,它告诉build-ndk工具在NDK的哪个子目录中查找某些键,包括文件和库,这些文件和库统称为NDK“平台”。 Each NDK distribution (each NDK tar/zip that we developers download from Google ) contains multiple platforms. 每个NDK发行版(我们开发人员从Google下载的每个NDK tar / zip)都包含多个平台。

For example, if you set APP_PLATFORM to android-21 , build-ndk will look in: 例如,如果将APP_PLATFORM设置为android-21build-ndk将显示在:

$(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. 如果您仅通过从Google的NDK下载网站下载zip / tar安装了NDK,则$(ndk_directory)就是您提取文件的目录。

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. 如果先通过下载Android(Java)SDK然后运行Android SDK Manager来安装NDK,然后运行Android SDK Manager来安装“ NDK”项,则$(ndk_directory)为$(sdk_directory)/ndk-bundle ,其中$(sdk_directory)为无论您的SDK安装在哪里。

$(architecture) is arm , arm64 , x86 , etc. $(architecture)armarm64x86等。

What is in a "platform"? 什么是“平台”?

The $(ndk_directory)/platforms/android-XX directory contains two super-important things: $(ndk_directory)/platforms/android-XX目录包含两个非常重要的内容:

  • all your C library calls like fopen() , atof() , sprintf() etc. The C library on Android is called "bionic." 您所有的C库都调用,例如fopen()atof()sprintf()等。Android上的C库称为“仿生”。
  • Android-specific NDK calls/types/defines like AInputQueue and EGLContext 特定于Android的NDK调用/类型/定义,例如AInputQueueEGLContext

What changes at different APP_PLATFORM levels? 在不同的APP_PLATFORM级别有哪些变化?

In each android-XX version, Google adds more calls to the NDK. 在每个android-XX版本中,Google都会向NDK添加更多调用。 For example, 例如,

  • APP_PLATFORM API level 9 added the very useful NativeActivity APP_PLATFORM API级别9添加了非常有用的NativeActivity
  • APP_PLATFORM API level 18 added OpenGL ES 3.0 APP_PLATFORM API级别18添加了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). 一些APP_PLATFORM版本还会将调用添加到C库,和/或“修复”缺少的内容(例如,在APP_PLATFORM 21中添加了令牌PTHREAD_KEYS_MAX )。

Click here to read Google's incomplete documentation on what changed in each APP_PLATFORM level 单击此处以阅读Google关于每个APP_PLATFORM级别更改内容的不完整文档

So far this is similar to the Java world. 到目前为止,这类似于Java世界。 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 (eg faster processors, new camera features, new audio features). 没有人期望Google或任何其他操作系统供应商在旧设备上提供所有新功能,特别是当这些功能依赖于仅在较新设备上找到的硬件(例如,更快的处理器,新的摄像头功能,新的音频功能)时。

But Google's NDK team did a naughty thing that the Java team did not. 但是Google的NDK团队做了一件淘气的事情,而Java团队却没有。

In some APP_PLATFORM versions, Google made gratuitous, breaking API changes that cannot possibly be excused by any legitimate argument such as those above. 在某些APP_PLATFORM版本中,Google做出了无偿的, APP_PLATFORM API更改,这些更改不能被上述任何合法参数所原谅

These are the types of breaking API changes that Android Java developers would never accept. 这些是Android Java开发人员永远不会接受的API重大更改类型。 For example, Google has 例如,谷歌有

  • renamed C library functions and 重命名的C库函数
  • changed C library functions from being inlined to being not inlined 将C库函数从内联更改为未内联

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). 最严重的情况是APP_PLATFORM 21,其中Google进行了许多重大更改,这些更改产生了非常多的APP_PLATFORM问题( 此处和以下更多示例)。

But there also have been changes in previous APP_PLATFORM s (eg signal() in API 19 ). 但是以前的APP_PLATFORM也有所变化(例如API 19中的 signal() )。

And there are even some breaking changes in APP_PLATFORM s after 21 such as APP_PLATFORM 24 (eg std::vector::resize as Karu mentions in a comment of this question ). 在21之后, APP_PLATFORM甚至还有一些重大更改,例如APP_PLATFORM 24(例如,正如Karu在此问题的评论中提到的那样, std::vector::resize )。

So this is clearly a bad Google habit that is here to stay. 因此,这显然是一个不良的Google习惯,这种习惯将持续下去。

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. 要了解为什么这些顽皮的更改会带来问题,请记住Android上的C库是共享库 ,这意味着非内联,非宏调用(如sprintf()的实现未编译到您的程序中,而是存在于程序中。测试设备和每个客户设备上的C库。

So it doesn't just matter what API version you have in your development environment. 因此,开发环境中的API版本并不重要。 It also matters what API version of C library is on each device where your app might run. 同样重要的是,您的应用可能会在运行的每台设备上使用C库的API版本。

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). 假设您的应用程序调用atof()并使用APP_PLATFORM 21构建应用程序,然后在运行Android 5或更高版本(API版本21或更高版本)的现代测试设备上对其进行测试。 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. 然后,您发布应用程序,突然发现大量使用Android OS 4.4及更早版本(API版本低于21)的客户报告您的应用程序在其设备上崩溃。

What's going on? 这是怎么回事?

In APP_PLATFORM 21 (Android 5), atof() is a regular (not inline , not macro) function. APP_PLATFORM 21(Android 5)中, atof()是常规函数(不是inline函数,不是宏)。 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. 因此,应用程序的本机部分( ndk-build将创建的myapp.so文件,以及使用System.loadLibrary("myapp")从Java代码加载的myapp.so文件)将被标记为依赖于称为C库中的atof()。

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. 在给定设备上运行应用程序时,Android将打开myapp.so ,查看对atof()的依赖关系,并在该设备上的C库中找到atof()

But the shock surprise is that in APP_PLATFORM s earlier than 21, atof() was an inline function in the platform header files, meaning that: 但是令人惊讶的是,在早于21的APP_PLATFORMatof()是平台头文件中的inline函数,这意味着:

  • 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). 在API <21的任何客户设备(运行Android <5的任何客户设备atof()上,C库中没有atof()实现。 There never needed to be, since atof() was inline back in those days. 因为atof()在当时是内联的,所以永远都不需要。

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 . 因此,当您在运行API版本<21(Android OS <5)的设备上运行应用程序时,Java调用System.loadLibrary("myapp")失败,因为运行时加载程序无法找到myapp.so所需的所有符号。 Android knows your myapp.so needs atof() but cannot find atof() in the C library on the device. Android知道您的myapp.so需要atof()但无法在设备的C库中找到atof() Crash. 崩溃

This atof() example is only one of many, undocumented or barely-documented breaking changes that Google brazenly refers to as WorkingAsIntended . 这个atof()示例只是Google无耻地称为WorkingAsIntended的许多未记录或几乎未记录的重大更改之一 Besides atof() , you can find huge numbers of other stackoverflow items with the same cause (eg with mkfifo() and, unbelievably, even rand() ) 除了atof() ,您还可以找到大量其他起因相同的其他stackoverflow项目(例如,使用mkfifo()甚至令人难以置信的是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." 在上面的atof()示例中,您可能对自己说:“好的,如果旧设备上没有atof() ,我将在我的应用程序中提供一个并发布新的应用程序版本。”

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. 与Android Java API不同,Google会仔细维护与旧API的向后兼容性,从而清楚地记录键入到targetSdkVersion参数的任何行为更改,而对于Android NDK APP_PLATFORM级别则没有此类文档

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. 与Java API一样,您可以查找NDK调用并找出支持该调用的最早的API版本(最早的客户Android OS)。

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: 但是,与带有targetSdkVersion的Java API targetSdkVersion ,当您更改NDK APP_PLATFORM级别时,将找不到任何Google文档,该文档告诉您:

  • what API changes (possibly even C library API changes) exist that might break your app on older devices. 存在哪些可能会在旧设备上破坏您的应用程序的API更改(甚至可能是C库API更改)。 For example, a list of functions like atof() , mkfifo() and rand() for which you would need to provide your own implementation for older devices 例如,一系列功能,如atof()mkfifo()rand() ,您需要为较旧的设备提供自己的实现
  • what effect not providing those re-implemented routines will have on the lowest Android OS that you can now support with your app 如果不提供这些重新实现的例程,将会对您现在可以在您的应用中支持的最低Android OS产生什么影响

Simply put, Google won't tell you the earliest Android version that each APP_PLATFORM supports. 简而言之,Google不会告诉您每个APP_PLATFORM支持的最早的Android版本。

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. 如果您碰巧有很多旧设备在闲逛,并且有很多时间,则可以在每个可能的旧Android版本上尝试使用您的应用,并查看由于缺少C库符号而崩溃的情况,并为未找到的功能提供自定义实现。 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. 当然,这只是测试的第一步:实际上,Google可以在符号仍然存在的地方进行重大更改(因此不会崩溃),但是调用的行为有所不同。 This would never be accepted at the Java level but for some reason Google feels entitled to do it with the NDK. 在Java级别上,这永远不会被接受,但是由于某种原因,谷歌觉得有权使用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: 如此有效的意思是,这就是Google NDK的官方政策:

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. 每次增加项目的APP_PLATFORM时,您都可以访问新的API,但是您也将获得一些重大更改,这些更改将导致您的应用在某些旧设备上崩溃。 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. 我们也不会告诉您仍然可以保证您的应用可以运行的最早的Android OS版本。

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 . 每次增加项目的APP_PLATFORM时,都必须将minSdkVersion设置为等于APP_PLATFORM ,以防止您的应用在旧设备上运行 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." Google有效地告诉您“要使用NDK的新功能,您必须放弃所有使用旧设备的客户,并放弃对使用旧设备的客户的未来销售。”

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) . 为了通过实际示例具体说明这一悲剧,请注意Google在API Level 21(Android OS 5.0)中添加了对OpenGL ES 3.1的支持。 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. 假设您想在新设备上支持新的OpenGL ES 3.1功能,但在旧设备上仍支持OpenGL ES 3.0( API级别18(Android OS 4.3) )和OpenGL ES 2.0( API级别5(Android OS 2.0) )。 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. 这是一种非常可能的情况,因为(与从OpenGL ES 1到2的过渡不同),在OpenGL ES 2到3中的更改非常小且是累积的。

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. 为了使用Google荒唐的NDK策略从您的应用程序中支持ES 3.1,您必须放弃对所有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. 上面提到了第一个解决方法:在每个可能的旧Android版本上仔细测试您的应用程序,不仅针对未找到符号的崩溃,而且还更改行为。

The second workaround is that you can, in theory, "ship" different versions of your NDK code to customers with different API versions. 第二种解决方法是,从理论上讲,您可以将不同版本的NDK代码“运送”给具有不同API版本的客户。

The easiest way is probably do that at the NDK level. 最简单的方法可能是在NDK级别执行此操作。 For example, you could build multiple myapp.so s in your NDK build, each with a different APP_PLATFORM value in Application.mk, and bundle all of them into your app .apk . 例如,您可以在NDK构建中构建多个myapp.so ,每个都在Application.mk中具有不同的APP_PLATFORM值,然后将它们全部捆绑到您的应用.apk Then from your Java code you could System.LoadLibrary() a different .so depending on the API version of the customer's device. 然后从Java代码中,可以根据客户设备的API版本,将System.LoadLibrary()不同的.so

This would be similar in structure to how NDK developers currently bundle multiple NDK versions for each architecture (eg armeabi , armeabi-v7a , mips , x86 ). 在结构上与NDK开发人员当前为每种体系结构捆绑多个NDK版本(例如armeabiarmeabi-v7amipsx86armeabi-v7a

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 create and distribute multiple APP_PLATFORM .so versions. 但是,存在巨大的实际差异:与ndk-build或多或少免费提供多个ABI而又不浪费开发人员时间的开发人员不同,开发人员将不得不花费大量时间破解NDK和Java构建脚本来创建和分发多个APP_PLATFORM .so版本。 Then every time developers change their C code, they must carefully consider how each function they call behaves (if it even exists) in each API version. 然后,每次开发人员更改C代码时,他们都必须仔细考虑在每个API版本中调用的每个函数的行为(如果存在)。 This kind of work is totally expected and acceptable for calls that relate directly to new hardware features, but it is totally ridiculous that Android's NDK team makes us do this for calls like atof() and rand() . 这种工作是完全可以预期的,并且对于与新硬件功能直接相关的调用是可以接受的,但是Android的NDK团队让我们对诸如atof()rand()类的调用执行此操作是完全荒谬的。

The third workaround is the one I suspect most developers do: fix problems as angry customers report them, and pray that there are not more such crashes to come (or causing customers to give their apps bad reviews and never reporting the issue to the developer). 第三种解决方法是我怀疑大多数开发人员都会采用的解决方法:修复生气的客户报告问题后,再祈祷不会再发生此类崩溃(或导致客户对其应用程序进行不良评价,并且永远不会将问题报告给开发人员) 。

dlsym() is a non-workaround dlsym()是一种非解决方法

You might compare the C NDK APP_PLATFORM compatibility issue to the much cleaner Java targetSdkVersion and say 您可以将C NDK APP_PLATFORM兼容性问题与更加干净的Java targetSdkVersion然后说

"hey, if I can set targetSdkVersion and then check for new features at runtime in Java, can't I set APP_PLATFORM and check for new features in runtime at C?" “嘿,如果我可以设置targetSdkVersion然后检查在Java运行时的新功能,我不能设置APP_PLATFORM并检查位于C运行时的新功能?”

Well, no. 好吧,不。

The first problem is that in order to do this in C, unlike in Java, you have to refrain from even referencing the routine in your code. 第一个问题是,与Java不同,为了在C中执行此操作,您必须避免甚至在代码中引用例程。 Then you'd have to open the C library with dlopen() and try to extract the routine you want with dlsym() . 然后,您必须使用dlopen()打开C库,并尝试使用dlsym()提取所需的例程。 Let's not even get into the likelihood of vendor device-dependency even finding the C library. 即使找到C库,我们也不要讨论供应商设备依赖性的可能性。 Plus the complexity that due to Android breaking changes, some routines have changed name so even the name you look up would have to depend on the API version of the device. 加上由于Android重大更改而带来的复杂性,某些例程已更改了名称,因此即使您查找的名称也必须取决于设备的API版本。

But the second, worse problem is that sometimes you are not the one making the call. 但是,第二个更严重的问题是,有时您不是打电话的那个人。 As we will explain just below, the compiler may insert calls to routines that Google has broken, like stpcpy() and std::vector::resize , and you are not in a position to replace these invocations with a call to dlopen() and dlsym() . 正如我们将在下面解释的那样,编译器可能会插入对Google中断的例程的调用,例如stpcpy()std::vector::resize ,并且您无法用对dlopen()调用来替换这些调用。和dlsym() The only way to prevent the compiler from calling them is to reduce APP_PLATFORM , and this defeats the purpose of accessing new features on compatible devices. 阻止编译器调用它们的唯一方法是减少APP_PLATFORM ,这APP_PLATFORM在兼容设备上访问新功能的目的。

Would having a list of changed calls help? 更改呼叫清单会有所帮助吗?

Amazingly, no. 令人惊讶的是,没有。 The problem is even worse than it sounds. 问题比听起来更糟。

Let's say hypothetically Google did publish a complete list of all routines like atof() with breaking changes. 假设我们假设Google 确实发布了所有程序的完整列表,例如atof()并进行了重大更改。 You could just scan your code for those routines, and if you don't call them, you're safe, right? 您可以只扫描代码中的这些例程,如果不调用它们,就很安全,对吗?

Wrong. 错误。

It turns out that sometimes the bleeding compiler will call those breaking-changes routines without them actually appearing in your code: 事实证明,有时出血的编译器会调用这些突破更改例程,而实际上它们不会出现在您的代码中:

  • if you build to APP_PLATFORM 21 (Android 5) and run on older devices (Android <5), you may be shocked to see crashes because the older devices cannot find stpcpy() , a routine you never call. 如果您构建到APP_PLATFORM 21(Android 5)并在较旧的设备(Android <5)上运行,您可能会惊讶地发现崩溃,因为较旧的设备无法找到您永远不会调用的stpcpy() It turns out the compiler will notice certain stpcpy() -like patterns in your code and replace them with a call to stpcpy() ! 事实证明,编译器会在您的代码中注意到某些stpcpy()的模式,并将其替换为对stpcpy()的调用! This can be seen in many stackoverflow examples: example 1 example 2 example 3 example 4 . 这可以在许多stackoverflow示例中看到: 示例1 示例2 示例3 示例4 If you attempt to implement stpcpy() yourself for backwards compatibility, you will get an infinite loop unless you are clever enough to implement it in a sufficiently non- stpcpy() way ! 如果您尝试自己实现stpcpy()以实现向后兼容性,则将获得无限循环,除非您足够聪明地以stpcpy()方式实现它! Insanity. 疯狂。
  • if you build to APP_PLATFORM 24 you will encounter a similar problem with std::vector::resize as Karu mentions in a comment of this question ). 如果您构建到APP_PLATFORM 24,您将遇到与std::vector::resize类似的问题,就像Karu在对此问题的评论中提到的那样。

In both cases, the compiler decides it can insert these calls because it inspects the set of include files you are using---your APP_PLATFORM ---and decides the calls are available. 在这两种情况下,编译器都决定可以插入这些调用,因为它检查了您正在使用的包含文件集-您的APP_PLATFORM并确定了这些调用可用。 You can't reduce APP_PLATFORM without losing access to the new routines you want to use on new devices. 在不失去对要在新设备上使用的新例程的访问权限的情况下,您不能减少APP_PLATFORM Catch-22. 赶上22。

How could Google get away with this? Google如何摆脱这种情况?

Summarizing the above, Google's effective NDK policy is: 综上所述,Google有效的NDK政策为:

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 , unless you are willing to do massive exhaustive testing on old devices, or pray. 每次增加项目的APP_PLATFORM时,都必须将minSdkVersion设置为等于APP_PLATFORM ,以防止您的应用程序在旧设备上运行 ,除非您愿意在旧设备上进行大量详尽的测试或祈祷。

I could never actually find an official Google policy statement to this effect. 实际上,我从来没有找到过官方的Google政策声明。 Google's official documentation says exactly the opposite. Google的官方文档说的恰恰相反。 In particular, this passage from Google's official NDK Levels documentation is utter nonsense: 特别是, 谷歌官方NDK级别文档中的这段话完全是胡说八道:

Each new release of NDK headers and libraries for a given Android API level is cumulative; 给定Android API级别的NDK头文件和库的每个新版本都是累积的; you are nearly always safe if you use the most recently released headers when building your app. 如果您在构建应用时使用最新发布的标头,则几乎总是安全的。 For example, you can use the NDK headers for Android API level 21 for an app targeting API level 16. By doing so, however, you increase your APK's footprint. 例如,您可以将Android API级别21的NDK标头用于定位API级别16的应用程序。但是,这样做会增加APK的占用空间。

In fact the exact opposite is true: it is imperative that you use API level 16 headers to target API level 16 devices, otherwise your app will crash if you use one of a large set of APIs like atof() with undocumented breaking changes. 实际上,事实恰恰相反:您必须使用API​​级别16标头来定位API级别16设备,否则,如果您使用诸如atof()类的大量API之一atof()记录重大更改,则应用程序将崩溃。

About the most help we ever got from Google was a cryptic NDK build warning WARNING: APP_PLATFORM android-XX is larger than android:minSdkVersion without any associated documentation that I could find. 我们从Google获得的最大帮助是一个神秘的NDK构建WARNING: APP_PLATFORM android-XX is larger than android:minSdkVersion没有任何我可以找到的相关文档。

I hope this answer has been eye-opening and helpful for developers, and perhaps as we increase awareness amongst developers it might motivate the Google NDK developers to respect backwards compatibility as much as the Google Java developers do. 我希望这个答案对开发人员大开眼界,并且对开发人员有所帮助,也许随着我们在开发人员中的知名度提高,它可能会激发Google NDK开发人员像向后兼容Java Java开发人员一样。

References: many, many links to other stackoverflow answers and other webpages are interleaved in the text above. 参考:上面的文本中插入了许多指向其他stackoverflow答案和其他网页的链接。

  1. android:minSdkVersion is the minimum OS version that your app expects. android:minSdkVersion是您的应用程序期望的最低操作系统版本。

  2. android:targetSdkVersion is essentially the maximum OS version that you've designed your app to work with. android:targetSdkVersion本质上是您设计应用程序可以使用的最高操作系统版本。 Here's an example of how this works. 这是一个如何工作的示例。 Imagine that you tested your app fine with API 19 and you release your app with android:targetSdkVersion =19. 假设您使用API​​ 19对应用程序进行了良好的测试,然后使用android:targetSdkVersion = 19发布了应用程序。 Then Google decides to release API 20 with a change in behavior of some API, but they don't want to change the behavior for old apps (to prevent from breaking them). 然后Google决定发布API 20并更改某些API的行为,但他们不想更改旧应用程序的行为(以防止破坏它们)。 So when your app starts up, Android sees that your app has targetSdkVersion =19, so it gives you the old API behavior, but if some other app says targetSdkVersion =20, Android will give it the new API behavior. 因此,当您的应用程序启动时,Android会发现您的应用程序具有targetSdkVersion = 19,因此它具有旧的API行为,但是如果其他一些应用程序将targetSdkVersion = 20,则Android将为其赋予新的API行为。

  3. APP_PLATFORM is the version of the native headers and libraries that the NDK will compile your native code with. APP_PLATFORM是NDK用来编译本机代码的本APP_PLATFORM和库的版本。 If you set APP_PLATFORM to a specific value and you use APIs that are only available in that platform version, then your app will not run properly on older platforms. 如果将APP_PLATFORM设置为特定值,并且使用仅在该平台版本中可用的API,则您的应用将无法在旧平台上正常运行。 So APP_PLATFORM is a minimum value. 因此APP_PLATFORM是最小值。 The solution is to use a lower value and not use those newer APIs, or to write code that decides at runtime whether to call the new APIs or not (and probably use dlopen / dlsym ). 解决方案是使用较低的值而不使用那些较新的API,或者编写在运行时决定是否调用新API的代码(可能使用dlopen / dlsym )。

It seems like in general it doesn't make sense to use an APP_PLATFORM value newer than android:minSdkVersion , unless you're doing some special (like being careful not to call new APIs by checking the version at runtime, plus making sure not to link to new APIs and instead using dlopen / dlsym ). 通常来说,使用比android:minSdkVersion更新的APP_PLATFORM值似乎没有任何意义,除非您做了一些特殊的操作(例如注意不要通过在运行时检查版本来调用新的API,并确保不要链接到新的API,而是使用dlopen / dlsym )。

So if you use APP_PLATFORM=13 and you call AMotionEvent_getAxisValue (which is not in earlier platform headers, implying that it isn't available at runtime on earlier platforms), your app will not run on devices with API < 13. The one caveat would be if AMotionEvent_getAxisValue is actually available on older versions, but it just wasn't in the header/library files or it just wasn't documented. 因此,如果您使用APP_PLATFORM=13并调用AMotionEvent_getAxisValue (在较早的平台头文件中不存在,这意味着它在较早的平台上无法在运行时使用),则您的应用程序将无法在API <13的设备上运行。如果AMotionEvent_getAxisValue实际上在较早版本上可用,但是它不在头文件/库文件中,或者未在文档中记录。 But I don't know if that's the case for this particular API (basically, that would require more research and risk analysis of whether you want to depend on something unsupported). 但是我不知道特定的API是否会出现这种情况(基本上,这将需要更多的研究和风险分析,以确定是否要依赖不受支持的内容)。

You are right about the minimum. 您对最低要求是正确的。 I am not sure what the target should represent, I think it gives some features of the target SDK but also makes sure the app will run with the minimum SDK. 我不确定目标应该代表什么,我认为它提供了目标SDK的某些功能,但也确保该应用程序将以最低的SDK运行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 android:minSdkVersion,android:targetSdkVersion和“target”之间的关系 - Relation between android:minSdkVersion, android:targetSdkVersion and “target” Android NDK:警告:APP_PLATFORM android-9比android:minSdkVersion 8大 - Android NDK: WARNING: APP_PLATFORM android-9 is larger than android:minSdkVersion 8 Android:minSdkVersion与targetSdkVersion之间的关系 - Android: Relationship between minSdkVersion and targetSdkVersion Android Studio minSdkVersion、targetSdkVersion和minSdk、targetSdk有什么区别? - Android Studio What is the difference between minSdkVersion, targetSdkVersion and minSdk, targetSdk? Android风格的应用程序minSdkVersion =“ 8” targetSdkVersion =“ 21” - Android style app minSdkVersion=“8” targetSdkVersion=“21” 使用targetSdkVersion为25和minSdkVersion 19构建Android应用程序 - Building an Android app with targetSdkVersion of 25 and minSdkVersion 19 Android SDK minSdkVersion 和 targetSdkVersion - Android SDK minSdkVersion and targetSdkVersion 通过Android NDK编译时默认使用的APP_PLATFORM - What APP_PLATFORM used by default when compiled through Android NDK Android Studio minSdkVersion和targetSdkVersion警告 - Android Studio minSdkVersion and targetSdkVersion warning Android在Google Play上发布应用的优化提示(检查targetSdkVersion和minSdkVersion) - android publish app on google play optimization tips (check targetSdkVersion and minSdkVersion)
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM