[Tutorial] JNI-Projekte mit AndroidStudio

  • 11 Antworten
  • Letztes Antwortdatum
Kiwi++Soft

Kiwi++Soft

Ehrenmitglied
32.707
Hallo zusammen,

ich möchte in diesem Tutorial kurz vorstellen, wie man mit AndroidStudio ein JNI Projekt erstellen kann:

JNI-Projekte mit AndroidStudio

Was ist JNI?
JNI steht für Java Native Interface und ist die Möglichkeit, nativen Code mit Java zu verbinden.

Warum sollte man ein JNI-Projekt bauen wollen?

Dafür gibt es verschiedene Gründe:
  • Man kann damit eigenen, nativen Code ausführen, der dann seinerseits auf native Bibliotheken zugreifen kann.
  • Nativer Code kann Performance Vorteile beinhalten.
  • Man macht es Code-Piraten schwerer, an den Code zu kommen. Es ist leichter eine Java-App zu decompilieren, als eine native Library.
Inhalt des Tutorials:

Installation und Projekt Anpassung
JNI 1: Einführung
JNI 2: Object-Aufrufe aus C

Ich wünsche viel Erfolg
 
Zuletzt bearbeitet:
  • Danke
Reaktionen: Kardroid, markus.tullius und TSC Yoda
Was muss man tun, um ein JNI-Projekt zu erstellen?

Das Android NDK (Native Development Kit) herunter laden. Zum Installieren den das Zip-File einfach nach C:\ entpacken, das erzeugt einen Ordner 'C:\android-ndk-r10e'

Ein neues Projekt erzeugen. Als Beispiel erzeugen wir ein Projekt Namens 'JniTut' und Domain 'tutorial.tut'. auf der nächsten Seite wählen wir 'Leere Activity' aus. Dann das Projekt erstellen lassen.

Nun müssen folgende Dateien angepasst werden:

.\gradle\wrapper\gradle-wrapper.properties: Letzte Zeile:

war:
Code:
distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip

soll:
Code:
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip

.\build.gradle: Im Block dependencies:

war:
Code:
        classpath 'com.android.tools.build:gradle:1.5.0'

soll:
Code:
        classpath "com.android.tools.build:gradle-experimental:0.6.0-beta5"

.\local.properties: Am Ende anfügen (wobei C:\android-ndk-r10e das Verzeichnis des vorhin installierten NDK bezeichnet):
Code:
ndk.dir=C\:\\android-ndk-r10e

.\app\build.gradle: Hier muss alles außer dem letzten Abschnitt 'dependencies' angepasst werden:
war:
Code:
apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "tut.tutorial.jnitut"
        minSdkVersion 21
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.1.1'
}
soll:
Code:
apply plugin: 'com.android.model.application'

model {
    android {
        compileSdkVersion 23
        buildToolsVersion "23.0.2"

        defaultConfig {
            applicationId "tut.tutorial.jnitut"
            minSdkVersion.apiLevel 21
            targetSdkVersion.apiLevel 23
            versionCode 1
            versionName "1.0"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles.add(file('proguard-rules.pro'))
            }
        }
        ndk {
            moduleName "JniTut"
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.1.1'
}

Ich hänge das erzeugte Projektverzeichnis als Beispiel an diesen Beitrag an
 

Anhänge

  • JniTut.zip
    3,4 MB · Aufrufe: 308
Zuletzt bearbeitet:
  • Danke
Reaktionen: TSC Yoda
JNI 1: Einführung:

Das Prinzip von JNI ist relativ einfach:

Man definiert in einer Klasse eine Methode, die man mit dem Modifier 'native' kennzeichnet. Dies Methode darf, genau wie eine 'abstract' Methode, keinen Methoden-Body haben.

Dann kompiliert man die Klasse.

Mit Hilfe eines Tools namens 'javah.exe' erzeugt man ein. C-Header Datei, die die genaue C-Declaration der nativen Java-Methode enthält.

Jetzt erstellt man eine C-Source-Datei, und included die Header Datei. Dort implementiert man die im Header declarierte Funktion.

In der Java-Klasse selbst muss man einen 'Static Initializer' schreiben, der die zugehörige native Bibliothek lädt.

Dann kompiliert man das Projekt, und fertig.

Schritt für Schritt:

Man öffnet die Klasse JniActivity und fügt die Definition der nativen Methode ein:
Code:
    private native int addNative(int i1, int i2);
Jetzt kompiliert man das Projekt. Unter .\app\build\intermediates\classes\debug\tut\tutorial\jnitut\ sollte jetzt die Datei JniActivity.class zu finden sein.

Jetzt muss man sich noch einen Ordner namens 'jni' unter .\app\src\main anlegen.

Um das Tool 'javah.exe' leichter aufrufen zu können, legt man sich unter .\app eine Datei javah.bat an. Der Inhalt ist dieser (Beachten: da steckt der Pfad zum Android-SDK drin, diesen an die lokalen Gegebenheiten anpassen!):
Code:
javah.exe -d ".\src\main\jni" -classpath ".\build\intermediates\exploded-aar\com.android.support\appcompat-v7\23.1.1\jars\classes.jar;.\build\intermediates\exploded-aar\com.android.support\support-v4\23.1.1\jars\classes.jar;.\build\intermediates\exploded-aar\com.android.support\support-v4\23.1.1\jars\libs\internal_impl-23.1.1.jar;C:\android-sdk\platforms\android-23\android.jar;.\build\intermediates\classes\debug" tut.tutorial.jnitut.JniActivity
PAUSE

Jetzt kann man die javah.bat Datei durch einen doppelklick im 'Datei-Explorer' getsrtet werden.

Dieser Schritt erzeugt eine Datei namens 'tut_tutorial_jnitut_JniActivity.h' im jni-Ordner.

Jetzt legen wir im jni-Ordner eine neue C-Source-Datei namens jnitut.c an. In der jnitut.c includieren wir die generierte Header-Datei:
Code:
#include "tut_tutorial_jnitut_JniActivity.h"

Jetzt kopieren wir die Declaration der Nativen Methode aus der Header-Datei:
Code:
JNIEXPORT jint JNICALL Java_tut_tutorial_jnitut_JniActivity_addNative
  (JNIEnv *, jobject, jint, jint);

und fügen diese in die Source-Datei ein. Das Semicolon ersetzen wir, geben den Argumenten auch namen und ergänzen einen trivialen Funktions-Body:
Code:
JNIEXPORT jint JNICALL Java_tut_tutorial_jnitut_JniActivity_addNative
        (JNIEnv * env, jobject this, jint i1, jint i2)
{
    return i1 + i2;
}

Wenn wir jetzt 'Build Projekt' aufrufen sollte unter .\app\build\intermediates\binaries\debug\lib\<any arch>\ eine Datei libJniTut.so erzeugt worden sein.

Jetzt benötigen wir in unserer JniActivity noch einen 'Static Initializer':
Code:
    static
    {
        System.loadLibrary("JniTut");
    }

Dann noch einen Test-Aufruf der nativen Methode in die 'onCreate(...)' Methode der JniActivity zufügen:
Code:
        Log.e("JniTut", "Native addNative 2+3 = " + addNative(2, 3));

Wenn wir jetzt einen Test-Lauf machen, sollte im Logcat die folgende Zeile auftauchen:
Code:
02-25 16:00:11.026 21002-21002/tut.tutorial.jnitut E/JniTut: Native addNative 2+3 = 5

Ich hänge den Stand unseres Projektes wieder als kompletter gezipter Projekt-Ordner an diesen Beitrag an.
 

Anhänge

  • JniTut.zip
    3,6 MB · Aufrufe: 321
JNI 2: Object-Aufrufe aus C

Hier möchte ich erklären, wie man aus dem C-Code auch wieder auf Java-Objecte zugreifen kann.

Der Inhalt dieses Posts folgt die nächsten Tage...
 
So richtig den Sinn dahinter versteh ich nicht ganz. Ich schreibe ganz normal Java code und durch das hinzufügen dieser einen Methode + Static Call, ist das dann eine native class die irgendwie besonders ist? Wenn ich jetzt nun eine neue Klasse erstelle, muss ich die in der header file auch includen?

Gruß
 
Eigentlich ist alles in dem Teil 'Schritt für Schritt' schon erklärt. Hast Du das vollständig gelesen?

Für jede Klasse, die 'Native Methoden' enthält, müssen die 'Header-Dateien' erzeugt werden.

Sollte man zwei Klassen haben, die 'Native Methoden' enthalten, müssen natürlich für beide Klassen die Header-Files erzeugt werden. Diese würde man dann am besten dadurch erzeugen, dass man mehrer Einträge in die (selbst erstellte) Datei 'javah.bat' macht:


u.k-f schrieb:
Code:
javah.exe -d ".\src\main\jni" -classpath ".\build\intermediates\exploded-aar\com.android.support\appcompat-v7\23.1.1\jars\classes.jar;.\build\intermediates\exploded-aar\com.android.support\support-v4\23.1.1\jars\classes.jar;.\build\intermediates\exploded-aar\com.android.support\support-v4\23.1.1\jars\libs\internal_impl-23.1.1.jar;C:\android-sdk\platforms\android-23\android.jar;.\build\intermediates\classes\debug" tut.tutorial.jnitut.JniActivity
javah.exe -d ".\src\main\jni" -classpath ".\build\intermediates\exploded-aar\com.android.support\appcompat-v7\23.1.1\jars\classes.jar;.\build\intermediates\exploded-aar\com.android.support\support-v4\23.1.1\jars\classes.jar;.\build\intermediates\exploded-aar\com.android.support\support-v4\23.1.1\jars\libs\internal_impl-23.1.1.jar;C:\android-sdk\platforms\android-23\android.jar;.\build\intermediates\classes\debug" tut.tutorial.jnitut.MyOtherClass
PAUSE

Und dann muss man die bat-Datei ausführen, damit die Header-Files erzeugt werden.

Darin sind sind die 'Declarationen der nativen Methoden' enthalten. Wichtig: Dabei den Unterschied zwischen 'Declaration' und 'Definition' beachten!

Als nächstes legt man ein oder mehrere C-Sourcen-Files an, in denen dann die im Header-File 'deklarierten' Methoden eine 'Definition' bekommen.

Wenn man dann das Projekt erstellt, werden aus den C-Sourcen die entsprechende native C-Library erstellt.

Im Java selbst muss man nichts weiter beachten, eine 'Native Methode' wird im Java genau wie jede 'Pure-Java Methode' aufgerufen.
 
Zuletzt bearbeitet:
Natürlich habe ich es gelesen, nur zum einen verstehe ich immer noch nicht den Sinn hinter der Methode. Also wieso die benötigt wird, wieso nicht alle Methoden nativ deklariert werden müssen usw.

Also ich füge einfach die Klasse addNative(i1, i2) hinzu und danach ist die ganze Klasse Nativ? Wenn ja wieso diese Parameter und nicht andere?
Oder definiere ich in meiner Java Methoden welche ich Nativ haben möchte und die Logik der Methode kommt dann in meine Cpp Datei? Wenn ja, wie greife ich dann dort auf Objekte zu?

Gruß
 
Ich habe dieses Tutorial nur an Leser adressiert, die der Sprache C mächtig sind, denn es würde den Rahmen dieses Tutorials sprengen, bei Adam und Eva zu beginnen.

Daher muss man zum Lesen dieses Tutorials Vorkenntnisse haben. Ein Entwickler sollte wissen, dass es keine 'Wunder' gibt, sondern dass man alles selbst codieren muss. Ein automatisches Umwandeln von Java nach C findet natürlich nicht statt! Es wurde ja auch erklärt, wo man den 'Nativen Code' rein schreiben muss (siehe unten).

Ebenso sollte ein C-Entwickler wissen, was eine Methoden Definition bzw Declaration ist. Trotzdem habe ich eine Erklärung dieses Unterschiedes noch einmal verlinkt:

u.k-f schrieb:
...Darin sind sind die 'Declarationen der nativen Methoden' enthalten. Wichtig: Dabei den Unterschied zwischen 'Declaration' und 'Definition' beachten!...

Im übrigen wurde genau die von Dir gestellte Frage in meinem vorherigen Beitrag noch einmal explizit beantwortet:

u.k-f schrieb:
...Und dann muss man die bat-Datei ausführen, damit die Header-Files erzeugt werden....

Hier wurde jetzt das Header-File mit der Definition der nativen Methode 'addNative(...)' erzeugt. (Diese Information konnte aus dem 'Java-Code' gezogen werden, da dort die 'Java-Seite' der nativen Methode addNative(...) durch 'private native int addNative(int i1, int i2);' definiert ist.

u.k-f schrieb:
...Als nächstes legt man ein oder mehrere C-Sourcen-Files an, in denen dann die im Header-File 'deklarierten' Methoden eine 'Definition' bekommen.

Hier wird die Logik dessen, was in der nativen Methode 'addNative(...)' passieren soll, von dem Entwickler explizit auscodiert.

Und ich habe ja auch die 'C-Inhalte' der nativen Methode mit aufgeschrieben. Wenn man sich das Projekt aus dem Anhang runter lädt, würde man auch die C-Datei in dem Projekt finden.

Aber mal ganz ehrlich, wenn man aus einem fertigen, compilierenden Projekt mit zusätzlichen Erklärungen nicht hinter das Prinzip des JNI kommt, sollte man sich lieber gar nicht mit dem Thema beschäftigen, denn das Erstellen des Projektes ist noch der geringste Grad der Komplexität dieses Themas.
 
Ich bin Hauptberuflich als C++ Entwickler unterwegs und ich finde halt nicht, dass es so ganz aus deinem Tutorial herauskommt, mit dem eigenen Code in der Source Datei etc.

Ich sehe JNI nur bedingt nützlich, da ich nichts davon sehe wie ich mein Objekt weiterhin dann in der Methode nutzen kann. Ich kann mir gut vorstellen, dass es irgendwie geht. Ich kann mir gut vorstellen das die Performanceerhöhung dann im verhältnis Wartung vom Code, welcher wahrscheinlich leiden muss, sich nicht lohnt.

Gruß
 
eTexx schrieb:
... ich finde halt nicht, dass es so ganz aus deinem Tutorial herauskommt, mit dem eigenen Code in der Source Datei etc...

Es wurde bereits in dem Tutorial an dieser Stelle geschrieben:

u.k-f schrieb:
...Jetzt erstellt man eine C-Source-Datei, und included die Header Datei. Dort implementiert man die im Header declarierte Funktion.....

Und dann noch ein weiteres Mal hier:

u.k-f schrieb:
Als nächstes legt man ein oder mehrere C-Sourcen-Files an, in denen dann die im Header-File 'deklarierten' Methoden eine 'Definition' bekommen...

Wie deutlich soll man es denn noch schreiben???

Deswegen habe ich ja auch gefragt, ob Du das Tutrial gelesen hast:

u.k-f schrieb:
Hast Du das vollständig gelesen

eTexx schrieb:
Natürlich habe ich es gelesen

Aber genau daran muss ich leider zweifeln. Zumindest wenn man mit 'Gelesen' vollständiges Lesen und nicht nur 'Überfliegen' meint.

eTexx schrieb:
...Ich sehe JNI nur bedingt nützlich, da ich nichts davon sehe wie ich mein Objekt weiterhin dann in der Methode nutzen kann. Ich kann mir gut vorstellen, dass es irgendwie geht...

Dass man es kann, habe ich auch bereits mitgeteilt:
u.k-f schrieb:
...Hier möchte ich erklären, wie man aus dem C-Code auch wieder auf Java-Objecte zugreifen kann.

Der Inhalt dieses Posts folgt die nächsten Tage...

Es fehlt zwar noch die Erklärung, wie es geht, aber dass es geht ist schon beschrieben...

Der Sinn von JNI ist aber eigentlich weniger in der Performance zu sehen, sondern mehr darin, dass man in C auf Resourcen zugreifen kann, die in Java nicht erreichbar sind.

Und ein weiterer Vorteil einer (teilweise) nativen Implementierung besteht darin, dass ein mieser kleiner Code-Pirat im allgemeinen davon überfordert ist, den Code einer nativen Bibliothek zu hacken...
 
Zuletzt bearbeitet:
  • Danke
Reaktionen: markus.tullius
Danke für das Tut, sehr aufschlussreich wie es aktuell funktioniert.

eTexx schrieb:
Ich kann mir gut vorstellen das die Performanceerhöhung dann im verhältnis Wartung vom Code, welcher wahrscheinlich leiden muss, sich nicht lohnt.

Es kann sich schon performancetechnisch lohnen. In meiner App habe ich cryptographische Funktionen, für die es Java Implementierungen und native C Implementierungen gibt. Wenn ich die Java Versionen verwende dauert das ganze ca. 40-60 Sekunden, mit den C Funktionen 5.
 
  • Danke
Reaktionen: Kiwi++Soft
Ich habe damals auch nativen Code verwendet um eine Bewegungserkennung zu implementieren, da es ohne openGL einfach zu lange gedauert hat.
Damals war es mit Eclipse noch einigermaßen einfach.

Danke für das Tutorial!
 
  • Danke
Reaktionen: Kiwi++Soft

Ähnliche Themen

L
Antworten
5
Aufrufe
1.024
jogimuc
J
L
Antworten
0
Aufrufe
608
louisbgt
L
Zurück
Oben Unten