Welche Beziehung besteht zwischen APP_PLATFORM, android: minSdkVersion und android: targetSdkVersion?

8

Ich entwickle eine Android-App mit NDK-Funktionen. Meine App definiert android:minSdkVersion und android:targetSdkVersion in AndroidManifest.xml und APP_PLATFORM in jni / Application.mk.

Mein derzeitiges Verständnis ist, dass android:minSdkVersion decalres die minimal unterstützte Betriebssystemversion ist, android:targetSdkVersion deklariert die Java-Bibliotheksversion, mit der verlinkt werden soll und APP_PLATFORM erklärt, dass die C ++ - Bibliothek verknüpft werden soll.

Zwei Fragen: 1) Ist mein Verständnis korrekt? 2) Ist es ok für APP_PLATFORM größer als android:minSdkVersion ? Oder müssen sie einander gleich sein?

Der Grund für meine Frage: Ich möchte, dass meine App für Geräte mit API & gt; = 10 verfügbar ist, aber ich muss NDK-Funktionen (wie AMotionEvent_getAxisValue ) verwenden, die in platforms\android-13 in NDK definiert sind. Also verwende ich android:minSdkVersion=10 und APP_PLATFORM=13 . Projekt wird erfolgreich kompiliert, aber wäre es auf API 10-12-Geräten lauffähig?

    
Nick 19.02.2014, 17:34
quelle

3 Antworten

19
  1. android:minSdkVersion ist die minimale Betriebssystemversion, die von Ihrer App erwartet wird.

  2. android:targetSdkVersion ist im Wesentlichen die maximale Betriebssystemversion, mit der Sie Ihre App entwickelt haben. Hier ist ein Beispiel, wie das funktioniert. Stellen Sie sich vor, Sie haben Ihre App mit API 19 getestet und veröffentlichen Ihre App mit android:targetSdkVersion = 19. Dann beschließt Google, API 20 mit einer Verhaltensänderung einiger APIs zu veröffentlichen, aber sie möchten das Verhalten für alte Apps nicht ändern (um zu verhindern, dass sie unterbrochen werden). Wenn Ihre App gestartet wird, erkennt Android, dass Ihre App targetSdkVersion = 19 hat. Dadurch erhalten Sie das alte API-Verhalten. Wenn jedoch eine andere App targetSdkVersion = 20 angibt, wird Android das neue API-Verhalten geben.

  3. APP_PLATFORM ist die Version der systemeigenen Header und Bibliotheken, mit denen der NDK den systemeigenen Code kompiliert. Wenn Sie APP_PLATFORM auf einen bestimmten Wert setzen und Sie APIs verwenden, die nur in dieser Plattformversion verfügbar sind, wird Ihre App auf älteren Plattformen nicht ordnungsgemäß ausgeführt. Also APP_PLATFORM ist ein Minimalwert. Die Lösung besteht darin, einen niedrigeren Wert zu verwenden und diese neueren APIs nicht zu verwenden oder Code zu schreiben, der zur Laufzeit entscheidet, ob die neuen APIs aufgerufen werden sollen (und wahrscheinlich dlopen / dlsym verwenden).

Es scheint, dass es im Allgemeinen keinen Sinn macht, einen APP_PLATFORM value neuer als android:minSdkVersion zu verwenden, es sei denn, Sie machen etwas Besonderes (z. B. achten Sie darauf, keine neuen APIs aufzurufen, indem Sie die Version zur Laufzeit überprüfen.) Außerdem stellen Sie sicher, dass Sie keine neuen APIs verwenden und stattdessen dlopen / dlsym ) verwenden.

Wenn Sie also APP_PLATFORM=13 verwenden und AMotionEvent_getAxisValue aufrufen (was nicht in früheren Plattformheadern enthalten ist, was bedeutet, dass es zur Laufzeit auf früheren Plattformen nicht verfügbar ist), wird Ihre App nicht auf Geräten mit API & lt; 13. Der einzige Nachteil wäre, wenn AMotionEvent_getAxisValue tatsächlich auf älteren Versionen verfügbar wäre, aber es war einfach nicht in den Header / Library-Dateien oder es wurde einfach nicht dokumentiert. Aber ich weiß nicht, ob das für diese bestimmte API der Fall ist (im Grunde würde das mehr Forschung und Risikoanalyse erfordern, ob Sie sich auf etwas verlassen wollen, das nicht unterstützt wird).

    
Xargs 24.02.2014, 08:52
quelle
15

Diese Antwort enthält schwer zu findende, aber wichtige Informationen von vielen hervorragenden Websites und Stackoverflow-Antworten / Kommentare zu diesem wenig dokumentierten NDK-Thema:

Vermeidung von Abstürzen: NDK-Version, minSdKVersion , targetSdkVersion und APP_PLATFORM (in project.properties)

Die kurze Version

Um zu verhindern, dass Ihre native Android-App auf älteren Geräten abstürzt, lautet die kurze Antwort, dass Ihr NDK APP_PLATFORM (in project.properties oder Application.mk ) dasselbe ist wie Ihr minSdkVersion (in AndroidManifest.xml ).

Je nachdem, welche NDK-Funktionen Sie verwenden, kann dies jedoch die Anzahl der Kunden, die Ihre App herunterladen können, stark einschränken.

Um herauszufinden, warum das so ist und ob Sie andere Optionen haben, lesen Sie weiter ...

Vier verschiedene Konzepte

  • NDK-Version (z. B. r10e, r13b): Dies ist die Version der Android NDK-Version (die tar / zip-Datei), die Sie von Google herunterladen. Hier sind alle NDK-Versionen

  • minSdkVersion (z. B. 15,19,21) ist eine Einstellung, die Sie in Ihrer AndroidManifest.xml in einem <uses-sdk> Element unter dem <manifest> -Element. Diese Einstellung betrifft sowohl die NDK- (native) als auch die Java-Entwicklung.

  • targetSdkVersion (z. B. 15,19,21) ist eine andere Einstellung, die Sie in Ihrer AndroidManifest.xml in einem <uses-sdk> Element unter dem Element <manifest> . Diese Einstellung betrifft nur die Java-Entwicklung.

  • APP_PLATFORM (z. B. 15,19,21) ist eine Einstellung, die Sie in Ihrem systemeigenen NDK-Projekt vornehmen. In der Regel befindet sich diese Einstellung in der Datei project.properties im Stammverzeichnis Ihres Projekts als letzte Zahl am Ende der Zeile target= , z. target=Google Inc.:Google APIs:21 für Level 21 (und normalerweise ist die Art, wie "21" dort ist, indem es die Option -t/--target für einen Kommandozeilenaufruf des Befehls android create project oder android update project ist). Sie können diese Einstellung auch vornehmen, indem Sie APP_PLATFORM := android-21 in Ihre Application.mk -Datei einfügen.

Drei dieser Konzepte verwenden API-Ebenennummern

minSdkVersion , targetSdkVersion und APP_PLATFORM verwenden das einheitliche Nummernschema von Android für API-Ebenen:

Klicken Sie hier, um das API-Level-Diagramm von Google zu sehen

>

Wie Sie sehen, entsprechen die Levels in etwa Android-Versionen, entsprechen aber nicht einmal annähernd der Nummer der Android-Version (das wäre zu einfach). Zum Beispiel entspricht API Level 10 Android OS 2.3.3 und 2.3.4.

Die niedlichen Code-Namen wie "Lollipop" und "Nougat" haben eine gröbere Granularität als API-Versionen. Zum Beispiel sind mehrere API-Versionen (21 und 22) "Lollipop"

Dieselben API-Versionsnummern werden vom Typ Java Build.VERSION_CODES codiert:

Klicken Sie hier, um Build.VERSION_CODES anzuzeigen

Zum Beispiel ist Stufe 22 LOLLIPOP_MR1

Nicht jede Nummer existiert für jedes Konzept. Zum Beispiel möchten Sie möglicherweise die API-Stufe 10 unterstützen, aber es gibt keine APP_PLATFORM 10, so dass Sie zu der letzten verfügbaren APP_PLATFORM , APP_PLATFORM 9 zurückkehren würden.

Klicken Sie hier, um die Verteilung von API-Versionen in der installierten Basis in freier Wildbahn anzuzeigen

Was ist minSdkVersion ?

Diese Einstellung ist bei weitem am einfachsten zu verstehen. Wenn Sie minSdkVersion beispielsweise auf 21 (entsprechend Android OS 5.0) setzen, bedeutet dies, dass der Google Play Store nur für Ihre App Android 5.0 und höher unterstützt und Android verhindert, dass Nutzer Ihre App installieren Android-Gerät ist niedriger als Android OS 5.0 installiert.

So minSdkVersion ist das niedrigste Betriebssystem, das Sie unterstützen. Schön und einfach.

Diese Einstellung hat offensichtlich Auswirkungen sowohl auf Java als auch auf C-Code: Sie möchten sie auf die niedrigste Betriebssystemversion einstellen, die beide Teile Ihres Codes unterstützen können.

Sowohl targetSdkVersion als auch APP_PLATFORM müssen größer oder gleich minSdkVersion sein.

Was ist targetSdkVersion ?

Diese Einstellung wirkt sich nur in der Android Java-Welt aus (sie hat keine Auswirkungen auf Ihren C NDK-Code). Es erfüllt einen Zweck in der Android Java Welt, der APP_PLATFORM in der Android C NDK Welt ähnlich ist.

Manchmal möchten Sie, dass Ihre App ältere Geräte unterstützt, aber auch neue Funktionen, die nur in neueren Java-API-Versionen verfügbar sind.

Zum Beispiel hat Android eine raffinierte Java VoiceInteractor API hinzugefügt, die nur in der API unterstützt wird 23 (Android 6.0) oder später. Möglicherweise möchten Sie VoiceInteractor unterstützen, wenn Ihre Kunden ein neues Gerät haben, Ihre App jedoch weiterhin auf älteren Geräten ausgeführt wird.

Wenn Sie targetSdkVersion auf 23 setzen, machen Sie einen einfachen Vertrag mit Android:

  • Android erklärt sich bereit, Ihrem App-Code Zugriff auf Java-Funktionen zu geben, die nur API-23 haben, wie zum Beispiel VoiceInteractor
  • Im Gegenzug erklären Sie sich damit einverstanden, zur Laufzeit in Ihrem Java-Code zu prüfen, ob diese Funktion auf dem Gerät des Kunden verfügbar ist, bevor Sie sie aufrufen (andernfalls stürzt Ihre App ab).

Dieser Vertrag funktioniert, weil es in Java "ok" ist, wenn Ihr Code Verweise auf Klassen / Methoden enthält, die möglicherweise nicht auf dem Gerät des Kunden vorhanden sind, solange Sie sie nicht aufrufen.

Der Vertrag gilt für alle Android Java-Funktionen, die nach Ihrem minSdkVersion bis einschließlich Ihrem targetSdkVersion hinzugefügt wurden.

Zusätzlich zum Zugriff auf bestimmte neue Java-APIs aktiviert oder deaktiviert die Einstellung targetSdkVersion auch bestimmte gut dokumentierte Kompatibilitätsverhalten:

Klicken Sie hier, um zu sehen, welche Verhaltensänderungen mit jedem targetSdkVersion

Diese gut dokumentierten Änderungen bilden auch eine Art Vertrag. Bei Android 4 migrierte Google zum Beispiel seine Android-Geräte-Designs von einer dedizierten Menüschaltfläche weg und hin zu einer Aktionsleiste auf dem Bildschirm. Wenn Ihre App ein targetSdkVersion niedriger als 14 (Android 4.0.1) hat, würde Google eine Softwaremenü-Schaltfläche auf dem Bildschirm platzieren, um sicherzustellen, dass Ihre App auch dann funktioniert, wenn das Gerät keine dedizierte Menüschaltfläche hat. Wenn Sie jedoch zum Zeitpunkt der Erstellung ein targetSdkVersion größer oder gleich 14 wählen, versprechen Sie Google, dass Sie entweder kein Menü haben oder eine Aktionsleiste verwenden, sodass Google die Softwaremenü-Schaltfläche nicht mehr anzeigt.

Was ist APP_PLATFORM ?

APP_PLATFORM führt eine ähnliche Funktion in der C NDK-Welt aus, die targetSdkVersion in der Java-Welt ausführt.

Leider ist APP_PLATFORM aufgrund einer Kombination von Einschränkungen in der C-Sprache und schlechtem Verhalten von Google wesentlich gefährlicher und, ehrlich gesagt, fast unbenutzbar.

Fangen wir von vorne an ...

APP_PLATFORM ist eine Nur-NDK-Einstellung, die dem build-ndk -Werkzeug mitteilt, welches Unterverzeichnis Ihres NDK nach bestimmten Schlüssel-Include-Dateien und -Bibliotheken sucht, die gemeinsam als NDK- "Plattform" bezeichnet werden. Jede NDK-Distribution (jede NDK tar / zip, die wir Entwickler von Google herunterladen ) enthält mehrere Plattformen.

Wenn Sie beispielsweise APP_PLATFORM auf android-21 setzen, wird build-ndk nachsehen:

%Vor%

für Include-Dateien und Bibliotheken.

Wenn Sie Ihr NDK installiert haben, indem Sie einfach eine Zip / Tar-Datei von der NDK-Website von Google herunterladen , dann $(ndk_directory) ist einfach das Verzeichnis, in das Sie die Datei extrahiert haben.

Wenn Sie Ihr NDK installiert haben, indem Sie zunächst das Android (Java) SDK heruntergeladen und anschließend den Android SDK Manager ausgeführt haben, ist $ (ndk_directory) $(sdk_directory)/ndk-bundle , wobei $(sdk_directory) für Ihr SDK steht ist installiert.

$(architecture) ist arm , arm64 , x86 usw.

Was ist in einer "Plattform"?

Das Verzeichnis $(ndk_directory)/platforms/android-XX enthält zwei sehr wichtige Dinge:

  • alle Ihre C-Bibliotheksaufrufe wie fopen() , atof() , sprintf() usw. Die C-Bibliothek auf Android heißt "bionic."
  • Android-spezifische NDK-Aufrufe / Typen / Definitionen wie AInputQueue und EGLContext

Was ändert sich bei verschiedenen APP_PLATFORM Ebenen?

In jeder android-XX -Version fügt Google dem NDK weitere Aufrufe hinzu. Zum Beispiel

  • APP_PLATFORM API Level 9 fügte die sehr nützliche NativeActivity
  • hinzu
  • APP_PLATFORM API Level 18 hinzugefügt OpenGL ES 3.0

Einige APP_PLATFORM -Versionen fügen auch Aufrufe an die C-Bibliothek hinzu und / oder "reparieren" Dinge, die fehlen (zum Beispiel wurde das Token PTHREAD_KEYS_MAX in APP_PLATFORM 21 hinzugefügt).

Klicken Sie hier, um die unvollständige Dokumentation von Google zu den Änderungen in APP_PLATFORM level zu lesen.

>

Bisher ist das ähnlich wie in der Java-Welt. Niemand erwartet von Google oder einem anderen Betriebssystemhersteller, dass alle neuen Funktionen auf alten Geräten verfügbar sind, insbesondere wenn diese Funktionen auf Hardware basieren, die nur auf neueren Geräten zu finden ist (z. B. schnellere Prozessoren, neue Kamerafunktionen, neue Audiofunktionen).

Aber das NDK-Team von Google hat etwas Unartiges getan, was das Java-Team nicht getan hat.

In einigen APP_PLATFORM -Versionen hat Google unbegründete API-Änderungen vorgenommen, die nicht durch irgendein legitimes Argument wie die oben genannten entschuldigt werden können

Dies sind die Arten von Breaking-API-Änderungen, die Android-Java-Entwickler niemals akzeptieren würden.Zum Beispiel hat Google

  • umbenannt in C-Bibliotheksfunktionen und
  • hat die C-Bibliotheksfunktionen von inlined zu nicht inlined geändert

Der schwerwiegendste Fall war APP_PLATFORM 21, wo Google viele Änderungen vorgenommen hat, die eine extrem hohe Anzahl von Stackoverflow-Problemen zur Folge hatten (viele Beispiele hier und mehr unten).

Aber es gab auch Änderungen in früheren APP_PLATFORM s (zB signal() in API 19 ).

Und es gibt sogar einige brechende Änderungen in APP_PLATFORM s nach 21 wie APP_PLATFORM 24 (zB std::vector::resize , wie Karu in einem Kommentar von diese Frage ).

Also das ist eindeutig eine schlechte Google-Gewohnheit, die hier zu bleiben ist.

Warum führen diese Änderungen dazu, dass meine App auf alten Geräten abstürzt?

Um zu sehen, warum diese ungezogenen Änderungen ein Problem darstellen, denken Sie daran, dass die C-Bibliothek auf Android eine shared library ist, was bedeutet, dass die Implementierung von Nicht-Inline-, Nicht-Makroaufrufen wie sprintf() ist nicht in Ihrem Programm kompiliert, sondern in der C-Bibliothek auf Ihren Testgeräten und auf jedem Kundengerät vorhanden.

Es ist also nicht egal, welche API-Version Sie in Ihrer Entwicklungsumgebung haben. Es spielt auch eine Rolle, welche API-Version der C-Bibliothek auf jedem Gerät vorhanden ist, auf dem die App ausgeführt wird.

Angenommen, Ihre App ruft atof() auf und Sie erstellen Ihre App mit APP_PLATFORM 21 und testen sie auf Ihren modernen Testgeräten mit Android 5 oder höher (API-Version 21 oder höher). Alles sieht gut aus.

Dann geben Sie Ihre App frei und finden plötzlich Millionen von Kunden mit Android-Betriebssystemversionen 4.4 und früher (API-Versionen unter 21), die einen Absturz Ihrer App auf ihren Geräten melden.

Was ist los?

In APP_PLATFORM 21 (Android 5) ist atof() eine reguläre Funktion (nicht inline , nicht Makro). Der native Teil Ihrer App (die myapp.so -Datei, die ndk-build erstellt und die Sie aus Ihrem Java-Code mit System.loadLibrary("myapp") laden) wird als abhängig von einer externen Funktion namens atof () in der C-Bibliothek.

Wenn Sie Ihre App auf einem bestimmten Gerät ausführen, wird Android Ihr myapp.so öffnen, die Abhängigkeit von atof() sehen und atof() in der C-Bibliothek auf diesem Gerät finden.

Aber die Schocküberraschung ist, dass in APP_PLATFORM s vor 21 atof() eine inline -Funktion in den Plattform-Header-Dateien war, was bedeutet:

  • seine Implementierung (seine Definition, sein Code, sein Körper) war in der Header-Datei und wurde in Ihre App kompiliert, als Sie Ihre App erstellt haben
  • Es gibt keine atof() -Implementierung in der C-Bibliothek auf einem Kundengerät mit API & lt; 21 (jedes Kundengerät mit Android & lt; 5). Es musste nie sein, da atof() in diesen Tagen inline war.

Wenn Sie Ihre App auf Geräten ausführen, auf denen die API-Version & lt; 21 (Android OS & lt; 5), schlägt der Java-Aufruf System.loadLibrary("myapp") fehl, weil der Laufzeitlader nicht alle Symbole finden kann, die von Ihrem myapp.so benötigt werden. Android kennt Ihre myapp.so benötigt atof() , kann aber atof() nicht in der C-Bibliothek auf dem Gerät finden. Absturz.

Dieses atof() Beispiel ist nur eine von vielen, undokumentierten oder kaum dokumentierten Änderungen, die Google schamlos als WorkingAsIntended . Außerdem atof() , Sie können eine große Anzahl von anderen stackoverflow-Elementen mit demselben finden Ursache (zB mit mkfifo() und, unglaublich , auch rand() )

Wie kann ich das beheben?

Im obigen Beispiel atof() könnten Sie sich selbst sagen: "Okay, wenn auf älteren Geräten kein atof() vorhanden ist, werde ich einen in meiner App bereitstellen und eine neue App-Version versenden."

Und in der Tat würde das funktionieren.

Aber Sie werden ein sinkendes Gefühl in Ihrem Magen haben, wenn Sie erkennen, dass es keine Antwort auf eine viel wichtigere Frage gibt:

Wie kann ich wissen, was sich geändert hat und welche alten Geräte betroffen sind?

Hier ist der wahre Kicker. Sie können nicht.

Im Gegensatz zur Android-Java-API, bei der Google die Abwärtskompatibilität zu alten APIs sorgfältig aufrechterhält und alle Verhaltensänderungen, die für die Parameter targetSdkVersion festgelegt sind, eindeutig dokumentiert, gibt es keine solche Dokumentation Android NDK APP_PLATFORM levels.

Wie die Java-API können Sie einen NDK-Aufruf nachschlagen und herausfinden, welche die früheste API-Version (das erste Android-Betriebssystem des Kunden) ist, auf der dieser Aufruf unterstützt wird.

Aber im Gegensatz zur Java-API mit targetSdkVersion , wenn Sie Ihre NDK APP_PLATFORM -Ebene ändern, werden Sie keine Google-Dokumentation finden, die Ihnen sagt:

  • Welche API-Änderungen (möglicherweise sogar C-Bibliotheks-API-Änderungen) existieren, die Ihre App auf älteren Geräten beschädigen könnten. Zum Beispiel eine Liste von Funktionen wie atof() , mkfifo() und rand() , für die Sie Ihre eigene Implementierung für ältere Geräte bereitstellen müssten
  • welchen Effekt die Bereitstellung dieser neu implementierten Routinen nicht auf das niedrigste Android-Betriebssystem hat, das Sie jetzt mit Ihrer App unterstützen können

Einfach gesagt, Google wird Ihnen nicht die früheste Android-Version nennen, die jedes APP_PLATFORM unterstützt.

Wenn Sie eine Menge alter Geräte herumliegen haben und eine Menge Zeit haben, könnten Sie Ihre App auf jeder möglichen alten Android-Version ausprobieren und sehen, was mit fehlenden C-Bibliothekssymbolen abstürzt benutzerdefinierte Implementierungen für Funktionen, die nicht gefunden werden. Das ist natürlich nur die erste Teststufe: In der Realität hätte Google brechende Änderungen vornehmen können, wo das Symbol immer noch vorhanden ist (also keinen Absturz), aber der Anruf verhält sich anders. Dies würde niemals auf Java-Ebene akzeptiert werden, aber Google fühlt sich aus irgendeinem Grund berechtigt, dies mit dem NDK zu tun.

Natürlich hat niemand Zeit, dies zu tun, und Entwickler müssen das auch nicht.

Das bedeutet effektiv, dass dies die offizielle Google NDK-Richtlinie ist:

  

Jedes Mal, wenn Sie APP_PLATFORM Ihres Projekts erhöhen, erhalten Sie Zugriff auf neue APIs, aber Sie erhalten auch einige wichtige Änderungen, die dazu führen, dass Ihre App auf einigen älteren Geräten abstürzt. Oh, und wir werden Ihnen keine spezifische Liste dieser Änderungen geben. Wir werden Ihnen auch nicht die früheste Version des Android-Betriebssystems nennen, auf der Ihre App immer noch funktioniert.

Und was genau das bedeutet, ist:

  

Jedes Mal, wenn Sie APP_PLATFORM Ihres Projekts erhöhen, müssen Sie minSdkVersion auf APP_PLATFORM setzen, verhindern, dass Ihre App auf älteren Geräten ausgeführt wird . Andernfalls kann Ihre App auf einigen älteren Geräten abstürzen.

Es ist schwer zu übertreiben, wie tragisch das ist.

Google sagt Ihnen effektiv Folgendes: "Um neue NDK-Funktionen nutzen zu können, müssen Sie alle Ihre Kunden mit alten Geräten aufgeben und zukünftige Verkäufe an Kunden mit älteren Geräten aufgeben."

Um diese Tragödie anhand eines realen Beispiels zu konkretisieren, beachten Sie, dass Google Unterstützung für OpenGL ES 3.1 in API Level 21 (Android OS 5.0) . Angenommen, Sie wollten neue OpenGL ES 3.1-Funktionen auf neuen Geräten unterstützen, aber OpenGL ES 3.0 ( API-Stufe 18) weiterhin unterstützen (Android OS 4.3) ) und OpenGL ES 2.0 ( API-Stufe 5 (Android OS 2.0) <) / a>) auf älteren Geräten. Dies ist ein sehr wahrscheinliches Szenario, da (im Gegensatz zum Übergang von OpenGL ES 1 zu 2) die Änderungen in OpenGL ES 2 zu 3 ziemlich gering und kumulativ sind.

Um ES 3.1 von Ihrer App aus mit der absurden NDK-Richtlinie von Google zu unterstützen, müssen Sie die Unterstützung für alle Geräte mit weniger als Android 5 einstellen.

Gibt es Problemumgehungen?

Irgendwie, aber es ist unwahrscheinlich, dass Entwickler Zeit für sie haben.

Der erste Workaround wurde oben erwähnt: Testen Sie Ihre App sorgfältig auf allen möglichen alten Android-Versionen, nicht nur bei nicht gefundenen Abstürzen, sondern auch bei Verhaltensänderungen.

Die zweite Problemumgehung besteht darin, dass Sie theoretisch verschiedene Versionen Ihres NDK-Codes an Kunden mit unterschiedlichen API-Versionen "versenden" können.

Der einfachste Weg ist wahrscheinlich das auf NDK-Ebene. Beispielsweise könnten Sie in Ihrem NDK-Build mehrere myapp.so s erstellen, jede mit einem anderen APP_PLATFORM -Wert in Application.mk, und alle in der App .apk bündeln. Dann könnten Sie von Ihrem Java-Code abhängig von der API-Version des Kundengeräts System.LoadLibrary() ein anderes .so haben.

Dies wäre ähnlich in der Struktur , wie NDK-Entwickler derzeit mehrere NDK-Versionen für jede Architektur bündeln (z. B. armeabi , armeabi-v7a , mips , x86 ).

Es gibt jedoch einen massiven praktischen Unterschied: im Gegensatz zu den mehreren ABIs, die ndk-build mehr oder weniger kostenlos zur Verfügung stellt, ohne Entwicklerzeit zu verschwenden, müsste der Entwickler viel Zeit damit verbringen, beide zu hacken die NDK- und Java-Build-Skripte zum Erstellen und Verteilen mehrerer APP_PLATFORM .so -Versionen. Jedes Mal, wenn Entwickler ihren C-Code ändern, müssen sie sorgfältig abwägen, wie sich jede aufgerufene Funktion in jeder API-Version verhält (falls sie überhaupt existiert). Diese Art von Arbeit wird bei Anrufen, die sich direkt auf neue Hardware-Funktionen beziehen, völlig erwartet und akzeptabel, aber es ist völlig lächerlich, dass das NDK-Team von Android uns dies für Aufrufe wie atof() und rand() tun lässt.

Die dritte Problemumgehung ist die, von der ich vermute, dass die meisten Entwickler Probleme beheben, wenn verärgerte Kunden sie melden, und bete , dass es nicht mehr solche Abstürze geben wird (oder Kunden ihre Apps schlecht machen) Bewertungen und nie das Problem an den Entwickler melden).

dlsym () ist eine Nichtumgehung

Sie könnten das Kompatibilitätsproblem C NDK APP_PLATFORM mit dem viel saubereren Java targetSdkVersion vergleichen und sagen

  

"Hey, wenn ich targetSdkVersion einstellen und dann nach neuen Features zur Laufzeit in Java suchen kann, kann ich APP_PLATFORM nicht setzen und nach neuen Features in Runtime bei C suchen?"

Nun, nein.

Das erste Problem besteht darin, dass Sie, um dies in C zu tun, im Gegensatz zu Java nicht einmal auf die Routine in Ihrem Code verweisen müssen. Dann müssten Sie die C-Bibliothek mit dlopen() öffnen und versuchen, die gewünschte Routine mit dlsym() zu extrahieren. Lassen Sie uns nicht einmal auf die Wahrscheinlichkeit der Geräteabhängigkeit von Anbietern eingehen, selbst wenn wir die C-Bibliothek finden. Plus die Komplexität, die aufgrund von Android brechen Änderungen, einige Routinen haben den Namen geändert so Selbst der Name, den Sie suchen, müsste von der API-Version des Geräts abhängen.

Aber das zweite, schlimmere Problem ist, dass Sie manchmal nicht den Anruf tätigen. Wie unten erklärt, kann der Compiler Aufrufe von Routinen einfügen, die von Google durchbrochen wurden, wie stpcpy() und std::vector::resize , und Sie sind nicht in der Lage, diese Aufrufe durch einen Aufruf von dlopen() und dlsym() zu ersetzen. . Der einzige Weg zu verhindern, dass der Compiler sie aufruft, ist APP_PLATFORM zu reduzieren, und dies verhindert den Zweck des Zugriffs auf neue Funktionen auf kompatiblen Geräten.

Helfen Sie mit einer Liste geänderter Anrufe?

Erstaunlicherweise nein. Das Problem ist noch schlimmer, als es sich anhört.

Sagen wir hypothetisch Google hat eine vollständige Liste aller Routinen wie atof() mit brechenden Änderungen veröffentlicht. Sie könnten einfach Ihren Code für diese Routinen scannen, und wenn Sie sie nicht anrufen, sind Sie in Sicherheit, oder?

Falsch.

Es stellt sich heraus, dass manchmal der blutende Compiler diese breaking-changes-Routinen aufruft, ohne dass sie tatsächlich in Ihrem Code erscheinen:

  • Wenn Sie auf APP_PLATFORM 21 (Android 5) bauen und auf älteren Geräten (Android & lt; 5) laufen, sind Sie vielleicht schockiert, da die älteren Geräte stpcpy() nicht finden können, eine Routine, die Sie nie aufrufen. Es stellt sich heraus, dass der Compiler bestimmte stpcpy() -ähnliche Muster in Ihrem Code bemerkt und sie durch einen Aufruf von stpcpy() ersetzt! Dies ist in vielen stackoverflow-Beispielen zu sehen: Beispiel 1 Beispiel 2 Beispiel 3 < a href="https://stackoverflow.com/questions/33977355/cannot-locate-symbol-stpcpy-using-qtds-driver-on-android"> Beispiel 4 . Wenn Sie versuchen, stpcpy() aus Gründen der Abwärtskompatibilität selbst zu implementieren, erhalten Sie eine Endlosschleife, sofern Sie nicht schlau genug sind, sie in ausreichend nicht stpcpy() way ! Wahnsinn.
  • Wenn Sie auf APP_PLATFORM 24 aufbauen, werden Sie auf ein ähnliches Problem mit std::vector::resize stoßen, wie Karu in einem Kommentar von diese Frage ).

In beiden Fällen entscheidet der Compiler, dass er diese Aufrufe einfügen kann, da er die von Ihnen verwendete Menge von Include-Dateien überprüft --- your APP_PLATFORM --- und entscheidet, ob die Aufrufe verfügbar sind. Sie können APP_PLATFORM nicht reduzieren, ohne den Zugriff auf die neuen Routinen zu verlieren, die Sie auf neuen Geräten verwenden möchten. Catch-22.

Wie konnte Google damit durchkommen?

Zusammenfassend ist die effektive NDK-Richtlinie von Google:

  

Jedes Mal, wenn Sie die APP_PLATFORM Ihres Projekts erhöhen, müssen Sie minSdkVersion auf APP_PLATFORM setzen, verhindern, dass Ihre App auf älteren Geräten ausgeführt wird , außer Sie sind dazu bereit massive erschöpfende Tests auf alten Geräten oder beten.

Ich konnte zu diesem Zweck niemals eine offizielle Google-Richtlinie finden. Googles offizielle Dokumentation sagt genau das Gegenteil. Insbesondere diese Passage aus der offiziellen Google-NDK-Level-Dokumentation ist völliger Quatsch:

  

Jede neue Version von NDK-Headern und -Bibliotheken für eine bestimmte Android API-Ebene ist kumulativ; Sie sind fast immer sicher, wenn Sie beim Erstellen Ihrer App die zuletzt veröffentlichten Header verwenden. Beispielsweise können Sie die NDK-Header für Android-API 21 für eine App verwenden, die auf API-Ebene 16 abzielt. Dadurch erhöhen Sie jedoch die Größe Ihres APK.

Tatsächlich ist genau das Gegenteil der Fall: Es ist zwingend erforderlich, API-Level-16-Header für API-Level-16-Geräte zu verwenden. Andernfalls stürzt Ihre App ab, wenn Sie einen großen Satz von APIs wie atof() mit undokumentiertem Brechen verwenden Änderungen.

Die meiste Hilfe, die wir je von Google bekommen haben, war eine kryptische NDK-Build-Warnung mit WARNING: APP_PLATFORM android-XX is larger than android:minSdkVersion ohne jegliche zugehörige Dokumentation, die ich finden konnte.

Ich hoffe, dass diese Antwort für die Entwickler aufschlussreich und hilfreich war. Vielleicht werden wir die Entwickler von Google NDK dazu motivieren, die Rückwärtskompatibilität genauso zu respektieren wie die Java-Entwickler von Google.

Referenzen: viele, viele Links zu anderen stackoverflow Antworten und andere Webseiten sind im obigen Text verschachtelt.

    
Louis Semprini 10.12.2016 19:34
quelle
0

Sie haben recht mit dem Minimum. Ich bin mir nicht sicher, was das Ziel darstellen sollte. Ich denke, es gibt einige Funktionen des Ziel-SDK, aber es stellt auch sicher, dass die App mit dem minimalen SDK ausgeführt wird.

    
Rado 19.02.2014 17:39
quelle

Tags und Links