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

我正在开发一个使用NDKfunction的Android应用程序。 我的应用程序在AndroidManifest.xml中定义了android:minSdkVersionandroid:targetSdkVersion ,在jni / Application.mk中定义了APP_PLATFORM

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

两个问题:1)我是不是正确的? 2)是否APP_PLATFORM是更大的android:minSdkVersion ? 或者他们必须是平等的对方?

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

  1. android:minSdkVersion是您的应用程序所需的最低操作系统版本。

  2. android:targetSdkVersion实际上是您devise应用程序的最大操作系统版本。 这是一个如何工作的例子。 想象一下,你用API 19testing了你的应用,然后用android:targetSdkVersion = 19发布你的应用。 然后,Google决定发布API 20,以改变某些API的行为,但不希望改变老应用程序的行为(以防止破坏它们)。 所以,当你的应用程序启动时,Android会发现你的应用程序有targetSdkVersion = 19,所以它给你旧的API行为,但是如果其他应用程序说targetSdkVersion = 20,Android会给它新的API行为。

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

一般来说,使用比android:minSdkVersion更新的APP_PLATFORM值是没有意义的,除非你做了一些特殊的事情(比如注意不要在运行时检查版本来调用新的API,并且确保不要链接到新的API,而是使用dlopen / dlsym )。

因此,如果您使用APP_PLATFORM=13并且调用AMotionEvent_getAxisValue (不在早期的平台头文件中,意味着它在早期平台上的运行时不可用),那么您的应用将不会在API <13的设备上运行。如果AMotionEvent_getAxisValue在旧版本上实际可用,但它不在头文件/库文件中,或者只是没有logging。 但是我不知道这个特定的API是否是这种情况(基本上,这需要更多的研究和风险分析来决定是否依赖不支持的东西)。

这个答案汇集了来自许多优秀的网站难以find,但重要的信息和stackoverflow答案/评论在这个logging不足的NDK主题:

避免崩溃:NDK版本, minSdKVersiontargetSdkVersionAPP_PLATFORM (在project.properties中)

简短的版本

为了防止原生Android应用程序在老客户设备上崩溃,最简单的答案是使您的NDK APP_PLATFORM (在project.propertiesApplication.mk )与您的minSdkVersion (在AndroidManifest.xml )相同。

但是,根据您使用的NDKfunction,这样做可能会严重限制可以下载您的应用程序的一组客户。

要了解这是为什么,是否有其他select,请继续阅读…

四个不同的概念

  • NDK版本 (例如r10e,r13b):这是从Google下载的Android NDK版本(tar / zip文件)的版本。 这里是所有的NDK版本

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

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

  • APP_PLATFORM (例如15,19,21)是您在Native NDK项目中进行的设置。 通常,此设置位于project.properties根目录的project.properties文件中,作为target=行结尾的最后一个数字,例如target=Google Inc.:Google APIs:21级(通常为“21”是通过命令行调用android create projectandroid update project命令的-t/--target选项。 您也可以通过将APP_PLATFORM := android-21放入您的Application.mk文件来进行设置。

其中三个概念使用API​​级别号码

minSdkVersiontargetSdkVersionAPP_PLATFORM使用Android的API级别的一致编号scheme:

点击此处查看Google的API等级图表

正如你所看到的,这些级别大致对应于Android版本,但是甚至不能与Android版本的数量相匹配(这太容易了)。 例如,API级别10对应于Android OS 2.3.3和2.3.4。

“棒棒糖”和“牛轧糖”这些可爱的代码名称比API版本更粗糙。 例如,几个API版本(21和22)是“棒棒糖”

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

点击这里查看Build.VERSION_CODES

例如,22级是LOLLIPOP_MR1

每个概念并不是每个数字都存在。 例如,您可能希望支持API级别10,但是没有APP_PLATFORM 10,因此您可以返回上一个可用的APP_PLATFORM APP_PLATFORM 9。

点击此处查看已安装版本中API版本的分布情况

什么是minSdkVersion

这个设置是迄今为止最容易理解的。 当您将minSdkVersion设置为21(对应于Android OS 5.0)时,这意味着Google Play商店只会将您的应用宣传为支持Android OS 5.0及更高版本,Android会阻止用户安装您的应用(如果其Android设备已经低于Android OS 5.0安装。

所以minSdkVersion是你所支持的最低的操作系统。 好而简单。

这个设置显然对Java和C代码都有影响:你想把它设置成你的代码可以支持的最低的操作系统版本。

targetSdkVersionAPP_PLATFORM必须大于或等于minSdkVersion

什么是targetSdkVersion

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

有时,您希望自己的应用支持较旧的设备,但也可以使用仅适用于较新的Java API版本的新function。

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

通过设置targetSdkVersion为23,你正在与Android做一个简单的合同:

  • Android同意将您的应用程序代码访问仅限于API-23的Javafunction,如VoiceInteractor
  • 作为回报,您同意在运行时检查Java代码,在调用它之前,在客户设备上是否可以使用该function(否则您的应用程序将崩溃)。

这个契约是有效的,因为在Java中,如果你的代码引用了可能不存在于客户设备上的类/方法,那么就是“OK”,只要你不叫它们。

该合同适用于您的minSdkVersion之前添加的所有Android Javafunction,包括您的targetSdkVersion

除了允许您访问某些新的Java API之外, targetSdkVersion设置还会启用或禁用某些logging良好的兼容性行为:

点击这里查看每个targetSdkVersion行为变化

这些详细logging的变化也构成了一种契约。 例如,围绕Android 4,Google将他们的Android设备devise从具有专用菜单button的屏幕移开,并转向具有屏幕上的操作栏。 如果您的应用的targetSdkVersion低于14(Android 4.0.1),则Google会在屏幕上放置一个软件菜单button,以确保即使设备没有专用菜单button,您的应用也能继续工作。 但是通过在构build时select一个大于或等于14的targetSdkVersion ,你可以保证Google没有菜单或者使用Action栏,所以Google不再提供软件菜单button。

什么是APP_PLATFORM

APP_PLATFORMtargetSdkVersion在Java世界中执行的C NDK世界中执行类似的function。

但是,不幸的是,由于C语言的限制和Google的不良行为的结合, APP_PLATFORM显然更危险,并且坦率地说几乎不可用。

让我们从头开始…

APP_PLATFORM是一个NDK专用的设置,它告诉build-ndk工具你的NDK的哪个子目录查找某些关键的包含文件和库,它们统称为NDK“平台”。 每个NDK发行版(我们开发者从Google下载的每个NDK tar / zip)都包含多个平台。

例如,如果将APP_PLATFORM设置为android-21 ,则build-ndk将显示在:

 $(ndk_directory)/platforms/android-21/arch-$(architecture)/usr/include $(ndk_directory)/platforms/android-21/arch-$(architecture)/usr/lib 

包含文件和库。

如果您只需从Google的NDK下载网站下载zip / tar来安装NDK,那么$(ndk_directory)就是您解压文件的目录。

如果你先安装你的NDK,首先下载Android(Java)SDK,然后运行Android SDK Manager来安装“NDK”项目,那么$(ndk_directory)是$(sdk_directory)/ndk-bundle ,其中$(sdk_directory)是无论您的SDK安装在哪里。

$(architecture)armarm64x86

什么是“平台”?

$(ndk_directory)/platforms/android-XX目录包含两个超级重要的东西:

  • 你所有的C库调用,如fopen()atof()sprintf()等。Android上的C库称为“仿生”。
  • Android特定的NDK调用/types/定义像AInputQueueEGLContext

在不同的APP_PLATFORM级别有什么变化?

在每个android-XX版本中,Google都会向NDK添加更多的调用。 例如,

  • APP_PLATFORM API级别9添加了非常有用的NativeActivity
  • APP_PLATFORM API级别18添加了OpenGL ES 3.0

某些APP_PLATFORM版本也会将调用添加到C库中,和/或“修复”缺less的东西(例如,在APP_PLATFORM 21中添加了令牌PTHREAD_KEYS_MAX )。

点击这里阅读关于每个APP_PLATFORM级别更改内容的不完整文档

到目前为止,这与Java世界相似。 没有人期望Google或任何其他操作系统厂商能够在旧设备上提供所有新function,特别是当这些function依赖于仅在新设备​​(例如,更快的处理器,新的相机function,新的audiofunction)上才能find的硬件时。

但是Google的NDK团队做了一个淘气的东西,Java团队没有。

在某些APP_PLATFORM版本中,Google做出了APP_PLATFORM ,打破了API的变更,这些变更不能由上述任何合法的论点来解决

这些是Android Java开发人员绝不会接受的突发API更改的types。 例如,Google有

  • 改名为C库函数
  • 将C库函数从内联变为不内联

其中最严重的情况是APP_PLATFORM 21,Google做出了很多突破性的改变,产生了很多的APP_PLATFORM问题( 这里有很多例子和更多的例子)。

但是之前的APP_PLATFORM也有变化(例如API 19中的 signal() )。

APP_PLATFORM 24之后的APP_PLATFORM s甚至有一些改变,例如APP_PLATFORM 24(例如,在Karu在这个问题的评论中提到的std::vector::resize )。

所以这显然是一个糟糕的谷歌习惯,在这里留下来。

为什么这些更改会让我的应用在旧设备上崩溃?

要明白为什么这些顽皮的变化是一个问题,请记住,Android上的C库是一个共享库 ,这意味着像sprintf()这样的非内联非macros调用的实现不会被编译到您的程序中,而是存在于C库在您的testing设备和每个客户设备上。

所以它不仅仅是你开发环境中的API版本。 在您的应用可能运行的每个设备上,C库的API版本也是很重要的。

假设您的应用程序调用atof()并使用APP_PLATFORM 21构build应用程序,并在运行Android 5或更高版本(API版本21或更高版本)的现代testing设备上进行testing。 一切看起来不错。

然后,你释放你的应用程序,突然发现Android操作系统版本4.4及更早版本(API版本低于21)的大量客户报告你的应用崩溃在他们的设备上。

这是怎么回事?

APP_PLATFORM 21(Android 5)中, atof()是一个常规的(不是inline ,而不是macros)函数。 因此,应用程序的本地部分( ndk-build将创build的myapp.so文件,以及使用System.loadLibrary("myapp")从Java代码加载的文件)将被标记为具有对称为外部函数C库中的atof()。

当您在给定设备上运行应用程序时,Android将打开您的myapp.so ,查看atof()的依赖关系,并在该设备上的C库中查找atof()

但令人震惊的是,在21以前的APP_PLATFORMatof()是平台头文件中的inline函数,意思是说:

  • 它的实现(它的定义,它的代码,它的正文)在头文件中,并且在你构build你的应用时被编译到你的应用中
  • 在API <21(运行Android <5的任何客户设备)的任何客户设备上,C库中不存在atof()实现。 从来没有必要,因为atof()在那些日子内线。

因此,当您在运行API版本<21(Android OS <5)的设备上运行应用程序时,Java调用System.loadLibrary("myapp")失败,因为运行时加载程序无法find您的myapp.so所需的所有符号。 Android知道你的myapp.so需要atof()但是在设备的C库中找不到atof() 。 崩溃。

这个atof()例子只是众多的,没有logging的或者几乎没有文档记载的,Google公然称之为WorkingAsIntended的重大更改之一 。 除了atof() ,你可以find相同的原因(例如与mkfifo()和令人难以置信,甚至rand() )大量的其他计算器项目

我该如何解决?

在上面的atof()例子中,你可能会对自己说:“好吧,如果旧设备上没有atof() ,我会在我的应用程序中提供一个,并发送一个新的应用程序版本。

而事实上,这将工作。

但是当你意识到一个更重要的问题没有答案的时候,你就会有一种沉闷的感觉:

我怎么知道什么改变了,旧设备会受到什么影响?

这是真正的踢球手。 你不能。

与Android Java API不同的是,Google谨慎地维护与旧API的向后兼容性,清楚logging与targetSdkVersion参数targetSdkVersion任何行为更改,而Android NDK APP_PLATFORM级别没有这样的文档

像Java API一样,您可以查找NDK调用,并找出支持该调用的最早的API版本(最早的客户Android操作系统)。

但是,与具有targetSdkVersion的Java API targetSdkVersion ,当您更改NDK APP_PLATFORM级别时,将无法find任何Google文档来告诉您:

  • 存在哪些API更改(可能甚至是C库API更改),这些更改可能会在较早的设备上损坏您的应用 例如,像atof()mkfifo()rand()这样的函数列表,您需要为旧设备提供自己的实现
  • 不提供这些重新实现的例程的效果将会在现在可以支持的最低的Android操作系统上实现

简而言之,Google不会告诉您每个APP_PLATFORM支持的最早的Android版本。

如果你碰巧有很多老设备躺在地上和很多时间,你可以在每一个可能的旧版本的Android上试用你的应用程序,并查看丢失的C库符号是什么,为没有find的函数提供定制的实现。 当然,这只是第一个testing级别:实际上Google可能会在符号仍然存在的地方做出突破性改变(所以没有崩溃),但是调用的行为有所不同。 这在Java级别上是不会被接受的,但是由于某种原因,Google觉得有权使用NDK。

当然,没有人有时间这样做,开发人员也不应该这样做。

所以有效的是,这就是Google NDK的官方政策:

每当您增加项目的APP_PLATFORM时,您都可以访问新的API,但是也会发生一些突变,这些更改会导致您的应用在某些较旧的设备上崩溃。 哦,我们不会给你一个具体的变化清单。 我们也不会告诉你的应用程序仍然保证工作的最早的Android操作系统版本。

实际上, 意味着什么:

每次增加项目的APP_PLATFORM时,都必须将minSdkVersion设置为APP_PLATFORM从而阻止您的应用在较旧的设备上运行 。 否则,您的应用可能会在一些较旧的设备上崩溃

很难夸大这是多么悲惨。

谷歌有效地告诉你“为了使用新的NDKfunction,你必须放弃所有用旧设备的客户,并放弃将来的销售给老客户。”

为了使这个悲剧具体化一个真实世界的例子,请注意Google在API Level 21(Android OS 5.0)中增加了对OpenGL ES 3.1的支持。 假设您希望在新设备上支持新的OpenGL ES 3.1特性,但仍旧支持旧设备上的OpenGL ES 3.0( API级别18(Android OS 4.3) )和OpenGL ES 2.0( API级别5(Android OS 2.0) )。 这是一个非常可能的情况,因为(与OpenGL ES 1到2的转换不同),OpenGL ES 2到3的变化相当小且累积。

为了支持谷歌荒谬的NDK政策,从您的应用程序支持ES 3.1,您将不得不放弃支持Android5以下的所有设备。

有解决方法吗?

那种,但是不太可能有任何开发者有时间为他们。

上面提到了第一个解决方法:在每个可能的旧版Android上仔细testing你的应用程序,不仅仅是为了find未find符号的崩溃,而且还要改变行为。

第二个解决方法是,理论上,您可以将不同版本的NDK代码“发运”给不同API版本的客户。

最简单的方法可能是在NDK级别。 例如,您可以在NDK版本中构build多个myapp.so ,每个在Application.mk中都有一个不同的APP_PLATFORM值,并将它们全部捆绑到您的应用.apk 。 然后从你的Java代码,你可以根据客户的设备的API版本System.LoadLibrary()一个不同的。

在结构上与NDK开发人员目前为每个体系结构捆绑多个NDK版本(例如armeabiarmeabi-v7amipsx86armeabi-v7a

但是,实际上有一个很大的区别:不同于ndk-build可以在不浪费开发者时间的情况下免费提供的多个ABI,开发人员不得不花费大量的时间攻击NDK和Java构build脚本来创build和分发多个APP_PLATFORM .so版本。 然后,每当开发人员更改其C代码时,他们都必须仔细考虑每个API版本中他们所调用的每个函数的行为(如果它存在的话)。 对于直接与新硬件function直接相关的通话,这种工作是完全可以预料的,也是可以接受的,但是Android的NDK团队使我们能够像atof()rand()这样的通话完成这件事,这完全是荒谬的。

第三个解决方法是我怀疑大多数开发人员所做的:解决问题,因为愤怒的客户报告他们,并祈祷没有更多的这样的崩溃来到(或导致客户给他们的应用程序坏评论,从不向开发者报告问题) 。

dlsym()是一个非解决方法

您可以将C NDK APP_PLATFORM兼容性问题与更干净的Java targetSdkVersion然后说

“嘿,如果我可以设置targetSdkVersion ,然后在Java中运行时检查新function,我不能设置targetSdkVersion并在运行时检查C中的新function吗?

那么,不。

第一个问题是,为了在C中执行此操作,与Java不同,您必须避免在代码中引用例程。 然后你必须用dlopen()打开C库,然后尝试用dlsym()提取你想要的例程。 即使findC库,我们甚至不会考虑供应商设备依赖的可能性。 再加上由于Android突破性变化的复杂性,一些例程已经改变了名字,所以即使你查找的名字也要依赖于设备的API版本。

但是第二个更糟糕的问题是,有时你不是那个打电话的人。 正如我们下面将要解释的那样,编译器可能会将调用插入到Google已经破坏的例程stpcpy()stpcpy()std::vector::resize ,并且您无法用调用dlopen()来取代这些调用。和dlsym() 。 阻止编译器调用它们的唯一方法是减lessAPP_PLATFORM ,这就使得在兼容设备上访问新function的目的失败了。

将有一个更改的电话列表帮助吗?

令人惊讶的是,没有。 这个问题比听起来更糟糕。

假设谷歌确实发布了所有例程(如atof()的完整列表,并发生了重大变化。 你可以扫描你的代码,如果你不给他们打电话,你就安全了,对吧?

错误。

事实certificate,有时出血的编译器会调用那些破坏更改例程,而不会实际出现在代码中:

  • 如果您构build到APP_PLATFORM 21(Android 5)并在较旧的设备(Android <5)上运行,那么看到崩溃可能会很震惊,因为较旧的设备找不到stpcpy() ,这是您永远不会调用的例程。 事实certificate,编译器会在你的代码中注意到某些stpcpy()的模式,并用一个对stpcpy()的调用来代替它们! 这可以在许多计算器例子中看到: 例子1 例子2 例子3 例子4 。 如果您尝试自己实现stpcpy()以实现向后兼容性,除非您足够聪明才能以非stpcpy()方式实现它,否则将会发生无限循环! 疯狂。
  • 如果你构build到APP_PLATFORM 24,你会遇到一个类似的问题与std::vector::resize Karu在这个问题的评论中提到)。

在这两种情况下,编译器都会决定它可以插入这些调用,因为它检查了正在使用的包含文件的集合—您的APP_PLATFORM —并决定调用是否可用。 您不能减lessAPP_PLATFORM而不会失去对新设备上要使用的新例程的访问。 第二十二条军规。

Google怎么能摆脱这个呢?

综上所述,Google的有效NDK政策是:

每当您增加项目的APP_PLATFORM时,必须将minSdkVersion设置为APP_PLATFORM ,以防止您的应用在较旧的设备上运行 ,除非您愿意在旧设备上进行大规模的testing,或者祈祷。

我实在无法find一个官方的Google政策声明。 谷歌的官方文件正好相反。 特别是谷歌官方NDK级文档中的这段话完全是废话:

每个给定Android API级别的NDK头文件和库的新版本都是累积的; 如果在构build应用程序时使用最近发布的标题,则几乎总是安全的。 例如,您可以将Android API级别为21的NDK标头用于定位API级别16的应用。但是,您可以增加APK的占用空间。

事实上,事实恰恰相反:您必须使用API​​级别16的头文件来定位API级别的16级设备,否则,如果您使用atof()这样的大型API集合中的一个,并且没有文档化的重大更改,您的应用程序将会崩溃。

关于我们从Google获得的最多的帮助是一个神秘的NDK构build警告WARNING: APP_PLATFORM android-XX is larger than android:minSdkVersion没有任何相关的文档,我可以find。

我希望这个答案对开发者来说是开眼界和有帮助的,也许随着我们提高开发人员的意识,可能会激励Google NDK开发人员像Google Java开发人员一样尊重向后兼容性。

参考:很多很多的链接到其他的stackoverflow的答案和其他网页是交错在上面的文字。

你是对的最低限度。 我不知道目标应该代表什么,我认为它提供了目标SDK的一些function,但也确保该应用程序将与最低的SDK运行。